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 | |
22 | QT_BEGIN_NAMESPACE |
23 | |
24 | QV4Include::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 | |
56 | QV4Include::~QV4Include() |
57 | { |
58 | #if QT_CONFIG(qml_network) |
59 | delete m_reply; |
60 | m_reply = nullptr; |
61 | #endif |
62 | } |
63 | |
64 | QV4::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 | |
84 | void 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 | |
102 | QV4::ReturnedValue QV4Include::result() |
103 | { |
104 | return m_resultObject.value(); |
105 | } |
106 | |
107 | #define INCLUDE_MAXIMUM_REDIRECT_RECURSION 15 |
108 | void 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 | */ |
170 | QJSValue 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 | |
232 | QT_END_NAMESPACE |
233 | |
234 | #include "moc_qv4include_p.cpp" |
235 | |