1// Copyright (C) 2016 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 "qv4include_p.h"
5#include "qv4scopedvalue_p.h"
6#include "qv4jscall_p.h"
7
8#include <QtQml/qjsengine.h>
9#if QT_CONFIG(qml_network)
10#include <QtNetwork/qnetworkrequest.h>
11#include <QtNetwork/qnetworkreply.h>
12#endif
13#include <QtCore/qfile.h>
14#include <QtQml/qqmlfile.h>
15
16#include <private/qqmlengine_p.h>
17#include <private/qv4engine_p.h>
18#include <private/qv4functionobject_p.h>
19#include <private/qv4script_p.h>
20#include <private/qv4context_p.h>
21
22QT_BEGIN_NAMESPACE
23
24QV4Include::QV4Include(const QUrl &url, QV4::ExecutionEngine *engine,
25 QV4::QmlContext *qmlContext, const QV4::Value &callback)
26 : QObject(engine->jsEngine())
27 , v4(engine), m_url(url)
28#if QT_CONFIG(qml_network)
29 , m_redirectCount(0), m_network(nullptr) , m_reply(nullptr)
30#endif
31{
32 if (qmlContext)
33 m_qmlContext.set(engine: v4, value: *qmlContext);
34 if (callback.as<QV4::FunctionObject>())
35 m_callbackFunction.set(engine: v4, value: callback);
36
37 m_resultObject.set(engine: v4, value: resultValue(v4));
38
39#if QT_CONFIG(qml_network)
40 if (QQmlEngine *qmlEngine = v4->qmlEngine()) {
41 m_network = qmlEngine->networkAccessManager();
42
43 QNetworkRequest request;
44 request.setUrl(url);
45
46 m_reply = m_network->get(request);
47 QObject::connect(sender: m_reply, SIGNAL(finished()), receiver: this, SLOT(finished()));
48 } else {
49 finished();
50 }
51#else
52 finished();
53#endif
54}
55
56QV4Include::~QV4Include()
57{
58#if QT_CONFIG(qml_network)
59 delete m_reply;
60 m_reply = nullptr;
61#endif
62}
63
64QV4::ReturnedValue QV4Include::resultValue(QV4::ExecutionEngine *v4, Status status,
65 const QString &statusText)
66{
67 QV4::Scope scope(v4);
68
69 // XXX It seems inefficient to create this object from scratch each time.
70 QV4::ScopedObject o(scope, v4->newObject());
71 QV4::ScopedString s(scope);
72 QV4::ScopedValue v(scope);
73 o->put(name: (s = v4->newString(QStringLiteral("OK"))), v: (v = QV4::Value::fromInt32(i: Ok)));
74 o->put(name: (s = v4->newString(QStringLiteral("LOADING"))), v: (v = QV4::Value::fromInt32(i: Loading)));
75 o->put(name: (s = v4->newString(QStringLiteral("NETWORK_ERROR"))), v: (v = QV4::Value::fromInt32(i: NetworkError)));
76 o->put(name: (s = v4->newString(QStringLiteral("EXCEPTION"))), v: (v = QV4::Value::fromInt32(i: Exception)));
77 o->put(name: (s = v4->newString(QStringLiteral("status"))), v: (v = QV4::Value::fromInt32(i: status)));
78 if (!statusText.isEmpty())
79 o->put(name: (s = v4->newString(QStringLiteral("statusText"))), v: (v = v4->newString(s: statusText)));
80
81 return o.asReturnedValue();
82}
83
84void QV4Include::callback(const QV4::Value &callback, const QV4::Value &status)
85{
86 if (!callback.isObject())
87 return;
88 QV4::ExecutionEngine *v4 = callback.as<QV4::Object>()->engine();
89 QV4::Scope scope(v4);
90 QV4::ScopedFunctionObject f(scope, callback);
91 if (!f)
92 return;
93
94 QV4::JSCallArguments jsCallData(scope, 1);
95 *jsCallData.thisObject = v4->globalObject->asReturnedValue();
96 jsCallData.args[0] = status;
97 f->call(data: jsCallData);
98 if (scope.hasException())
99 scope.engine->catchException();
100}
101
102QV4::ReturnedValue QV4Include::result()
103{
104 return m_resultObject.value();
105}
106
107#define INCLUDE_MAXIMUM_REDIRECT_RECURSION 15
108void QV4Include::finished()
109{
110#if QT_CONFIG(qml_network)
111 m_redirectCount++;
112
113 if (m_redirectCount < INCLUDE_MAXIMUM_REDIRECT_RECURSION) {
114 QVariant redirect = m_reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute);
115 if (redirect.isValid()) {
116 m_url = m_url.resolved(relative: redirect.toUrl());
117 delete m_reply;
118
119 QNetworkRequest request;
120 request.setUrl(m_url);
121
122 m_reply = m_network->get(request);
123 QObject::connect(sender: m_reply, SIGNAL(finished()), receiver: this, SLOT(finished()));
124 return;
125 }
126 }
127
128 QV4::Scope scope(v4);
129 QV4::ScopedObject resultObj(scope, m_resultObject.value());
130 QV4::ScopedString status(scope, v4->newString(QStringLiteral("status")));
131 if (m_reply->error() == QNetworkReply::NoError) {
132 QByteArray data = m_reply->readAll();
133
134 QString code = QString::fromUtf8(ba: data);
135
136 QV4::Scoped<QV4::QmlContext> qml(scope, m_qmlContext.value());
137 QV4::Script script(v4, qml, /*parse as QML binding*/false, code, m_url.toString());
138
139 script.parse();
140 if (!scope.hasException())
141 script.run();
142 if (scope.hasException()) {
143 QV4::ScopedValue ex(scope, scope.engine->catchException());
144 resultObj->put(name: status, v: QV4::ScopedValue(scope, QV4::Value::fromInt32(i: Exception)));
145 QV4::ScopedString exception(scope, v4->newString(QStringLiteral("exception")));
146 resultObj->put(name: exception, v: ex);
147 } else {
148 resultObj->put(name: status, v: QV4::ScopedValue(scope, QV4::Value::fromInt32(i: Ok)));
149 }
150 } else {
151 resultObj->put(name: status, v: QV4::ScopedValue(scope, QV4::Value::fromInt32(i: NetworkError)));
152 }
153#else
154 QV4::Scope scope(v4);
155 QV4::ScopedObject resultObj(scope, m_resultObject.value());
156 QV4::ScopedString status(scope, v4->newString(QStringLiteral("status")));
157 resultObj->put(status, QV4::ScopedValue(scope, QV4::Value::fromInt32(NetworkError)));
158#endif // qml_network
159
160 QV4::ScopedValue cb(scope, m_callbackFunction.value());
161 callback(callback: cb, status: resultObj);
162
163 disconnect();
164 deleteLater();
165}
166
167/*
168 Documented in qv4engine.cpp
169*/
170QJSValue QV4Include::method_include(QV4::ExecutionEngine *engine, const QUrl &url,
171 const QJSValue &callbackFunction)
172{
173 QQmlRefPointer<QQmlContextData> context = engine->callingQmlContext();
174
175 if ((!context || !context->isJSContext()) && engine->qmlEngine()) {
176 return QJSValuePrivate::fromReturnedValue(
177 d: engine->throwError(
178 message: QString::fromUtf8(
179 utf8: "Qt.include(): Can only be called from JavaScript files")));
180 }
181
182
183 QV4::Scope scope(engine);
184 QV4::ScopedValue scopedCallbackFunction(scope, QV4::Value::undefinedValue());
185 if (auto function = QJSValuePrivate::asManagedType<QV4::FunctionObject>(jsval: &callbackFunction))
186 scopedCallbackFunction = *function;
187
188 const QQmlEngine *qmlEngine = engine->qmlEngine();
189 const QUrl intercepted = qmlEngine
190 ? qmlEngine->interceptUrl(url, type: QQmlAbstractUrlInterceptor::JavaScriptFile)
191 : url;
192 QString localFile = QQmlFile::urlToLocalFileOrQrc(intercepted);
193
194 QV4::ScopedValue result(scope);
195 QV4::Scoped<QV4::QmlContext> qmlcontext(scope, scope.engine->qmlContext());
196
197 if (localFile.isEmpty()) {
198#if QT_CONFIG(qml_network)
199 QV4Include *i = new QV4Include(url, engine, qmlcontext, scopedCallbackFunction);
200 result = i->result();
201#else
202 result = resultValue(scope.engine, NetworkError);
203 callback(scopedCallbackFunction, result);
204#endif
205 } else {
206 QScopedPointer<QV4::Script> script;
207 QString error;
208 script.reset(other: QV4::Script::createFromFileOrCache(engine: scope.engine, qmlContext: qmlcontext, fileName: localFile, originalUrl: url, error: &error));
209
210 if (!script.isNull()) {
211 script->parse();
212 if (!scope.hasException())
213 script->run();
214 if (scope.hasException()) {
215 QV4::ScopedValue ex(scope, scope.engine->catchException());
216 result = resultValue(v4: scope.engine, status: Exception);
217 QV4::ScopedString exception(scope, scope.engine->newString(QStringLiteral("exception")));
218 result->as<QV4::Object>()->put(name: exception, v: ex);
219 } else {
220 result = resultValue(v4: scope.engine, status: Ok);
221 }
222 } else {
223 result = resultValue(v4: scope.engine, status: NetworkError, statusText: error);
224 }
225
226 callback(callback: scopedCallbackFunction, status: result);
227 }
228
229 return QJSValuePrivate::fromReturnedValue(d: result->asReturnedValue());
230}
231
232QT_END_NAMESPACE
233
234#include "moc_qv4include_p.cpp"
235

source code of qtdeclarative/src/qml/jsruntime/qv4include.cpp