| 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_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 | void QV4Include::finished() |
| 108 | { |
| 109 | #if QT_CONFIG(qml_network) |
| 110 | QV4::Scope scope(v4); |
| 111 | QV4::ScopedObject resultObj(scope, m_resultObject.value()); |
| 112 | QV4::ScopedString status(scope, v4->newString(QStringLiteral("status" ))); |
| 113 | if (m_reply->error() == QNetworkReply::NoError) { |
| 114 | QByteArray data = m_reply->readAll(); |
| 115 | |
| 116 | QString code = QString::fromUtf8(ba: data); |
| 117 | |
| 118 | QV4::Scoped<QV4::QmlContext> qml(scope, m_qmlContext.value()); |
| 119 | QV4::Script script(v4, qml, /*parse as QML binding*/false, code, m_url.toString()); |
| 120 | |
| 121 | script.parse(); |
| 122 | if (!scope.hasException()) |
| 123 | script.run(); |
| 124 | if (scope.hasException()) { |
| 125 | QV4::ScopedValue ex(scope, scope.engine->catchException()); |
| 126 | resultObj->put(name: status, v: QV4::ScopedValue(scope, QV4::Value::fromInt32(i: Exception))); |
| 127 | QV4::ScopedString exception(scope, v4->newString(QStringLiteral("exception" ))); |
| 128 | resultObj->put(name: exception, v: ex); |
| 129 | } else { |
| 130 | resultObj->put(name: status, v: QV4::ScopedValue(scope, QV4::Value::fromInt32(i: Ok))); |
| 131 | } |
| 132 | } else { |
| 133 | resultObj->put(name: status, v: QV4::ScopedValue(scope, QV4::Value::fromInt32(i: NetworkError))); |
| 134 | } |
| 135 | #else |
| 136 | QV4::Scope scope(v4); |
| 137 | QV4::ScopedObject resultObj(scope, m_resultObject.value()); |
| 138 | QV4::ScopedString status(scope, v4->newString(QStringLiteral("status" ))); |
| 139 | resultObj->put(status, QV4::ScopedValue(scope, QV4::Value::fromInt32(NetworkError))); |
| 140 | #endif // qml_network |
| 141 | |
| 142 | QV4::ScopedValue cb(scope, m_callbackFunction.value()); |
| 143 | callback(callback: cb, status: resultObj); |
| 144 | |
| 145 | disconnect(); |
| 146 | deleteLater(); |
| 147 | } |
| 148 | |
| 149 | /* |
| 150 | Documented in qv4engine.cpp |
| 151 | */ |
| 152 | QJSValue QV4Include::method_include(QV4::ExecutionEngine *engine, const QUrl &url, |
| 153 | const QJSValue &callbackFunction) |
| 154 | { |
| 155 | QQmlRefPointer<QQmlContextData> context = engine->callingQmlContext(); |
| 156 | |
| 157 | if ((!context || !context->isJSContext()) && engine->qmlEngine()) { |
| 158 | return QJSValuePrivate::fromReturnedValue( |
| 159 | d: engine->throwError( |
| 160 | message: QString::fromUtf8( |
| 161 | utf8: "Qt.include(): Can only be called from JavaScript files" ))); |
| 162 | } |
| 163 | |
| 164 | |
| 165 | QV4::Scope scope(engine); |
| 166 | QV4::ScopedValue scopedCallbackFunction(scope, QV4::Value::undefinedValue()); |
| 167 | if (auto function = QJSValuePrivate::asManagedType<QV4::FunctionObject>(jsval: &callbackFunction)) |
| 168 | scopedCallbackFunction = *function; |
| 169 | |
| 170 | const QQmlEngine *qmlEngine = engine->qmlEngine(); |
| 171 | const QUrl intercepted = qmlEngine |
| 172 | ? qmlEngine->interceptUrl(url, type: QQmlAbstractUrlInterceptor::JavaScriptFile) |
| 173 | : url; |
| 174 | QString localFile = QQmlFile::urlToLocalFileOrQrc(intercepted); |
| 175 | |
| 176 | QV4::ScopedValue result(scope); |
| 177 | QV4::Scoped<QV4::QmlContext> qmlcontext(scope, scope.engine->qmlContext()); |
| 178 | |
| 179 | if (localFile.isEmpty()) { |
| 180 | #if QT_CONFIG(qml_network) |
| 181 | QV4Include *i = new QV4Include(url, engine, qmlcontext, scopedCallbackFunction); |
| 182 | result = i->result(); |
| 183 | #else |
| 184 | result = resultValue(scope.engine, NetworkError); |
| 185 | callback(scopedCallbackFunction, result); |
| 186 | #endif |
| 187 | } else { |
| 188 | QScopedPointer<QV4::Script> script; |
| 189 | QString error; |
| 190 | script.reset(other: QV4::Script::createFromFileOrCache(engine: scope.engine, qmlContext: qmlcontext, fileName: localFile, originalUrl: url, error: &error)); |
| 191 | |
| 192 | if (!script.isNull()) { |
| 193 | script->parse(); |
| 194 | if (!scope.hasException()) |
| 195 | script->run(); |
| 196 | if (scope.hasException()) { |
| 197 | QV4::ScopedValue ex(scope, scope.engine->catchException()); |
| 198 | result = resultValue(v4: scope.engine, status: Exception); |
| 199 | QV4::ScopedString exception(scope, scope.engine->newString(QStringLiteral("exception" ))); |
| 200 | result->as<QV4::Object>()->put(name: exception, v: ex); |
| 201 | } else { |
| 202 | result = resultValue(v4: scope.engine, status: Ok); |
| 203 | } |
| 204 | } else { |
| 205 | result = resultValue(v4: scope.engine, status: NetworkError, statusText: error); |
| 206 | } |
| 207 | |
| 208 | callback(callback: scopedCallbackFunction, status: result); |
| 209 | } |
| 210 | |
| 211 | return QJSValuePrivate::fromReturnedValue(d: result->asReturnedValue()); |
| 212 | } |
| 213 | |
| 214 | QT_END_NAMESPACE |
| 215 | |
| 216 | #include "moc_qv4include_p.cpp" |
| 217 | |