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

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