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