aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/types/qquickworkerscript.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/types/qquickworkerscript.cpp')
-rw-r--r--src/qml/types/qquickworkerscript.cpp326
1 files changed, 120 insertions, 206 deletions
diff --git a/src/qml/types/qquickworkerscript.cpp b/src/qml/types/qquickworkerscript.cpp
index 6159355afc..edb112276c 100644
--- a/src/qml/types/qquickworkerscript.cpp
+++ b/src/qml/types/qquickworkerscript.cpp
@@ -37,9 +37,12 @@
**
****************************************************************************/
+#include "qtqmlglobal_p.h"
#include "qquickworkerscript_p.h"
+#if QT_CONFIG(qml_list_model)
#include "qqmllistmodel_p.h"
#include "qqmllistmodelworkeragent_p.h"
+#endif
#include <private/qqmlengine_p.h>
#include <private/qqmlexpression_p.h>
@@ -65,6 +68,7 @@
#include <private/qv4functionobject_p.h>
#include <private/qv4script_p.h>
#include <private/qv4scopedvalue_p.h>
+#include <private/qv4jscall_p.h>
QT_BEGIN_NAMESPACE
@@ -135,49 +139,26 @@ public:
QQuickWorkerScriptEnginePrivate(QQmlEngine *eng);
- class WorkerEngine : public QV8Engine
- {
- public:
- WorkerEngine(QQuickWorkerScriptEnginePrivate *parent);
- ~WorkerEngine();
+ QQmlEngine *qmlengine;
+
+ QMutex m_lock;
+ QWaitCondition m_wait;
- void init();
+ struct WorkerScript : public QV8Engine {
+ WorkerScript(int id, QQuickWorkerScriptEnginePrivate *parent);
+ ~WorkerScript() override;
#if QT_CONFIG(qml_network)
QNetworkAccessManager *networkAccessManager() override;
#endif
- QQuickWorkerScriptEnginePrivate *p;
-
- QV4::ReturnedValue sendFunction(int id);
-
- QV4::PersistentValue onmessage;
- private:
- QV4::PersistentValue createsend;
+ QQuickWorkerScriptEnginePrivate *p = nullptr;
+ QUrl source;
+ QQuickWorkerScript *owner = nullptr;
#if QT_CONFIG(qml_network)
- QNetworkAccessManager *accessManager;
+ QScopedPointer<QNetworkAccessManager> accessManager;
#endif
- };
-
- WorkerEngine *workerEngine;
- static QQuickWorkerScriptEnginePrivate *get(QV8Engine *e) {
- return static_cast<WorkerEngine *>(e)->p;
- }
-
- QQmlEngine *qmlengine;
-
- QMutex m_lock;
- QWaitCondition m_wait;
-
- struct WorkerScript {
- WorkerScript();
- ~WorkerScript();
-
- int id;
- QUrl source;
- bool initialized;
- QQuickWorkerScript *owner;
- QV4::PersistentValue qmlContext;
+ int id = -1;
};
QHash<int, WorkerScript *> workers;
@@ -185,7 +166,7 @@ public:
int m_nextId;
- static void method_sendMessage(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData);
+ static QV4::ReturnedValue method_sendMessage(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
signals:
void stopThread();
@@ -199,128 +180,25 @@ private:
void reportScriptException(WorkerScript *, const QQmlError &error);
};
-QQuickWorkerScriptEnginePrivate::WorkerEngine::WorkerEngine(QQuickWorkerScriptEnginePrivate *parent)
-: QV8Engine(0), p(parent)
-#if QT_CONFIG(qml_network)
-, accessManager(0)
-#endif
-{
- m_v4Engine->v8Engine = this;
-}
-
-QQuickWorkerScriptEnginePrivate::WorkerEngine::~WorkerEngine()
-{
-#if QT_CONFIG(qml_network)
- delete accessManager;
-#endif
-}
-
-void QQuickWorkerScriptEnginePrivate::WorkerEngine::init()
-{
- initQmlGlobalObject();
-#define CALL_ONMESSAGE_SCRIPT \
- "(function(object, message) { "\
- "var isfunction = false; "\
- "try { "\
- "isfunction = object.WorkerScript.onMessage instanceof Function; "\
- "} catch (e) {}" \
- "if (isfunction) "\
- "object.WorkerScript.onMessage(message); "\
- "})"
-
-#define SEND_MESSAGE_CREATE_SCRIPT \
- "(function(method, engine) { "\
- "return (function(id) { "\
- "return (function(message) { "\
- "if (arguments.length) method(engine, id, message); "\
- "}); "\
- "}); "\
- "})"
-
- QV4::Scope scope(m_v4Engine);
- QV4::ExecutionContext *globalContext = scope.engine->rootContext();
- onmessage.set(scope.engine, QV4::Script(globalContext, QString::fromUtf8(CALL_ONMESSAGE_SCRIPT)).run()); // do not use QStringLiteral here, MSVC2012 cannot apply this cleanly to the macro
- Q_ASSERT(!scope.engine->hasException);
- QV4::Script createsendscript(globalContext, QString::fromUtf8(SEND_MESSAGE_CREATE_SCRIPT)); // do not use QStringLiteral here, MSVC2012 cannot apply this cleanly to the macro
- QV4::ScopedFunctionObject createsendconstructor(scope, createsendscript.run());
- Q_ASSERT(!scope.engine->hasException);
- QV4::ScopedString name(scope, m_v4Engine->newString(QStringLiteral("sendMessage")));
- QV4::ScopedValue function(scope, QV4::BuiltinFunction::create(globalContext, name,
- QQuickWorkerScriptEnginePrivate::method_sendMessage));
- QV4::ScopedCallData callData(scope, 1);
- callData->args[0] = function;
- callData->thisObject = global();
- createsendconstructor->call(scope, callData);
- createsend.set(scope.engine, scope.result.asReturnedValue());
-}
-
-// Requires handle and context scope
-QV4::ReturnedValue QQuickWorkerScriptEnginePrivate::WorkerEngine::sendFunction(int id)
-{
- QV4::ExecutionEngine *v4 = createsend.engine();
- if (!v4)
- return QV4::Encode::undefined();
-
- QV4::Scope scope(v4);
- QV4::ScopedFunctionObject f(scope, createsend.value());
-
- QV4::ScopedCallData callData(scope, 1);
- callData->args[0] = QV4::Primitive::fromInt32(id);
- callData->thisObject = global();
- f->call(scope, callData);
- if (scope.hasException())
- scope.result = scope.engine->catchException();
- return scope.result.asReturnedValue();
-}
-
-#if QT_CONFIG(qml_network)
-QNetworkAccessManager *QQuickWorkerScriptEnginePrivate::WorkerEngine::networkAccessManager()
-{
- if (!accessManager) {
- if (p->qmlengine && p->qmlengine->networkAccessManagerFactory()) {
- accessManager = p->qmlengine->networkAccessManagerFactory()->create(p);
- } else {
- accessManager = new QNetworkAccessManager(p);
- }
- }
- return accessManager;
-}
-#endif
-
QQuickWorkerScriptEnginePrivate::QQuickWorkerScriptEnginePrivate(QQmlEngine *engine)
-: workerEngine(0), qmlengine(engine), m_nextId(0)
+: qmlengine(engine), m_nextId(0)
{
}
-void QQuickWorkerScriptEnginePrivate::method_sendMessage(const QV4::BuiltinFunction *, QV4::Scope &scope, QV4::CallData *callData)
+QV4::ReturnedValue QQuickWorkerScriptEnginePrivate::method_sendMessage(const QV4::FunctionObject *b,
+ const QV4::Value *, const QV4::Value *argv, int argc)
{
- WorkerEngine *engine = (WorkerEngine*)scope.engine->v8Engine;
-
- int id = callData->argc > 1 ? callData->args[1].toInt32() : 0;
+ QV4::Scope scope(b);
+ WorkerScript *script = static_cast<WorkerScript *>(scope.engine->v8Engine);
- QV4::ScopedValue v(scope, callData->argument(2));
+ QV4::ScopedValue v(scope, argc > 0 ? argv[0] : QV4::Value::undefinedValue());
QByteArray data = QV4::Serialize::serialize(v, scope.engine);
- QMutexLocker locker(&engine->p->m_lock);
- WorkerScript *script = engine->p->workers.value(id);
+ QMutexLocker locker(&script->p->m_lock);
if (script && script->owner)
QCoreApplication::postEvent(script->owner, new WorkerDataEvent(0, data));
- scope.result = QV4::Encode::undefined();
-}
-
-QV4::ReturnedValue QQuickWorkerScriptEnginePrivate::getWorker(WorkerScript *script)
-{
- if (!script->initialized) {
- script->initialized = true;
-
- QV4::ExecutionEngine *v4 = QV8Engine::getV4(workerEngine);
- QV4::Scope scope(v4);
- QV4::ScopedValue v(scope, workerEngine->sendFunction(script->id));
- script->qmlContext.set(v4, QV4::QmlContext::createWorkerContext(v4->rootContext(), script->source, v));
- }
-
- return script->qmlContext.value();
+ return QV4::Encode::undefined();
}
bool QQuickWorkerScriptEnginePrivate::event(QEvent *event)
@@ -337,6 +215,7 @@ bool QQuickWorkerScriptEnginePrivate::event(QEvent *event)
emit stopThread();
return true;
} else if (event->type() == (QEvent::Type)WorkerRemoveEvent::WorkerRemove) {
+ QMutexLocker locker(&m_lock);
WorkerRemoveEvent *workerEvent = static_cast<WorkerRemoveEvent *>(event);
QHash<int, WorkerScript *>::iterator itr = workers.find(workerEvent->workerId());
if (itr != workers.end()) {
@@ -355,19 +234,23 @@ void QQuickWorkerScriptEnginePrivate::processMessage(int id, const QByteArray &d
if (!script)
return;
- QV4::ExecutionEngine *v4 = QV8Engine::getV4(workerEngine);
+ QV4::ExecutionEngine *v4 = QV8Engine::getV4(script);
QV4::Scope scope(v4);
- QV4::ScopedFunctionObject f(scope, workerEngine->onmessage.value());
+ QV4::ScopedString v(scope);
+ QV4::ScopedObject worker(scope, v4->globalObject->get((v = v4->newString(QStringLiteral("WorkerScript")))));
+ QV4::ScopedFunctionObject onmessage(scope);
+ if (worker)
+ onmessage = worker->get((v = v4->newString(QStringLiteral("onMessage"))));
+
+ if (!onmessage)
+ return;
QV4::ScopedValue value(scope, QV4::Serialize::deserialize(data, v4));
- QV4::Scoped<QV4::QmlContext> qmlContext(scope, script->qmlContext.value());
- Q_ASSERT(!!qmlContext);
-
- QV4::ScopedCallData callData(scope, 2);
- callData->thisObject = workerEngine->global();
- callData->args[0] = qmlContext->d()->qml; // ###
- callData->args[1] = value;
- f->call(scope, callData);
+
+ QV4::JSCallData jsCallData(scope, 1);
+ *jsCallData->thisObject = v4->global();
+ jsCallData->args[0] = value;
+ onmessage->call(jsCallData);
if (scope.hasException()) {
QQmlError error = scope.engine->catchExceptionAsQmlError();
reportScriptException(script, error);
@@ -381,39 +264,37 @@ void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
QString fileName = QQmlFile::urlToLocalFileOrQrc(url);
- QV4::ExecutionEngine *v4 = QV8Engine::getV4(workerEngine);
- QV4::Scope scope(v4);
- QScopedPointer<QV4::Script> program;
-
WorkerScript *script = workers.value(id);
if (!script)
return;
- script->source = url;
- QV4::Scoped<QV4::QmlContext> qmlContext(scope, getWorker(script));
- Q_ASSERT(!!qmlContext);
+ QV4::ExecutionEngine *v4 = QV8Engine::getV4(script);
+
+ script->source = url;
- if (const QQmlPrivate::CachedQmlUnit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(url)) {
- QV4::CompiledData::CompilationUnit *jsUnit = cachedUnit->createCompilationUnit();
- program.reset(new QV4::Script(v4, qmlContext, jsUnit));
+ if (fileName.endsWith(QLatin1String(".mjs"))) {
+ auto moduleUnit = v4->loadModule(url);
+ if (moduleUnit) {
+ if (moduleUnit->instantiate(v4))
+ moduleUnit->evaluate();
+ } else {
+ v4->throwError(QStringLiteral("Could not load module file"));
+ }
} else {
- QFile f(fileName);
- if (!f.open(QIODevice::ReadOnly)) {
- qWarning().nospace() << "WorkerScript: Cannot find source file " << url.toString();
+ QString error;
+ QV4::Scope scope(v4);
+ QScopedPointer<QV4::Script> program;
+ program.reset(QV4::Script::createFromFileOrCache(v4, /*qmlContext*/nullptr, fileName, url, &error));
+ if (program.isNull()) {
+ if (!error.isEmpty())
+ qWarning().nospace() << error;
return;
}
- QByteArray data = f.readAll();
- QString sourceCode = QString::fromUtf8(data);
- QmlIR::Document::removeScriptPragmas(sourceCode);
-
- program.reset(new QV4::Script(v4, qmlContext, sourceCode, url.toString()));
- program->parse();
+ if (!v4->hasException)
+ program->run();
}
- if (!v4->hasException)
- program->run();
-
if (v4->hasException) {
QQmlError error = v4->catchExceptionAsQmlError();
reportScriptException(script, error);
@@ -423,9 +304,7 @@ void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
void QQuickWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script,
const QQmlError &error)
{
- QQuickWorkerScriptEnginePrivate *p = QQuickWorkerScriptEnginePrivate::get(workerEngine);
-
- QMutexLocker locker(&p->m_lock);
+ QMutexLocker locker(&script->p->m_lock);
if (script->owner)
QCoreApplication::postEvent(script->owner, new WorkerErrorEvent(error));
}
@@ -514,21 +393,47 @@ QQuickWorkerScriptEngine::~QQuickWorkerScriptEngine()
d->deleteLater();
}
-QQuickWorkerScriptEnginePrivate::WorkerScript::WorkerScript()
-: id(-1), initialized(false), owner(0)
+QQuickWorkerScriptEnginePrivate::WorkerScript::WorkerScript(int id, QQuickWorkerScriptEnginePrivate *parent)
+ : QV8Engine(new QV4::ExecutionEngine)
+ , p(parent)
+ , id(id)
{
+ m_v4Engine->v8Engine = this;
+
+ initQmlGlobalObject();
+
+ QV4::Scope scope(m_v4Engine);
+ QV4::ScopedObject api(scope, scope.engine->newObject());
+ QV4::ScopedString name(scope, m_v4Engine->newString(QStringLiteral("sendMessage")));
+ QV4::ScopedValue sendMessage(scope, QV4::FunctionObject::createBuiltinFunction(m_v4Engine, name, method_sendMessage, 1));
+ api->put(QV4::ScopedString(scope, scope.engine->newString(QStringLiteral("sendMessage"))), sendMessage);
+ m_v4Engine->globalObject->put(QV4::ScopedString(scope, scope.engine->newString(QStringLiteral("WorkerScript"))), api);
}
QQuickWorkerScriptEnginePrivate::WorkerScript::~WorkerScript()
{
+ delete m_v4Engine;
+}
+
+#if QT_CONFIG(qml_network)
+QNetworkAccessManager *QQuickWorkerScriptEnginePrivate::WorkerScript::networkAccessManager()
+{
+ if (!accessManager) {
+ if (p->qmlengine && p->qmlengine->networkAccessManagerFactory()) {
+ accessManager.reset(p->qmlengine->networkAccessManagerFactory()->create(p));
+ } else {
+ accessManager.reset(new QNetworkAccessManager(p));
+ }
+ }
+ return accessManager.data();
}
+#endif
int QQuickWorkerScriptEngine::registerWorkerScript(QQuickWorkerScript *owner)
{
typedef QQuickWorkerScriptEnginePrivate::WorkerScript WorkerScript;
- WorkerScript *script = new WorkerScript;
+ WorkerScript *script = new WorkerScript(d->m_nextId++, d);
- script->id = d->m_nextId++;
script->owner = owner;
d->m_lock.lock();
@@ -542,7 +447,7 @@ void QQuickWorkerScriptEngine::removeWorkerScript(int id)
{
QQuickWorkerScriptEnginePrivate::WorkerScript* script = d->workers.value(id);
if (script) {
- script->owner = 0;
+ script->owner = nullptr;
QCoreApplication::postEvent(d, new WorkerRemoveEvent(id));
}
}
@@ -561,9 +466,6 @@ void QQuickWorkerScriptEngine::run()
{
d->m_lock.lock();
- d->workerEngine = new QQuickWorkerScriptEnginePrivate::WorkerEngine(d);
- d->workerEngine->init();
-
d->m_wait.wakeAll();
d->m_lock.unlock();
@@ -572,8 +474,6 @@ void QQuickWorkerScriptEngine::run()
qDeleteAll(d->workers);
d->workers.clear();
-
- delete d->workerEngine; d->workerEngine = 0;
}
@@ -581,8 +481,8 @@ void QQuickWorkerScriptEngine::run()
\qmltype WorkerScript
\instantiates QQuickWorkerScript
\ingroup qtquick-threading
- \inqmlmodule QtQuick
- \brief Enables the use of threads in a Qt Quick application
+ \inqmlmodule QtQml
+ \brief Enables the use of threads in a Qt Quick application.
Use WorkerScript to run operations in a new thread.
This is useful for running operations in the background so
@@ -595,22 +495,32 @@ void QQuickWorkerScriptEngine::run()
\snippet qml/workerscript/workerscript.qml 0
- The above worker script specifies a JavaScript file, "script.js", that handles
- the operations to be performed in the new thread. Here is \c script.js:
+ The above worker script specifies a JavaScript file, "script.mjs", that handles
+ the operations to be performed in the new thread. Here is \c script.mjs:
- \quotefile qml/workerscript/script.js
+ \quotefile qml/workerscript/script.mjs
When the user clicks anywhere within the rectangle, \c sendMessage() is
called, triggering the \tt WorkerScript.onMessage() handler in
- \tt script.js. This in turn sends a reply message that is then received
+ \tt script.mjs. This in turn sends a reply message that is then received
by the \tt onMessage() handler of \tt myWorker.
+ The example uses a script that is an ECMAScript module, because it has the ".mjs" extension.
+ It can use import statements to access functionality from other modules and it is run in JavaScript
+ strict mode.
+
+ If a worker script has the extension ".js" instead, then it is considered to contain plain JavaScript
+ statements and it is run in non-strict mode.
+
+ \note Each WorkerScript element will instantiate a separate JavaScript engine to ensure perfect
+ isolation and thread-safety. If the impact of that results in a memory consumption that is too
+ high for your environment, then consider sharing a WorkerScript element.
\section3 Restrictions
Since the \c WorkerScript.onMessage() function is run in a separate thread, the
JavaScript file is evaluated in a context separate from the main QML engine. This means
- that unlike an ordinary JavaScript file that is imported into QML, the \c script.js
+ that unlike an ordinary JavaScript file that is imported into QML, the \c script.mjs
in the above example cannot access the properties, methods or other attributes
of the QML item, nor can it access any context properties set on the QML object
through QQmlContext.
@@ -618,13 +528,14 @@ void QQuickWorkerScriptEngine::run()
Additionally, there are restrictions on the types of values that can be passed to and
from the worker script. See the sendMessage() documentation for details.
- Worker script can not use \l {qtqml-javascript-imports.html}{.import} syntax.
+ Worker scripts that are plain JavaScript sources can not use \l {qtqml-javascript-imports.html}{.import} syntax.
+ Scripts that are ECMAScript modules can freely use import and export statements.
\sa {Qt Quick Examples - Threading},
{Threaded ListModel Example}
*/
QQuickWorkerScript::QQuickWorkerScript(QObject *parent)
-: QObject(parent), m_engine(0), m_scriptId(-1), m_componentComplete(true)
+: QObject(parent), m_engine(nullptr), m_scriptId(-1), m_componentComplete(true)
{
}
@@ -638,6 +549,10 @@ QQuickWorkerScript::~QQuickWorkerScript()
This holds the url of the JavaScript file that implements the
\tt WorkerScript.onMessage() handler for threaded operations.
+
+ If the file name component of the url ends with ".mjs", then the script
+ is parsed as an ECMAScript module and run in strict mode. Otherwise it is considered to be
+ plain script.
*/
QUrl QQuickWorkerScript::source() const
{
@@ -685,7 +600,7 @@ void QQuickWorkerScript::sendMessage(QQmlV4Function *args)
}
QV4::Scope scope(args->v4engine());
- QV4::ScopedValue argument(scope, QV4::Primitive::undefinedValue());
+ QV4::ScopedValue argument(scope, QV4::Value::undefinedValue());
if (args->length() != 0)
argument = (*args)[0];
@@ -704,7 +619,7 @@ QQuickWorkerScriptEngine *QQuickWorkerScript::engine()
QQmlEngine *engine = qmlEngine(this);
if (!engine) {
qWarning("QQuickWorkerScript: engine() called without qmlEngine() set");
- return 0;
+ return nullptr;
}
m_engine = QQmlEnginePrivate::get(engine)->getWorkerScriptEngine();
@@ -715,7 +630,7 @@ QQuickWorkerScriptEngine *QQuickWorkerScript::engine()
return m_engine;
}
- return 0;
+ return nullptr;
}
void QQuickWorkerScript::componentComplete()
@@ -739,8 +654,7 @@ bool QQuickWorkerScript::event(QEvent *event)
QQmlEngine *engine = qmlEngine(this);
if (engine) {
WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
- QV8Engine *v8engine = QQmlEnginePrivate::get(engine)->v8engine();
- QV4::Scope scope(QV8Engine::getV4(v8engine));
+ QV4::Scope scope(engine->handle());
QV4::ScopedValue value(scope, QV4::Serialize::deserialize(workerEvent->data(), scope.engine));
emit message(QQmlV4Handle(value));
}