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 
57 DEFINE_BOOL_CONFIG_OPTION(dumpErrors, QML_DUMP_ERRORS);
58 
59 QT_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 
66 QQmlDataBlob's are loaded by a QQmlTypeLoader.  The user creates the QQmlDataBlob
67 and then calls QQmlTypeLoader::load() or QQmlTypeLoader::loadWithStaticData() to load it.
68 The QQmlTypeLoader invokes callbacks on the QQmlDataBlob as data becomes available.
69 */
70 
71 /*!
72 \enum QQmlDataBlob::Status
73 
74 This 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 
91 This 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 /*!
101 Create a new QQmlDataBlob for \a url and of the provided \a type.
102 */
QQmlDataBlob(const QUrl & url,Type type,QQmlTypeLoader * manager)103 QQmlDataBlob::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(m_url,
110                     (QQmlAbstractUrlInterceptor::DataType)m_type);
111 }
112 
113 /*!  \internal */
~QQmlDataBlob()114 QQmlDataBlob::~QQmlDataBlob()
115 {
116     Q_ASSERT(m_waitingOnMe.isEmpty());
117 
118     cancelAllWaitingFor();
119 }
120 
121 /*!
122   Must be called before loading can occur.
123 */
startLoading()124 void QQmlDataBlob::startLoading()
125 {
126     Q_ASSERT(status() == QQmlDataBlob::Null);
127     m_data.setStatus(QQmlDataBlob::Loading);
128 }
129 
130 /*!
131 Returns the type provided to the constructor.
132 */
type() const133 QQmlDataBlob::Type QQmlDataBlob::type() const
134 {
135     return m_type;
136 }
137 
138 /*!
139 Returns the blob's status.
140 */
status() const141 QQmlDataBlob::Status QQmlDataBlob::status() const
142 {
143     return m_data.status();
144 }
145 
146 /*!
147 Returns true if the status is Null.
148 */
isNull() const149 bool QQmlDataBlob::isNull() const
150 {
151     return status() == Null;
152 }
153 
154 /*!
155 Returns true if the status is Loading.
156 */
isLoading() const157 bool QQmlDataBlob::isLoading() const
158 {
159     return status() == Loading;
160 }
161 
162 /*!
163 Returns true if the status is WaitingForDependencies.
164 */
isWaiting() const165 bool QQmlDataBlob::isWaiting() const
166 {
167     return status() == WaitingForDependencies ||
168             status() == ResolvingDependencies;
169 }
170 
171 /*!
172 Returns true if the status is Complete.
173 */
isComplete() const174 bool QQmlDataBlob::isComplete() const
175 {
176     return status() == Complete;
177 }
178 
179 /*!
180 Returns true if the status is Error.
181 */
isError() const182 bool QQmlDataBlob::isError() const
183 {
184     return status() == Error;
185 }
186 
187 /*!
188 Returns true if the status is Complete or Error.
189 */
isCompleteOrError() const190 bool QQmlDataBlob::isCompleteOrError() const
191 {
192     Status s = status();
193     return s == Error || s == Complete;
194 }
195 
196 /*!
197 Returns the data download progress from 0 to 1.
198 */
progress() const199 qreal 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 /*!
207 Returns the physical url of the data.  Initially this is the same as
208 finalUrl(), but if a URL interceptor is set, it will work on this URL
209 and leave finalUrl() alone.
210 
211 \sa finalUrl()
212 */
url() const213 QUrl QQmlDataBlob::url() const
214 {
215     return m_url;
216 }
217 
urlString() const218 QString QQmlDataBlob::urlString() const
219 {
220     if (m_urlString.isEmpty())
221         m_urlString = m_url.toString();
222 
223     return m_urlString;
224 }
225 
226 /*!
227 Returns the logical URL to be used for resolving further URLs referred to in
228 the code.
229 
230 This is the blob url passed to the constructor. If a URL interceptor rewrites
231 the URL, this one stays the same. If a network redirect happens while fetching
232 the data, this url is updated to reflect the new location. Therefore, if both
233 an interception and a redirection happen, the final url will indirectly
234 incorporate the result of the interception, potentially breaking further
235 lookups.
236 
237 \sa url()
238 */
finalUrl() const239 QUrl QQmlDataBlob::finalUrl() const
240 {
241     return m_finalUrl;
242 }
243 
244 /*!
245 Returns the finalUrl() as a string.
246 */
finalUrlString() const247 QString QQmlDataBlob::finalUrlString() const
248 {
249     if (m_finalUrlString.isEmpty())
250         m_finalUrlString = m_finalUrl.toString();
251 
252     return m_finalUrlString;
253 }
254 
255 /*!
256 Return the errors on this blob.
257 
258 May only be called from the load thread, or after the blob isCompleteOrError().
259 */
errors() const260 QList<QQmlError> QQmlDataBlob::errors() const
261 {
262     Q_ASSERT(isCompleteOrError() || (m_typeLoader && m_typeLoader->m_thread->isThisThread()));
263     return m_errors;
264 }
265 
266 /*!
267 Mark this blob as having \a errors.
268 
269 All outstanding dependencies will be cancelled.  Requests to add new dependencies
270 will be ignored.  Entry into the Error state is irreversable.
271 
272 The setError() method may only be called from within a QQmlDataBlob callback.
273 */
setError(const QQmlError & errors)274 void 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 */
setError(const QList<QQmlError> & errors)286 void 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 
setError(const QQmlJS::DiagnosticMessage & error)307 void QQmlDataBlob::setError(const QQmlJS::DiagnosticMessage &error)
308 {
309     QQmlError e;
310     e.setColumn(qmlConvertSourceCoordinate<quint32, int>(error.loc.startColumn));
311     e.setLine(qmlConvertSourceCoordinate<quint32, int>(error.loc.startLine));
312     e.setDescription(error.message);
313     e.setUrl(url());
314     setError(e);
315 }
316 
setError(const QVector<QQmlError> & errors)317 void QQmlDataBlob::setError(const QVector<QQmlError> &errors)
318 {
319     QList<QQmlError> finalErrors;
320     finalErrors.reserve(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 
setError(const QString & description)329 void QQmlDataBlob::setError(const QString &description)
330 {
331     QQmlError e;
332     e.setDescription(description);
333     e.setUrl(url());
334     setError(e);
335 }
336 
337 /*!
338 Wait for \a blob to become complete or to error.  If \a blob is already
339 complete or in error, or this blob is already complete, this has no effect.
340 
341 The setError() method may only be called from within a QQmlDataBlob callback.
342 */
addDependency(QQmlDataBlob * blob)343 void 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(m_waitingFor))
355         if (existingDep.data() == blob)
356             return;
357 
358     m_data.setStatus(WaitingForDependencies);
359 
360     m_waitingFor.append(blob);
361     blob->m_waitingOnMe.append(this);
362 
363     // Check circular dependency
364     if (m_waitingOnMe.indexOf(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 
373 Invoked when data for the blob is received.  Implementors should use this callback
374 to determine a blob's dependencies.  Within this callback you may call setError()
375 or addDependency().
376 */
377 
378 /*!
379 Invoked once data has either been received or a network error occurred, and all
380 dependencies are complete.
381 
382 You can set an error in this method, but you cannot add new dependencies.  Implementors
383 should use this callback to finalize processing of data.
384 
385 The default implementation does nothing.
386 
387 XXX Rename processData() or some such to avoid confusion between done() (processing thread)
388 and completed() (main thread)
389 */
done()390 void QQmlDataBlob::done()
391 {
392 }
393 
394 #if QT_CONFIG(qml_network)
395 /*!
396 Invoked if there is a network error while fetching this blob.
397 
398 The default implementation sets an appropriate QQmlError.
399 */
networkError(QNetworkReply::NetworkError networkError)400 void 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 /*!
450 Called if \a blob, which was previously waited for, has an error.
451 
452 The default implementation does nothing.
453 */
dependencyError(QQmlDataBlob * blob)454 void QQmlDataBlob::dependencyError(QQmlDataBlob *blob)
455 {
456     Q_UNUSED(blob);
457 }
458 
459 /*!
460 Called if \a blob, which was previously waited for, has completed.
461 
462 The default implementation does nothing.
463 */
dependencyComplete(QQmlDataBlob * blob)464 void QQmlDataBlob::dependencyComplete(QQmlDataBlob *blob)
465 {
466     Q_UNUSED(blob);
467 }
468 
469 /*!
470 Called when all blobs waited for have completed.  This occurs regardless of
471 whether they are in error, or complete state.
472 
473 The default implementation does nothing.
474 */
allDependenciesDone()475 void QQmlDataBlob::allDependenciesDone()
476 {
477     m_data.setStatus(QQmlDataBlob::ResolvingDependencies);
478 }
479 
480 /*!
481 Called when the download progress of this blob changes.  \a progress goes
482 from 0 to 1.
483 
484 This callback is only invoked if an asynchronous load for this blob is
485 made.  An asynchronous load is one in which the Asynchronous mode is
486 specified explicitly, or one that is implicitly delayed due to a network
487 operation.
488 
489 The default implementation does nothing.
490 */
downloadProgressChanged(qreal progress)491 void QQmlDataBlob::downloadProgressChanged(qreal progress)
492 {
493     Q_UNUSED(progress);
494 }
495 
496 /*!
497 Invoked on the main thread sometime after done() was called on the load thread.
498 
499 You cannot modify the blobs state at all in this callback and cannot depend on the
500 order or timeliness of these callbacks.  Implementors should use this callback to notify
501 dependencies on the main thread that the blob is done and not a lot else.
502 
503 This callback is only invoked if an asynchronous load for this blob is
504 made.  An asynchronous load is one in which the Asynchronous mode is
505 specified explicitly, or one that is implicitly delayed due to a network
506 operation.
507 
508 The default implementation does nothing.
509 */
completed()510 void QQmlDataBlob::completed()
511 {
512 }
513 
514 
tryDone()515 void 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(this);
537 
538         release();
539     }
540 }
541 
cancelAllWaitingFor()542 void 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(this);
550     }
551 }
552 
notifyAllWaitingOnMe()553 void 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 
notifyComplete(QQmlDataBlob * blob)565 void 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 
readAll(QString * error) const596 QString 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(QIODevice::ReadOnly)) {
604         *error = f.errorString();
605         return QString();
606     }
607 
608     const qint64 fileSize = fileInfo.size();
609 
610     if (uchar *mappedData = f.map(0, fileSize)) {
611         QString source = QString::fromUtf8(reinterpret_cast<const char *>(mappedData), fileSize);
612         f.unmap(mappedData);
613         return source;
614     }
615 
616     QByteArray data(fileSize, Qt::Uninitialized);
617     if (f.read(data.data(), data.length()) != data.length()) {
618         *error = f.errorString();
619         return QString();
620     }
621     return QString::fromUtf8(data);
622 }
623 
sourceTimeStamp() const624 QDateTime QQmlDataBlob::SourceCodeData::sourceTimeStamp() const
625 {
626     if (hasInlineSourceCode)
627         return QDateTime();
628 
629     return fileInfo.lastModified();
630 }
631 
exists() const632 bool QQmlDataBlob::SourceCodeData::exists() const
633 {
634     if (hasInlineSourceCode)
635         return true;
636     return fileInfo.exists();
637 }
638 
isEmpty() const639 bool QQmlDataBlob::SourceCodeData::isEmpty() const
640 {
641     if (hasInlineSourceCode)
642         return inlineSourceCode.isEmpty();
643     return fileInfo.size() == 0;
644 }
645 
646 QT_END_NAMESPACE
647