Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmldatablob.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 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// Qt-Security score:significant
4
5#include <private/qqmldatablob_p.h>
6#include <private/qqmlglobal_p.h>
7#include <private/qqmlprofiler_p.h>
8#include <private/qqmlsourcecoordinate_p.h>
9#include <private/qqmltypedata_p.h>
10#include <private/qqmltypeloader_p.h>
11#include <private/qqmltypeloaderthread_p.h>
12
13#include <QtQml/qqmlengine.h>
14
15#include <QtCore/qcryptographichash.h>
16
17#include <qtqml_tracepoints_p.h>
18
20
21QT_BEGIN_NAMESPACE
22
23/*!
24\class QQmlDataBlob
25\brief The QQmlDataBlob encapsulates a data request that can be issued to a QQmlTypeLoader.
26\internal
27
28QQmlDataBlob's are loaded by a QQmlTypeLoader. The user creates the QQmlDataBlob
29and then calls QQmlTypeLoader::load() or QQmlTypeLoader::loadWithStaticData() to load it.
30The QQmlTypeLoader invokes callbacks on the QQmlDataBlob as data becomes available.
31*/
32
33/*!
34\enum QQmlDataBlob::Status
35
36This enum describes the status of the data blob.
37
38\value Null The blob has not yet been loaded by a QQmlTypeLoader
39\value Loading The blob is loading network data. The QQmlDataBlob::setData() callback has
40 not yet been invoked or has not yet returned.
41\value WaitingForDependencies The blob is waiting for dependencies to be done before continuing.
42 This status only occurs after the QQmlDataBlob::setData() callback has been made,
43 and when the blob has outstanding dependencies.
44\value Complete The blob's data has been loaded and all dependencies are done.
45\value Error An error has been set on this blob.
46*/
47
48/*!
49\enum QQmlDataBlob::Type
50
51This enum describes the type of the data blob.
52
53\value QmlFile This is a QQmlTypeData
54\value JavaScriptFile This is a QQmlScriptData
55\value QmldirFile This is a QQmlQmldirData
56*/
57
58/*!
59Create a new QQmlDataBlob for \a url and of the provided \a type.
60*/
61QQmlDataBlob::QQmlDataBlob(const QUrl &url, Type type, QQmlTypeLoader *manager)
62 : m_typeLoader(manager)
63 , m_type(type)
64 , m_url(manager->interceptUrl(url, (QQmlAbstractUrlInterceptor::DataType)type))
65 , m_finalUrl(url)
66 , m_redirectCount(0)
67 , m_inCallback(false)
68 , m_isDone(false)
69{
70}
71
72/*! \internal */
73QQmlDataBlob::~QQmlDataBlob()
74{
75 Q_ASSERT(m_waitingOnMe.isEmpty());
76
77 // Deleting a QQmlDataBlob in the engine thread is conceptually dangerous
78 // because it manipulates other blobs' m_waitingFor lists. We can guarantee
79 // that the list is empty if the blob isCompleteOrError. Therefore, in such
80 // a case, it's fine to delete it from the engine thread.
81 // Furthermore, if the type loader thread is (temporarily or permanently)
82 // shut down, we cannot run into concurrency here.
83 //
84 // Unfortunately, the typeLoader pointer itself may be dangling at this point
85 // if the QQmlDataBlob is destroyed after the engine. That can happen if you
86 // hold on to a QQmlComponent for longer than the engine it belongs to. You
87 // shouldn't do it, but the mistake is very common and harmless if you don't
88 // touch the component anymore after the engine is gone. In order to actually
89 // assert on the thread safety here, we'd have to touch the typeLoader pointer.
90 // Q_ASSERT(isCompleteOrError() || isTypeLoaderThread() || !isTypeLoaderThreadRunning());
91
92 cancelAllWaitingFor();
93}
94
95/*!
96 Must be called before loading can occur.
97*/
98void QQmlDataBlob::startLoading()
99{
100 // This can be called on either thread but since both status() and setStatus() are atomic
101 // this is fine.
102 Q_ASSERT(status() == QQmlDataBlob::Null);
103 setStatus(QQmlDataBlob::Loading);
104}
105
106/*!
107Returns the type provided to the constructor.
108*/
109QQmlDataBlob::Type QQmlDataBlob::type() const
110{
111 return m_type;
112}
113
114/*!
115Returns the blob's status.
116*/
117QQmlDataBlob::Status QQmlDataBlob::status() const
118{
119 return m_data.status();
120}
121
122/*!
123Returns true if the status is Null.
124*/
125bool QQmlDataBlob::isNull() const
126{
127 return status() == Null;
128}
129
130/*!
131Returns true if the status is Loading.
132*/
133bool QQmlDataBlob::isLoading() const
134{
135 return status() == Loading;
136}
137
138/*!
139Returns true if the status is WaitingForDependencies.
140*/
141bool QQmlDataBlob::isWaiting() const
142{
143 return status() == WaitingForDependencies ||
144 status() == ResolvingDependencies;
145}
146
147/*!
148Returns true if the status is Complete.
149*/
150bool QQmlDataBlob::isComplete() const
151{
152 return status() == Complete;
153}
154
155/*!
156Returns true if the status is Error.
157*/
158bool QQmlDataBlob::isError() const
159{
160 return status() == Error;
161}
162
163/*!
164Returns true if the status is Complete or Error.
165*/
166bool QQmlDataBlob::isCompleteOrError() const
167{
168 Status s = status();
169 return s == Error || s == Complete;
170}
171
172bool QQmlDataBlob::isAsync() const
173{
174 return m_data.isAsync();
175}
176
177/*!
178Returns the data download progress from 0 to 1.
179*/
180qreal QQmlDataBlob::progress() const
181{
182 return m_data.progress();
183}
184
185/*!
186Returns the physical url of the data. Initially this is the same as
187finalUrl(), but if a URL interceptor is set, it will work on this URL
188and leave finalUrl() alone.
189
190\sa finalUrl()
191*/
192QUrl QQmlDataBlob::url() const
193{
194 return m_url;
195}
196
197QString QQmlDataBlob::urlString() const
198{
199 // TODO: This is dangerous. It can be called from either thread.
200 if (m_urlString.isEmpty())
201 m_urlString = m_url.toString();
202
203 return m_urlString;
204}
205
206/*!
207Returns the logical URL to be used for resolving further URLs referred to in
208the code.
209
210This is the blob url passed to the constructor. If a URL interceptor rewrites
211the URL, this one stays the same. If a network redirect happens while fetching
212the data, this url is updated to reflect the new location. Therefore, if both
213an interception and a redirection happen, the final url will indirectly
214incorporate the result of the interception, potentially breaking further
215lookups.
216
217\sa url()
218*/
219QUrl QQmlDataBlob::finalUrl() const
220{
221 return m_finalUrl;
222}
223
224/*!
225Returns the finalUrl() as a string.
226*/
227QString QQmlDataBlob::finalUrlString() const
228{
229 // TODO: This is dangerous. It can be called from either thread.
230
231 if (m_finalUrlString.isEmpty())
232 m_finalUrlString = m_finalUrl.toString();
233
234 return m_finalUrlString;
235}
236
237/*!
238Return the errors on this blob.
239
240May only be called from the load thread, or after the blob isCompleteOrError().
241*/
242QList<QQmlError> QQmlDataBlob::errors() const
243{
244 Q_ASSERT(isCompleteOrError()
245 || !m_typeLoader
246 || !m_typeLoader->thread()
247 || m_typeLoader->thread()->isThisThread());
248 return m_errors;
249}
250
251/*!
252Mark this blob as having \a errors.
253
254All outstanding dependencies will be cancelled. Requests to add new dependencies
255will be ignored. Entry into the Error state is irreversable.
256
257The setError() method may only be called from within a QQmlDataBlob callback.
258*/
259void QQmlDataBlob::setError(const QQmlError &errors)
260{
261 assertTypeLoaderThread();
262
263 QList<QQmlError> l;
264 l << errors;
265 setError(l);
266}
267
268/*!
269\overload
270*/
271void QQmlDataBlob::setError(const QList<QQmlError> &errors)
272{
273 assertTypeLoaderThread();
274
275 Q_ASSERT(status() != Error);
276 Q_ASSERT(m_errors.isEmpty());
277
278 // m_errors must be set before the m_data fence
279 m_errors.reserve(errors.size());
280 for (const QQmlError &error : errors) {
281 if (error.url().isEmpty()) {
282 QQmlError mutableError = error;
283 mutableError.setUrl(url());
284 m_errors.append(mutableError);
285 } else {
286 m_errors.append(error);
287 }
288 }
289
290 cancelAllWaitingFor();
291 setStatus(Error);
292
293 if (dumpErrors()) {
294 qWarning().nospace() << "Errors for " << urlString();
295 for (int ii = 0; ii < errors.size(); ++ii)
296 qWarning().nospace() << " " << qPrintable(errors.at(ii).toString());
297 }
298
299 if (!m_inCallback)
300 tryDone();
301}
302
303void QQmlDataBlob::setError(const QQmlJS::DiagnosticMessage &error)
304{
305 assertTypeLoaderThread();
306 QQmlError e;
307 e.setColumn(qmlConvertSourceCoordinate<quint32, int>(error.loc.startColumn));
308 e.setLine(qmlConvertSourceCoordinate<quint32, int>(error.loc.startLine));
309 e.setDescription(error.message);
310 e.setUrl(url());
311 setError(e);
312}
313
314void QQmlDataBlob::setError(const QString &description)
315{
316 assertTypeLoaderThread();
317 QQmlError e;
318 e.setDescription(description);
319 e.setUrl(url());
320 setError(e);
321}
322
323/*!
324Wait for \a blob to become complete or to error. If \a blob is already
325complete or in error, or this blob is already complete, this has no effect.
326
327The setError() method may only be called from within a QQmlDataBlob callback.
328*/
329void QQmlDataBlob::addDependency(const QQmlDataBlob::Ptr &blob)
330{
331 assertTypeLoaderThread();
332
333 Q_ASSERT(status() != Null);
334
335 if (!blob ||
336 blob->status() == Error || blob->status() == Complete ||
337 status() == Error || status() == Complete || m_isDone)
338 return;
339
340 for (const auto &existingDep: std::as_const(m_waitingFor)) {
341 if (existingDep.data() == blob)
342 return;
343 }
344
345 m_waitingFor.append(blob);
346 blob->m_waitingOnMe.append(this);
347
348 setStatus(WaitingForDependencies);
349
350 // Check circular dependency
351 if (m_waitingOnMe.indexOf(blob.data()) >= 0) {
352 qCWarning(lcCycle) << "Cyclic dependency detected between" << this->url().toString()
353 << "and" << blob->url().toString();
354 QQmlError error;
355 error.setUrl(url());
356 error.setDescription(QString::fromLatin1("Cyclic dependency detected between \"%1\" and \"%2\"")
357 .arg(url().toString(), blob->url().toString()));
358 setError(error);
359 }
360}
361
362/*!
363\fn void QQmlDataBlob::dataReceived(const Data &data)
364
365Invoked when data for the blob is received. Implementors should use this callback
366to determine a blob's dependencies. Within this callback you may call setError()
367or addDependency().
368*/
369
370/*!
371Invoked once data has either been received or a network error occurred, and all
372dependencies are complete.
373
374You can set an error in this method, but you cannot add new dependencies. Implementors
375should use this callback to finalize processing of data.
376
377The default implementation does nothing.
378
379XXX Rename processData() or some such to avoid confusion between done() (processing thread)
380and completed() (main thread)
381*/
382void QQmlDataBlob::done()
383{
384 assertTypeLoaderThread();
385}
386
387#if QT_CONFIG(qml_network)
388/*!
389Invoked if there is a network error while fetching this blob.
390
391The default implementation sets an appropriate QQmlError.
392*/
393void QQmlDataBlob::networkError(QNetworkReply::NetworkError networkError)
394{
395 assertTypeLoaderThread();
396
397 Q_UNUSED(networkError);
398
399 QQmlError error;
400 error.setUrl(m_url);
401
402 const char *errorString = nullptr;
403 switch (networkError) {
404 default:
405 errorString = "Network error";
406 break;
407 case QNetworkReply::ConnectionRefusedError:
408 errorString = "Connection refused";
409 break;
410 case QNetworkReply::RemoteHostClosedError:
411 errorString = "Remote host closed the connection";
412 break;
413 case QNetworkReply::HostNotFoundError:
414 errorString = "Host not found";
415 break;
416 case QNetworkReply::TimeoutError:
417 errorString = "Timeout";
418 break;
419 case QNetworkReply::ProxyConnectionRefusedError:
420 case QNetworkReply::ProxyConnectionClosedError:
421 case QNetworkReply::ProxyNotFoundError:
422 case QNetworkReply::ProxyTimeoutError:
423 case QNetworkReply::ProxyAuthenticationRequiredError:
424 case QNetworkReply::UnknownProxyError:
425 errorString = "Proxy error";
426 break;
427 case QNetworkReply::ContentAccessDenied:
428 errorString = "Access denied";
429 break;
430 case QNetworkReply::ContentNotFoundError:
431 errorString = "File not found";
432 break;
433 case QNetworkReply::AuthenticationRequiredError:
434 errorString = "Authentication required";
435 break;
436 };
437
438 error.setDescription(QLatin1String(errorString));
439
440 setError(error);
441}
442#endif // qml_network
443
444/*!
445Called if \a blob, which was previously waited for, has an error.
446
447The default implementation does nothing.
448*/
449void QQmlDataBlob::dependencyError(const QQmlDataBlob::Ptr &blob)
450{
451 assertTypeLoaderThread();
452 Q_UNUSED(blob);
453}
454
455/*!
456Called if \a blob, which was previously waited for, has completed.
457
458The default implementation does nothing.
459*/
460void QQmlDataBlob::dependencyComplete(const QQmlDataBlob::Ptr &blob)
461{
462 assertTypeLoaderThread();
463 Q_UNUSED(blob);
464}
465
466/*!
467Called when all blobs waited for have completed. This occurs regardless of
468whether they are in error, or complete state.
469
470The default implementation does nothing.
471*/
472void QQmlDataBlob::allDependenciesDone()
473{
474 assertTypeLoaderThread();
475 setStatus(QQmlDataBlob::ResolvingDependencies);
476}
477
478/*!
479Called when the download progress of this blob changes. \a progress goes
480from 0 to 1.
481
482This callback is only invoked if an asynchronous load for this blob is
483made. An asynchronous load is one in which the Asynchronous mode is
484specified explicitly, or one that is implicitly delayed due to a network
485operation.
486
487The default implementation does nothing.
488*/
489void QQmlDataBlob::downloadProgressChanged(qreal progress)
490{
491 Q_UNUSED(progress);
492 assertEngineThread();
493}
494
495/*!
496Invoked on the main thread sometime after done() was called on the load thread.
497
498You cannot modify the blobs state at all in this callback and cannot depend on the
499order or timeliness of these callbacks. Implementors should use this callback to notify
500dependencies on the main thread that the blob is done and not a lot else.
501
502This callback is only invoked if an asynchronous load for this blob is
503made. An asynchronous load is one in which the Asynchronous mode is
504specified explicitly, or one that is implicitly delayed due to a network
505operation.
506
507The default implementation does nothing.
508*/
509void QQmlDataBlob::completed()
510{
511 assertEngineThread();
512}
513
514void QQmlDataBlob::tryDone()
515{
516 assertTypeLoaderThread();
517
518 if (status() != Loading && m_waitingFor.isEmpty() && !m_isDone) {
519 m_isDone = true;
520 addref();
521
522#ifdef DATABLOB_DEBUG
523 qWarning("QQmlDataBlob::done() %s", qPrintable(urlString()));
524#endif
525 done();
526
527 if (status() != Error)
528 setStatus(Complete);
529
530 notifyAllWaitingOnMe();
531
532 // Locking is not required here, as anyone expecting callbacks must
533 // already be protected against the blob being completed (as set above);
534#ifdef DATABLOB_DEBUG
535 qWarning("QQmlDataBlob: Dispatching completed");
536#endif
537 m_typeLoader->thread()->callCompleted(this);
538
539 release();
540 }
541}
542
543void QQmlDataBlob::cancelAllWaitingFor()
544{
545 while (m_waitingFor.size()) {
546
547 // We can assert here since we are sure that m_waitingFor is either empty whenever
548 // we delete a QQmlDataBlob from outside the type loader thread, or the type loader
549 // thread has been suspended before.
550 assertTypeLoaderThreadIfRunning();
551
552 QQmlRefPointer<QQmlDataBlob> blob = m_waitingFor.takeLast();
553
554 Q_ASSERT(blob->m_waitingOnMe.contains(this));
555
556 blob->m_waitingOnMe.removeOne(this);
557 }
558}
559
560void QQmlDataBlob::notifyAllWaitingOnMe()
561{
562 assertTypeLoaderThread();
563
564 while (m_waitingOnMe.size()) {
565 QQmlDataBlob::Ptr blob = m_waitingOnMe.takeLast();
566
567 Q_ASSERT(std::any_of(blob->m_waitingFor.constBegin(), blob->m_waitingFor.constEnd(),
568 [this](const QQmlRefPointer<QQmlDataBlob> &waiting) { return waiting.data() == this; }));
569
570 blob->notifyComplete(this);
571 }
572}
573
574void QQmlDataBlob::notifyComplete(const QQmlDataBlob::Ptr &blob)
575{
576 assertTypeLoaderThread();
577
578 Q_ASSERT(blob->status() == Error || blob->status() == Complete);
579 Q_TRACE_SCOPE(QQmlCompiling, blob->url());
580 QQmlCompilingProfiler prof(typeLoader()->profiler(), blob.data());
581
582 m_inCallback = true;
583
584 QQmlRefPointer<QQmlDataBlob> blobRef;
585 for (int i = 0; i < m_waitingFor.size(); ++i) {
586 if (m_waitingFor.at(i).data() == blob) {
587 blobRef = m_waitingFor.takeAt(i);
588 break;
589 }
590 }
591 Q_ASSERT(blobRef);
592
593 if (blob->status() == Error) {
594 dependencyError(blob);
595 } else if (blob->status() == Complete) {
596 dependencyComplete(blob);
597 }
598
599 if (!isError() && m_waitingFor.isEmpty())
600 allDependenciesDone();
601
602 m_inCallback = false;
603
604 tryDone();
605}
606
607QString QQmlDataBlob::SourceCodeData::readAll(QString *error) const
608{
609 error->clear();
610 if (hasInlineSourceCode)
611 return inlineSourceCode;
612
613 QFile f(fileInfo.absoluteFilePath());
614 if (!f.open(QIODevice::ReadOnly)) {
615 *error = f.errorString();
616 return QString();
617 }
618
619 const qint64 fileSize = fileInfo.size();
620
621 if (uchar *mappedData = f.map(0, fileSize)) {
622 QString source = QString::fromUtf8(reinterpret_cast<const char *>(mappedData), fileSize);
623 f.unmap(mappedData);
624 return source;
625 }
626
627 QByteArray data(fileSize, Qt::Uninitialized);
628 if (f.read(data.data(), data.size()) != data.size()) {
629 *error = f.errorString();
630 return QString();
631 }
632 return QString::fromUtf8(data);
633}
634
635QDateTime QQmlDataBlob::SourceCodeData::sourceTimeStamp() const
636{
637 if (hasInlineSourceCode)
638 return QDateTime();
639
640 return fileInfo.lastModified();
641}
642
643QByteArray QQmlDataBlob::SourceCodeData::checksum() const
644{
645 QCryptographicHash hash(QCryptographicHash::Md5);
646 if (hasInlineSourceCode) {
647 hash.addData(inlineSourceCode.toUtf8());
648 return hash.result();
649 }
650
651 QFile f(fileInfo.absoluteFilePath());
652 if (!f.open(QIODevice::ReadOnly))
653 return QByteArray();
654 if (!hash.addData(&f))
655 return QByteArray();
656 return hash.result();
657}
658
659bool QQmlDataBlob::SourceCodeData::exists() const
660{
661 if (hasInlineSourceCode)
662 return true;
663 return fileInfo.exists();
664}
665
666bool QQmlDataBlob::SourceCodeData::isEmpty() const
667{
668 if (hasInlineSourceCode)
669 return inlineSourceCode.isEmpty();
670 return fileInfo.size() == 0;
671}
672
673bool QQmlDataBlob::setStatus(Status status)
674{
675 switch (status) {
676 case Loading:
677 break;
678 case WaitingForDependencies:
679 Q_ASSERT(!m_waitingFor.isEmpty());
680 break;
681 case Null:
682 case ResolvingDependencies:
683 case Complete:
684 case Error:
685 Q_ASSERT(m_waitingFor.isEmpty());
686 break;
687 }
688
689 return m_data.setStatus(status);
690}
691
692QT_END_NAMESPACE
DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE)