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