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