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
71DEFINE_BOOL_CONFIG_OPTION(disableDiskCache, QML_DISABLE_DISK_CACHE);
72DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE);
73
74QT_BEGIN_NAMESPACE
75
76namespace {
77
78 template<typename LockType>
79 struct LockHolder
80 {
81 LockType& lock;
82 LockHolder(LockType *l) : lock(*l) { lock.lock(); }
83 ~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
92The QQmlTypeLoader class is provided for the exclusive use of the QQmlTypeLoader class.
93
94Clients create QQmlDataBlob instances and submit them to the QQmlTypeLoader class
95through the QQmlTypeLoader::load() or QQmlTypeLoader::loadWithStaticData() methods.
96The loader then fetches the data over the network or from the local file system in an efficient way.
97QQmlDataBlob is an abstract class, so should always be specialized.
98
99Once data is received, the QQmlDataBlob::dataReceived() method is invoked on the blob. The
100derived class should use this callback to process the received data. Processing of the data can
101result in an error being set (QQmlDataBlob::setError()), or one or more dependencies being
102created (QQmlDataBlob::addDependency()). Dependencies are other QQmlDataBlob's that
103are required before processing can fully complete.
104
105To complete processing, the QQmlDataBlob::done() callback is invoked. done() is called when
106one 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
114Thus QQmlDataBlob::done() will always eventually be called, even if the blob has an error set.
115*/
116
117void 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)
136void QQmlTypeLoader::setProfiler(QQmlProfiler *profiler)
137{
138 Q_ASSERT(!m_profiler);
139 m_profiler.reset(other: profiler);
140}
141#endif
142
143struct PlainLoader {
144 void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
145 {
146 loader->loadThread(blob);
147 }
148 void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
149 {
150 loader->m_thread->load(b: blob);
151 }
152 void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
153 {
154 loader->m_thread->loadAsync(b: blob);
155 }
156};
157
158struct StaticLoader {
159 const QByteArray &data;
160 StaticLoader(const QByteArray &data) : data(data) {}
161
162 void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
163 {
164 loader->loadWithStaticDataThread(blob, data);
165 }
166 void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
167 {
168 loader->m_thread->loadWithStaticData(b: blob, data);
169 }
170 void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
171 {
172 loader->m_thread->loadWithStaticDataAsync(b: blob, data);
173 }
174};
175
176struct CachedLoader {
177 const QV4::CompiledData::Unit *unit;
178 CachedLoader(const QV4::CompiledData::Unit *unit) : unit(unit) {}
179
180 void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
181 {
182 loader->loadWithCachedUnitThread(blob, unit);
183 }
184 void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
185 {
186 loader->m_thread->loadWithCachedUnit(b: blob, unit);
187 }
188 void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
189 {
190 loader->m_thread->loadWithCachedUnitAsync(b: blob, unit);
191 }
192};
193
194template<typename Loader>
195void 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/*!
231Load the provided \a blob from the network or filesystem.
232
233The loader must be locked.
234*/
235void QQmlTypeLoader::load(QQmlDataBlob *blob, Mode mode)
236{
237 doLoad(loader: PlainLoader(), blob, mode);
238}
239
240/*!
241Load the provided \a blob with \a data. The blob's URL is not used by the data loader in this case.
242
243The loader must be locked.
244*/
245void QQmlTypeLoader::loadWithStaticData(QQmlDataBlob *blob, const QByteArray &data, Mode mode)
246{
247 doLoad(loader: StaticLoader(data), blob, mode);
248}
249
250void QQmlTypeLoader::loadWithCachedUnit(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit, Mode mode)
251{
252 doLoad(loader: CachedLoader(unit), blob, mode);
253}
254
255void QQmlTypeLoader::loadWithStaticDataThread(QQmlDataBlob *blob, const QByteArray &data)
256{
257 ASSERT_LOADTHREAD();
258
259 setData(blob, data);
260}
261
262void QQmlTypeLoader::loadWithCachedUnitThread(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit)
263{
264 ASSERT_LOADTHREAD();
265
266 setCachedUnit(blob, unit);
267}
268
269void 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(url: 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(b: blob, p: 1.);
298
299 setData(blob, fileName);
300
301 } else {
302#if QT_CONFIG(qml_network)
303 QNetworkReply *reply = m_thread->networkAccessManager()->get(request: QNetworkRequest(blob->m_url));
304 QQmlTypeLoaderNetworkReplyProxy *nrp = m_thread->networkReplyProxy();
305 blob->addref();
306 m_networkReplies.insert(akey: reply, avalue: blob);
307
308 if (reply->isFinished()) {
309 nrp->manualFinished(reply);
310 } else {
311 QObject::connect(sender: reply, SIGNAL(downloadProgress(qint64,qint64)),
312 receiver: nrp, SLOT(downloadProgress(qint64,qint64)));
313 QObject::connect(sender: reply, SIGNAL(finished()),
314 receiver: 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)
331void QQmlTypeLoader::networkReplyFinished(QNetworkReply *reply)
332{
333 Q_ASSERT(m_thread->isThisThread());
334
335 reply->deleteLater();
336
337 QQmlDataBlob *blob = m_networkReplies.take(akey: 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(code: QNetworkRequest::RedirectionTargetAttribute);
345 if (redirect.isValid()) {
346 QUrl url = reply->url().resolved(relative: redirect.toUrl());
347 blob->m_finalUrl = url;
348 blob->m_finalUrlString.clear();
349
350 QNetworkReply *reply = m_thread->networkAccessManager()->get(request: QNetworkRequest(url));
351 QObject *nrp = m_thread->networkReplyProxy();
352 QObject::connect(sender: reply, SIGNAL(finished()), receiver: nrp, SLOT(finished()));
353 m_networkReplies.insert(akey: reply, avalue: 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
371void QQmlTypeLoader::networkReplyProgress(QNetworkReply *reply,
372 qint64 bytesReceived, qint64 bytesTotal)
373{
374 Q_ASSERT(m_thread->isThisThread());
375
376 QQmlDataBlob *blob = m_networkReplies.value(akey: 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(b: blob, p: blob->m_data.progress());
385 }
386}
387#endif // qml_network
388
389/*!
390Return the QQmlEngine associated with this loader
391*/
392QQmlEngine *QQmlTypeLoader::engine() const
393{
394 return m_engine;
395}
396
397/*! \internal
398Call the initializeEngine() method on \a iface. Used by QQmlImportDatabase to ensure it
399gets called in the correct thread.
400*/
401template<class Interface>
402void 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
415void QQmlTypeLoader::initializeEngine(QQmlEngineExtensionInterface *iface, const char *uri)
416{
417 doInitializeEngine(iface, thread: m_thread, engine: engine(), uri);
418}
419
420void QQmlTypeLoader::initializeEngine(QQmlExtensionInterface *iface, const char *uri)
421{
422 doInitializeEngine(iface, thread: m_thread, engine: engine(), uri);
423}
424
425void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QByteArray &data)
426{
427 QQmlDataBlob::SourceCodeData d;
428 d.inlineSourceCode = QString::fromUtf8(str: data);
429 d.hasInlineSourceCode = true;
430 setData(blob, d);
431}
432
433void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QString &fileName)
434{
435 QQmlDataBlob::SourceCodeData d;
436 d.fileInfo = QFileInfo(fileName);
437 setData(blob, d);
438}
439
440void 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
460void 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
480void QQmlTypeLoader::shutdownThread()
481{
482 if (m_thread && !m_thread->isShutdown())
483 m_thread->shutdown();
484}
485
486QQmlTypeLoader::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
496QQmlTypeLoader::Blob::Blob(const QUrl &url, QQmlDataBlob::Type type, QQmlTypeLoader *loader)
497 : QQmlDataBlob(url, type, loader), m_importCache(loader)
498{
499}
500
501QQmlTypeLoader::Blob::~Blob()
502{
503}
504
505bool 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
525bool QQmlTypeLoader::Blob::updateQmldir(const QQmlRefPointer<QQmlQmldirData> &data, PendingImportPtr import, QList<QQmlError> *errors)
526{
527 QString qmldirIdentifier = data->urlString();
528 QString qmldirUrl = qmldirIdentifier.left(n: qmldirIdentifier.lastIndexOf(c: QLatin1Char('/')) + 1);
529
530 typeLoader()->setQmldirContent(filePath: qmldirIdentifier, content: data->content());
531
532 if (!m_importCache.updateQmldirContent(importDb: typeLoader()->importDatabase(), uri: import->uri, prefix: import->qualifier, qmldirIdentifier, qmldirUrl, errors))
533 return false;
534
535 if (!loadImportDependencies(currentImport: import, qmldirUri: 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(filePath: qmldirIdentifier);
547 const auto qmldirScripts = qmldir.scripts();
548 for (const QQmlDirParser::Script &script : qmldirScripts) {
549 QUrl scriptUrl = libraryUrl.resolved(relative: QUrl(script.fileName));
550 QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(unNormalizedUrl: scriptUrl);
551 addDependency(blob.data());
552
553 scriptImported(blob, import->location, script.nameSpace, import->qualifier);
554 }
555 }
556
557 return true;
558}
559
560bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import, QList<QQmlError> *errors)
561{
562 return addImport(import: std::make_shared<PendingImport>(args: this, args&: import), errors);
563}
564
565bool 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(relative: QUrl(import->uri));
573 QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(unNormalizedUrl: 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, uri: import->uri, vmaj: import->majorVersion, vmin: import->minorVersion,
583 qmldirFilePath: &qmldirFilePath, url: &qmldirUrl);
584 if (qmldirResult == QQmlImports::QmldirFound) {
585 // This is a local library import
586 if (!m_importCache.addLibraryImport(importDb: importDatabase, uri: import->uri, prefix: import->qualifier, vmaj: import->majorVersion,
587 vmin: import->minorVersion, qmldirIdentifier: qmldirFilePath, qmldirUrl, incomplete: false, errors))
588 return false;
589
590 if (!loadImportDependencies(currentImport: import, qmldirUri: 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(filePath: qmldirFilePath);
597 const auto qmldirScripts = qmldir.scripts();
598 for (const QQmlDirParser::Script &script : qmldirScripts) {
599 QUrl scriptUrl = libraryUrl.resolved(relative: QUrl(script.fileName));
600 QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(unNormalizedUrl: 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(uri: import->uri, majorVersion: import->majorVersion)
610
611 // Otherwise, try to register further module types.
612 || (qmldirResult != QQmlImports::QmldirInterceptedToRemote
613 && QQmlMetaType::qmlRegisterModuleTypes(uri: import->uri, majorVersion: 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(uri: import->uri)) {
618
619 if (!m_importCache.addLibraryImport(
620 importDb: importDatabase, uri: import->uri, prefix: import->qualifier, vmaj: import->majorVersion,
621 vmin: import->minorVersion, qmldirIdentifier: QString(), qmldirUrl: QString(), incomplete: 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 type: 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(importDb: importDatabase, uri: import->uri, prefix: import->qualifier, vmaj: import->majorVersion,
638 vmin: import->minorVersion, qmldirIdentifier: QString(), qmldirUrl: QString(), incomplete: true, errors))
639 return false;
640
641 // Probe for all possible locations
642 int priority = 0;
643 const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(uri: import->uri, basePaths: remotePathList, vmaj: import->majorVersion, vmin: import->minorVersion);
644 for (const QString &qmldirPath : qmlDirPaths) {
645 if (interceptor) {
646 QUrl url = interceptor->intercept(
647 path: QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
648 type: QQmlAbstractUrlInterceptor::QmldirFile);
649 if (!QQmlFile::isLocalFile(url)
650 && !fetchQmldir(url, import, priority: ++priority, errors)) {
651 return false;
652 }
653 } else if (!fetchQmldir(url: QUrl(qmldirPath), import, priority: ++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(s: QLatin1String(path.endsWith(c: QLatin1Char('/')) ? "qmldir" : "/qmldir"));
668 importUrl.setPath(path);
669 QUrl qmldirUrl = finalUrl().resolved(relative: importUrl);
670 if (!QQmlImports::isLocal(url: qmldirUrl)) {
671 // This is a remote file; the import is currently incomplete
672 incomplete = true;
673 }
674
675 if (!m_importCache.addFileImport(importDatabase, uri: import->uri, prefix: import->qualifier, vmaj: import->majorVersion,
676 vmin: import->minorVersion, incomplete, errors))
677 return false;
678
679 if (incomplete) {
680 if (!fetchQmldir(url: qmldirUrl, import, priority: 1, errors))
681 return false;
682 }
683 }
684
685 return true;
686}
687
688void 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>(n: import->location.line));
701 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: import->location.column));
702 errors.prepend(t: error); // put it back on the list after filling out information.
703 setError(errors);
704 }
705 }
706}
707
708bool QQmlTypeLoader::Blob::loadImportDependencies(PendingImportPtr currentImport, const QString &qmldirUri, QList<QQmlError> *errors)
709{
710 const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(filePath: 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(import: dependencyImport, errors))
718 return false;
719 }
720 return true;
721}
722
723bool QQmlTypeLoader::Blob::isDebugging() const
724{
725 return typeLoader()->engine()->handle()->debugger() != nullptr;
726}
727
728bool QQmlTypeLoader::Blob::diskCacheEnabled() const
729{
730 return (!disableDiskCache() && !isDebugging()) || forceDiskCache();
731}
732
733bool 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/*!
760Constructs a new type loader that uses the given \a engine.
761*/
762QQmlTypeLoader::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/*!
771Destroys the type loader, first clearing the cache of any information about
772loaded files.
773*/
774QQmlTypeLoader::~QQmlTypeLoader()
775{
776 // Stop the loader thread before releasing resources
777 shutdownThread();
778
779 clearCache();
780
781 invalidate();
782}
783
784QQmlImportDatabase *QQmlTypeLoader::importDatabase() const
785{
786 return &QQmlEnginePrivate::get(e: engine())->importDatabase;
787}
788
789QUrl QQmlTypeLoader::normalize(const QUrl &unNormalizedUrl)
790{
791 QUrl normalized(unNormalizedUrl);
792 if (normalized.scheme() == QLatin1String("qrc"))
793 normalized.setHost(host: QString()); // map qrc:///a.qml to qrc:/a.qml
794 return normalized;
795}
796
797/*!
798Returns a QQmlTypeData for the specified \a url. The QQmlTypeData may be cached.
799*/
800QQmlRefPointer<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(akey: 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(akey: url, avalue: typeData);
820 QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
821 if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(uri: typeData->url(), status: &error)) {
822 QQmlTypeLoader::loadWithCachedUnit(blob: typeData, unit: cachedUnit, mode);
823 } else {
824 typeData->setCachedUnitStatus(error);
825 QQmlTypeLoader::load(blob: 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/*!
847Returns a QQmlTypeData for the given \a data with the provided base \a url. The
848QQmlTypeData will not be cached.
849*/
850QQmlRefPointer<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(blob: typeData, data, mode);
856
857 return QQmlRefPointer<QQmlTypeData>(typeData, QQmlRefPointer<QQmlTypeData>::Adopt);
858}
859
860/*!
861Return a QQmlScriptBlob for \a url. The QQmlScriptData may be cached.
862*/
863QQmlRefPointer<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(akey: url);
874
875 if (!scriptBlob) {
876 scriptBlob = new QQmlScriptBlob(url, this);
877 m_scriptCache.insert(akey: url, avalue: scriptBlob);
878
879 QQmlMetaType::CachedUnitLookupError error;
880 if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(uri: scriptBlob->url(), status: &error)) {
881 QQmlTypeLoader::loadWithCachedUnit(blob: scriptBlob, unit: cachedUnit);
882 } else {
883 scriptBlob->setCachedUnitStatus(error);
884 QQmlTypeLoader::load(blob: scriptBlob);
885 }
886 }
887
888 return scriptBlob;
889}
890
891/*!
892Returns a QQmlQmldirData for \a url. The QQmlQmldirData may be cached.
893*/
894QQmlRefPointer<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(akey: url);
902
903 if (!qmldirData) {
904 qmldirData = new QQmlQmldirData(url, this);
905 m_qmldirCache.insert(akey: url, avalue: qmldirData);
906 QQmlTypeLoader::load(blob: qmldirData);
907 }
908
909 return qmldirData;
910}
911
912/*!
913Returns the absolute filename of path via a directory cache.
914Returns a empty string if the path does not exist.
915
916Why a directory cache? QML checks for files in many paths with
917invalid directories. By caching whether a directory exists
918we avoid many stats. We also cache the files' existence in the
919directory, for the same reason.
920*/
921QString QQmlTypeLoader::absoluteFilePath(const QString &path)
922{
923 if (path.isEmpty())
924 return QString();
925 if (path.at(i: 0) == QLatin1Char(':')) {
926 // qrc resource
927 QFileInfo fileInfo(path);
928 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
929 } else if (path.count() > 3 && path.at(i: 3) == QLatin1Char(':') &&
930 path.startsWith(s: QLatin1String("qrc"), cs: 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(c: QLatin1Char('/'));
950 QString dirPath(path.left(n: lastSlash));
951
952 LockHolder<QQmlTypeLoader> holder(this);
953 if (!m_importDirCache.contains(key: dirPath)) {
954 bool exists = QDir(dirPath).exists();
955 QCache<QString, bool> *entry = exists ? new QCache<QString, bool> : nullptr;
956 m_importDirCache.insert(akey: dirPath, aobject: entry);
957 }
958 QCache<QString, bool> *fileSet = m_importDirCache.object(key: dirPath);
959 if (!fileSet)
960 return QString();
961
962 QString absoluteFilePath;
963 QString fileName(path.mid(position: lastSlash+1, n: path.length()-lastSlash-1));
964
965 bool *value = fileSet->object(key: fileName);
966 if (value) {
967 if (*value)
968 absoluteFilePath = path;
969 } else {
970 bool exists = QFile::exists(fileName: path);
971 fileSet->insert(akey: fileName, aobject: new bool(exists));
972 if (exists)
973 absoluteFilePath = path;
974 }
975
976 if (absoluteFilePath.length() > 2 && absoluteFilePath.at(i: 0) != QLatin1Char('/') && absoluteFilePath.at(i: 1) != QLatin1Char(':'))
977 absoluteFilePath = QFileInfo(absoluteFilePath).absoluteFilePath();
978
979 return absoluteFilePath;
980}
981
982bool QQmlTypeLoader::fileExists(const QString &path, const QString &file)
983{
984 const QChar nullChar(QChar::Null);
985 if (path.isEmpty() || path.contains(c: nullChar) || file.isEmpty() || file.contains(c: 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(key: path);
992 if (fileSet) {
993 if (bool *value = fileSet->object(key: file))
994 return *value;
995 } else if (m_importDirCache.contains(key: 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(akey: path, aobject: fileSet);
1004 if (!fileSet)
1005 return false;
1006 }
1007
1008 const bool exists = fileInfo.exists();
1009 fileSet->insert(akey: file, aobject: new bool(exists));
1010 return exists;
1011 };
1012
1013 if (path.at(i: 0) == QLatin1Char(':')) {
1014 // qrc resource
1015 return addToCache(QFileInfo(path + file));
1016 }
1017
1018 if (path.count() > 3 && path.at(i: 3) == QLatin1Char(':')
1019 && path.startsWith(s: QLatin1String("qrc"), cs: 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/*!
1043Returns true if the path is a directory via a directory cache. Cache is
1044shared with absoluteFilePath().
1045*/
1046bool QQmlTypeLoader::directoryExists(const QString &path)
1047{
1048 if (path.isEmpty())
1049 return false;
1050
1051 bool isResource = path.at(i: 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(c: QLatin1Char('/')))
1064 --length;
1065 QString dirPath(path.left(n: length));
1066
1067 LockHolder<QQmlTypeLoader> holder(this);
1068 if (!m_importDirCache.contains(key: dirPath)) {
1069 bool exists = QDir(dirPath).exists();
1070 QCache<QString, bool> *files = exists ? new QCache<QString, bool> : nullptr;
1071 m_importDirCache.insert(akey: dirPath, aobject: files);
1072 }
1073
1074 QCache<QString, bool> *fileSet = m_importDirCache.object(key: dirPath);
1075 return fileSet != nullptr;
1076}
1077
1078
1079/*!
1080Return a QQmlTypeLoaderQmldirContent for absoluteFilePath. The QQmlTypeLoaderQmldirContent may be cached.
1081
1082\a filePath is a local file path.
1083
1084It can also be a remote path for a remote directory import, but it will have been cached by now in this case.
1085*/
1086const 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(key: filePathIn))
1105 return **entry;
1106 else
1107 return QQmlTypeLoaderQmldirContent();
1108 }
1109 }
1110
1111 QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(key: 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(fileName: filePath)) {
1122 ERROR(CASE_MISMATCH_ERROR.arg(filePath));
1123 } else if (file.open(flags: QFile::ReadOnly)) {
1124 QByteArray data = file.readAll();
1125 qmldir->setContent(location: filePath, content: QString::fromUtf8(str: 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(key: filePath, value: qmldir);
1135 return *qmldir;
1136}
1137
1138void QQmlTypeLoader::setQmldirContent(const QString &url, const QString &content)
1139{
1140 QQmlTypeLoaderQmldirContent *qmldir;
1141 QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(key: url);
1142 if (val) {
1143 qmldir = *val;
1144 } else {
1145 qmldir = new QQmlTypeLoaderQmldirContent;
1146 m_importQmlDirCache.insert(key: url, value: qmldir);
1147 }
1148
1149 if (!qmldir->hasContent())
1150 qmldir->setContent(location: url, content);
1151}
1152
1153/*!
1154Clears cached information about loaded files, including any type data, scripts
1155and qmldir information.
1156*/
1157void 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(c: 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
1177void 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(a: size * 2, TYPELOADER_MINIMUM_TRIM_THRESHOLD);
1184}
1185
1186void 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(t: 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(it: 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
1221bool QQmlTypeLoader::isTypeLoaded(const QUrl &url) const
1222{
1223 LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this));
1224 return m_typeCache.contains(akey: url);
1225}
1226
1227bool QQmlTypeLoader::isScriptLoaded(const QUrl &url) const
1228{
1229 LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this));
1230 return m_scriptCache.contains(akey: url);
1231}
1232
1233QT_END_NAMESPACE
1234

source code of qtdeclarative/src/qml/qml/qqmltypeloader.cpp