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

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