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