From 5032ff5c2b48d53e4580bb7d50b1f6de081263b0 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 4 Apr 2019 15:41:18 +0200 Subject: Move workerscript to its own module Change-Id: I778cfe842ddf1c600a837d8f2061a338887eed95 Reviewed-by: Lars Knoll --- src/imports/imports.pro | 2 + src/imports/qtquick2/plugin.cpp | 6 + src/imports/qtquick2/qtquick2.pro | 2 + src/imports/workerscript/dependencies.json | 2 + src/imports/workerscript/plugin.cpp | 81 +++ src/imports/workerscript/qmldir | 3 + src/imports/workerscript/workerscript.pro | 11 + src/qml/jsruntime/jsruntime.pri | 2 - src/qml/jsruntime/qv4objectproto_p.h | 2 +- src/qml/jsruntime/qv4regexpobject.cpp | 7 - src/qml/jsruntime/qv4regexpobject_p.h | 9 +- src/qml/jsruntime/qv4sequenceobject_p.h | 2 +- src/qml/jsruntime/qv4serialize.cpp | 447 ---------------- src/qml/jsruntime/qv4serialize_p.h | 76 --- src/qml/qml/qqmlengine.cpp | 16 - src/qml/qml/qqmlengine_p.h | 4 +- src/qml/types/qquickworkerscript.cpp | 669 ----------------------- src/qml/types/qquickworkerscript_p.h | 123 ----- src/qml/types/types.pri | 7 - src/qmlworkerscript/qmlworkerscript.pro | 20 + src/qmlworkerscript/qqmlworkerscriptmodule.cpp | 62 +++ src/qmlworkerscript/qqmlworkerscriptmodule_p.h | 69 +++ src/qmlworkerscript/qquickworkerscript.cpp | 673 ++++++++++++++++++++++++ src/qmlworkerscript/qquickworkerscript_p.h | 123 +++++ src/qmlworkerscript/qtqmlworkerscriptglobal.h | 58 ++ src/qmlworkerscript/qtqmlworkerscriptglobal_p.h | 60 +++ src/qmlworkerscript/qv4serialize.cpp | 447 ++++++++++++++++ src/qmlworkerscript/qv4serialize_p.h | 76 +++ src/src.pro | 2 + 29 files changed, 1707 insertions(+), 1354 deletions(-) create mode 100644 src/imports/workerscript/dependencies.json create mode 100644 src/imports/workerscript/plugin.cpp create mode 100644 src/imports/workerscript/qmldir create mode 100644 src/imports/workerscript/workerscript.pro delete mode 100644 src/qml/jsruntime/qv4serialize.cpp delete mode 100644 src/qml/jsruntime/qv4serialize_p.h delete mode 100644 src/qml/types/qquickworkerscript.cpp delete mode 100644 src/qml/types/qquickworkerscript_p.h create mode 100644 src/qmlworkerscript/qmlworkerscript.pro create mode 100644 src/qmlworkerscript/qqmlworkerscriptmodule.cpp create mode 100644 src/qmlworkerscript/qqmlworkerscriptmodule_p.h create mode 100644 src/qmlworkerscript/qquickworkerscript.cpp create mode 100644 src/qmlworkerscript/qquickworkerscript_p.h create mode 100644 src/qmlworkerscript/qtqmlworkerscriptglobal.h create mode 100644 src/qmlworkerscript/qtqmlworkerscriptglobal_p.h create mode 100644 src/qmlworkerscript/qv4serialize.cpp create mode 100644 src/qmlworkerscript/qv4serialize_p.h (limited to 'src') diff --git a/src/imports/imports.pro b/src/imports/imports.pro index 901c263be5..6b31dfc65d 100644 --- a/src/imports/imports.pro +++ b/src/imports/imports.pro @@ -1,4 +1,5 @@ TEMPLATE = subdirs +QT_FOR_CONFIG += qml-private SUBDIRS += \ builtins \ @@ -6,6 +7,7 @@ SUBDIRS += \ models \ labsmodels +qtConfig(qml-worker-script): SUBDIRS += workerscript qtConfig(thread): SUBDIRS += folderlistmodel qtHaveModule(sql): SUBDIRS += localstorage qtConfig(settings): SUBDIRS += settings diff --git a/src/imports/qtquick2/plugin.cpp b/src/imports/qtquick2/plugin.cpp index a5a2c73ced..efa05c349c 100644 --- a/src/imports/qtquick2/plugin.cpp +++ b/src/imports/qtquick2/plugin.cpp @@ -42,6 +42,9 @@ #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #include +#if QT_CONFIG(qml_worker_script) +#include +#endif #endif // QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include @@ -63,6 +66,9 @@ public: #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QQmlEnginePrivate::registerQuickTypes(); QQmlModelsModule::registerQuickTypes(); +#if QT_CONFIG(qml_worker_script) + QQmlWorkerScriptModule::registerQuickTypes(); +#endif #endif QQmlQtQuick2Module::defineModule(); diff --git a/src/imports/qtquick2/qtquick2.pro b/src/imports/qtquick2/qtquick2.pro index 1b45d69eb7..8543049ead 100644 --- a/src/imports/qtquick2/qtquick2.pro +++ b/src/imports/qtquick2/qtquick2.pro @@ -8,4 +8,6 @@ SOURCES += \ QT += quick-private qml-private qmlmodels-private +qtConfig(qml-worker-script): QT += qmlworkerscript-private + load(qml_plugin) diff --git a/src/imports/workerscript/dependencies.json b/src/imports/workerscript/dependencies.json new file mode 100644 index 0000000000..0d4f101c7a --- /dev/null +++ b/src/imports/workerscript/dependencies.json @@ -0,0 +1,2 @@ +[ +] diff --git a/src/imports/workerscript/plugin.cpp b/src/imports/workerscript/plugin.cpp new file mode 100644 index 0000000000..5b3bff7934 --- /dev/null +++ b/src/imports/workerscript/plugin.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \qmlmodule QtQml.WorkerScript 2.\QtMinorVersion + \title Qt QML WorkerScript QML Types + \ingroup qmlmodules + \brief Provides QML types for worker scripts + \since 5.14 + + This QML module contains types for using worker scripts. + + To use the types in this module, import the module with the following line: + + \qml \QtMinorVersion + import QtQml.WorkerScript 2.\1 + \endqml +*/ + +class QtQmlWorkerScriptPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) +public: + QtQmlWorkerScriptPlugin(QObject *parent = nullptr) : QQmlExtensionPlugin(parent) { } + void registerTypes(const char *uri) override + { + Q_ASSERT(QLatin1String(uri) == QLatin1String("QtQml.WorkerScript")); + + QQmlWorkerScriptModule::defineModule(); + + // Auto-increment the import to stay in sync with ALL future QtQuick minor versions from 5.11 onward + qmlRegisterModule(uri, 2, QT_VERSION_MINOR); + } +}; + +QT_END_NAMESPACE + +#include "plugin.moc" diff --git a/src/imports/workerscript/qmldir b/src/imports/workerscript/qmldir new file mode 100644 index 0000000000..0e811d1dbc --- /dev/null +++ b/src/imports/workerscript/qmldir @@ -0,0 +1,3 @@ +module QtQml.WorkerScript +plugin workerscriptsplugin +classname QtQmlWorkerScriptPlugin diff --git a/src/imports/workerscript/workerscript.pro b/src/imports/workerscript/workerscript.pro new file mode 100644 index 0000000000..d48e285bda --- /dev/null +++ b/src/imports/workerscript/workerscript.pro @@ -0,0 +1,11 @@ +CXX_MODULE = qml +TARGET = workerscriptplugin +TARGETPATH = QtQml/WorkerScript.2 +IMPORT_VERSION = 2.$$QT_MINOR_VERSION + +SOURCES += \ + plugin.cpp + +QT = qml-private qmlworkerscript-private + +load(qml_plugin) diff --git a/src/qml/jsruntime/jsruntime.pri b/src/qml/jsruntime/jsruntime.pri index a24ee0a188..ae9a36ca22 100644 --- a/src/qml/jsruntime/jsruntime.pri +++ b/src/qml/jsruntime/jsruntime.pri @@ -42,7 +42,6 @@ SOURCES += \ $$PWD/qv4objectiterator.cpp \ $$PWD/qv4regexp.cpp \ $$PWD/qv4runtimecodegen.cpp \ - $$PWD/qv4serialize.cpp \ $$PWD/qv4script.cpp \ $$PWD/qv4symbol.cpp \ $$PWD/qv4setobject.cpp \ @@ -109,7 +108,6 @@ HEADERS += \ $$PWD/qv4property_p.h \ $$PWD/qv4objectiterator_p.h \ $$PWD/qv4regexp_p.h \ - $$PWD/qv4serialize_p.h \ $$PWD/qv4script_p.h \ $$PWD/qv4symbol_p.h \ $$PWD/qv4setobject_p.h \ diff --git a/src/qml/jsruntime/qv4objectproto_p.h b/src/qml/jsruntime/qv4objectproto_p.h index e9515b5b68..8707305dc2 100644 --- a/src/qml/jsruntime/qv4objectproto_p.h +++ b/src/qml/jsruntime/qv4objectproto_p.h @@ -74,7 +74,7 @@ struct ObjectCtor: FunctionObject static ReturnedValue virtualCall(const FunctionObject *m, const Value *thisObject, const Value *argv, int argc); }; -struct ObjectPrototype: Object +struct Q_QML_PRIVATE_EXPORT ObjectPrototype: Object { void init(ExecutionEngine *engine, Object *ctor); diff --git a/src/qml/jsruntime/qv4regexpobject.cpp b/src/qml/jsruntime/qv4regexpobject.cpp index 5bd25dcbec..64aba1d85c 100644 --- a/src/qml/jsruntime/qv4regexpobject.cpp +++ b/src/qml/jsruntime/qv4regexpobject.cpp @@ -198,13 +198,6 @@ QString RegExpObject::toString() const return p; } -QString RegExpObject::source() const -{ - Scope scope(engine()); - ScopedValue s(scope, get(scope.engine->id_source())); - return s->toQString(); -} - ReturnedValue RegExpObject::builtinExec(ExecutionEngine *engine, const String *str) { QString s = str->toQString(); diff --git a/src/qml/jsruntime/qv4regexpobject_p.h b/src/qml/jsruntime/qv4regexpobject_p.h index 04b533e49d..b94889e9f0 100644 --- a/src/qml/jsruntime/qv4regexpobject_p.h +++ b/src/qml/jsruntime/qv4regexpobject_p.h @@ -101,7 +101,7 @@ DECLARE_HEAP_OBJECT(RegExpCtor, FunctionObject) { } -struct RegExpObject: Object { +struct Q_QML_PRIVATE_EXPORT RegExpObject: Object { V4_OBJECT2(RegExpObject, Object) Q_MANAGED_TYPE(RegExpObject) V4_INTERNALCLASS(RegExpObject) @@ -145,7 +145,12 @@ struct RegExpObject: Object { QRegularExpression toQRegularExpression() const; #endif QString toString() const; - QString source() const; + QString source() const + { + Scope scope(engine()); + ScopedValue s(scope, get(scope.engine->id_source())); + return s->toQString(); + } Heap::RegExp *value() const { return d()->value; } uint flags() const { return d()->value->flags; } diff --git a/src/qml/jsruntime/qv4sequenceobject_p.h b/src/qml/jsruntime/qv4sequenceobject_p.h index da71215bed..886dfaa521 100644 --- a/src/qml/jsruntime/qv4sequenceobject_p.h +++ b/src/qml/jsruntime/qv4sequenceobject_p.h @@ -65,7 +65,7 @@ QT_BEGIN_NAMESPACE namespace QV4 { -struct SequencePrototype : public QV4::Object +struct Q_QML_PRIVATE_EXPORT SequencePrototype : public QV4::Object { V4_PROTOTYPE(arrayPrototype) void init(); diff --git a/src/qml/jsruntime/qv4serialize.cpp b/src/qml/jsruntime/qv4serialize.cpp deleted file mode 100644 index a5e62d3e35..0000000000 --- a/src/qml/jsruntime/qv4serialize.cpp +++ /dev/null @@ -1,447 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qv4serialize_p.h" - -#include -#include -#include -#if QT_CONFIG(qml_sequence_object) -#include -#endif -#include -#include - -QT_BEGIN_NAMESPACE - -using namespace QV4; - -// We allow the following JavaScript types to be passed between the main and -// the secondary thread: -// + undefined -// + null -// + Boolean -// + String -// + Function -// + Array -// + "Simple" Objects -// + Number -// + Date -// + RegExp -// - -enum Type { - WorkerUndefined, - WorkerNull, - WorkerTrue, - WorkerFalse, - WorkerString, - WorkerFunction, - WorkerArray, - WorkerObject, - WorkerInt32, - WorkerUint32, - WorkerNumber, - WorkerDate, - WorkerRegexp, - WorkerListModel, -#if QT_CONFIG(qml_sequence_object) - WorkerSequence -#endif -}; - -static inline quint32 valueheader(Type type, quint32 size = 0) -{ - return quint8(type) << 24 | (size & 0xFFFFFF); -} - -static inline Type headertype(quint32 header) -{ - return (Type)(header >> 24); -} - -static inline quint32 headersize(quint32 header) -{ - return header & 0xFFFFFF; -} - -static inline void push(QByteArray &data, quint32 value) -{ - data.append((const char *)&value, sizeof(quint32)); -} - -static inline void push(QByteArray &data, double value) -{ - data.append((const char *)&value, sizeof(double)); -} - -static inline void push(QByteArray &data, void *ptr) -{ - data.append((const char *)&ptr, sizeof(void *)); -} - -static inline void reserve(QByteArray &data, int extra) -{ - data.reserve(data.size() + extra); -} - -static inline quint32 popUint32(const char *&data) -{ - quint32 rv = *((const quint32 *)data); - data += sizeof(quint32); - return rv; -} - -static inline double popDouble(const char *&data) -{ - double rv = *((const double *)data); - data += sizeof(double); - return rv; -} - -static inline void *popPtr(const char *&data) -{ - void *rv = *((void *const *)data); - data += sizeof(void *); - return rv; -} - -// XXX TODO: Check that worker script is exception safe in the case of -// serialization/deserialization failures - -#define ALIGN(size) (((size) + 3) & ~3) -void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine *engine) -{ - QV4::Scope scope(engine); - - if (v.isEmpty()) { - Q_ASSERT(!"Serialize: got empty value"); - } else if (v.isUndefined()) { - push(data, valueheader(WorkerUndefined)); - } else if (v.isNull()) { - push(data, valueheader(WorkerNull)); - } else if (v.isBoolean()) { - push(data, valueheader(v.booleanValue() == true ? WorkerTrue : WorkerFalse)); - } else if (v.isString()) { - const QString &qstr = v.toQString(); - int length = qstr.length(); - if (length > 0xFFFFFF) { - push(data, valueheader(WorkerUndefined)); - return; - } - int utf16size = ALIGN(length * sizeof(quint16)); - - reserve(data, utf16size + sizeof(quint32)); - push(data, valueheader(WorkerString, length)); - - int offset = data.size(); - data.resize(data.size() + utf16size); - char *buffer = data.data() + offset; - - memcpy(buffer, qstr.constData(), length*sizeof(QChar)); - } else if (v.as()) { - // XXX TODO: Implement passing function objects between the main and - // worker scripts - push(data, valueheader(WorkerUndefined)); - } else if (const QV4::ArrayObject *array = v.as()) { - uint length = array->getLength(); - if (length > 0xFFFFFF) { - push(data, valueheader(WorkerUndefined)); - return; - } - reserve(data, sizeof(quint32) + length * sizeof(quint32)); - push(data, valueheader(WorkerArray, length)); - ScopedValue val(scope); - for (uint ii = 0; ii < length; ++ii) - serialize(data, (val = array->get(ii)), engine); - } else if (v.isInteger()) { - reserve(data, 2 * sizeof(quint32)); - push(data, valueheader(WorkerInt32)); - push(data, (quint32)v.integerValue()); -// } else if (v.IsUint32()) { -// reserve(data, 2 * sizeof(quint32)); -// push(data, valueheader(WorkerUint32)); -// push(data, v.Uint32Value()); - } else if (v.isNumber()) { - reserve(data, sizeof(quint32) + sizeof(double)); - push(data, valueheader(WorkerNumber)); - push(data, v.asDouble()); - } else if (const QV4::DateObject *d = v.as()) { - reserve(data, sizeof(quint32) + sizeof(double)); - push(data, valueheader(WorkerDate)); - push(data, d->date()); - } else if (const RegExpObject *re = v.as()) { - quint32 flags = re->flags(); - QString pattern = re->source(); - int length = pattern.length() + 1; - if (length > 0xFFFFFF) { - push(data, valueheader(WorkerUndefined)); - return; - } - int utf16size = ALIGN(length * sizeof(quint16)); - - reserve(data, sizeof(quint32) + utf16size); - push(data, valueheader(WorkerRegexp, flags)); - push(data, (quint32)length); - - int offset = data.size(); - data.resize(data.size() + utf16size); - char *buffer = data.data() + offset; - - memcpy(buffer, pattern.constData(), length*sizeof(QChar)); - } else if (const QObjectWrapper *qobjectWrapper = v.as()) { - // XXX TODO: Generalize passing objects between the main thread and worker scripts so - // that others can trivially plug in their elements. - if (QObject *lm = qobjectWrapper->object()) { - if (QObject *agent = qvariant_cast(lm->property("agent"))) { - if (QMetaObject::invokeMethod(agent, "addref")) { - push(data, valueheader(WorkerListModel)); - push(data, (void *)agent); - return; - } - } - } - // No other QObject's are allowed to be sent - push(data, valueheader(WorkerUndefined)); - } else if (const Object *o = v.as()) { -#if QT_CONFIG(qml_sequence_object) - if (o->isListType()) { - // valid sequence. we generate a length (sequence length + 1 for the sequence type) - uint seqLength = ScopedValue(scope, o->get(engine->id_length()))->toUInt32(); - uint length = seqLength + 1; - if (length > 0xFFFFFF) { - push(data, valueheader(WorkerUndefined)); - return; - } - reserve(data, sizeof(quint32) + length * sizeof(quint32)); - push(data, valueheader(WorkerSequence, length)); - serialize(data, QV4::Value::fromInt32(QV4::SequencePrototype::metaTypeForSequence(o)), engine); // sequence type - ScopedValue val(scope); - for (uint ii = 0; ii < seqLength; ++ii) - serialize(data, (val = o->get(ii)), engine); // sequence elements - - return; - } -#endif - - // regular object - QV4::ScopedValue val(scope, v); - QV4::ScopedArrayObject properties(scope, QV4::ObjectPrototype::getOwnPropertyNames(engine, val)); - quint32 length = properties->getLength(); - if (length > 0xFFFFFF) { - push(data, valueheader(WorkerUndefined)); - return; - } - push(data, valueheader(WorkerObject, length)); - - QV4::ScopedValue s(scope); - for (quint32 ii = 0; ii < length; ++ii) { - s = properties->get(ii); - serialize(data, s, engine); - - QV4::String *str = s->as(); - val = o->get(str); - if (scope.hasException()) - scope.engine->catchException(); - - serialize(data, val, engine); - } - return; - } else { - push(data, valueheader(WorkerUndefined)); - } -} - -struct VariantRef -{ - VariantRef() : obj(nullptr) {} - VariantRef(const VariantRef &r) : obj(r.obj) { addref(); } - VariantRef(QObject *a) : obj(a) { addref(); } - ~VariantRef() { release(); } - - VariantRef &operator=(const VariantRef &o) { - o.addref(); - release(); - obj = o.obj; - return *this; - } - - void addref() const - { - if (obj) - QMetaObject::invokeMethod(obj, "addref"); - } - - void release() const - { - if (obj) - QMetaObject::invokeMethod(obj, "release"); - - } - - QObject *obj; -}; - -QT_END_NAMESPACE -Q_DECLARE_METATYPE(VariantRef) -Q_DECLARE_METATYPE(QV4::ExecutionEngine *) -QT_BEGIN_NAMESPACE - -ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) -{ - quint32 header = popUint32(data); - Type type = headertype(header); - - Scope scope(engine); - - switch (type) { - case WorkerUndefined: - return QV4::Encode::undefined(); - case WorkerNull: - return QV4::Encode::null(); - case WorkerTrue: - return QV4::Encode(true); - case WorkerFalse: - return QV4::Encode(false); - case WorkerString: - { - quint32 size = headersize(header); - QString qstr((const QChar *)data, size); - data += ALIGN(size * sizeof(quint16)); - return QV4::Encode(engine->newString(qstr)); - } - case WorkerFunction: - Q_ASSERT(!"Unreachable"); - break; - case WorkerArray: - { - quint32 size = headersize(header); - ScopedArrayObject a(scope, engine->newArrayObject()); - ScopedValue v(scope); - for (quint32 ii = 0; ii < size; ++ii) { - v = deserialize(data, engine); - a->put(ii, v); - } - return a.asReturnedValue(); - } - case WorkerObject: - { - quint32 size = headersize(header); - ScopedObject o(scope, engine->newObject()); - ScopedValue name(scope); - ScopedString n(scope); - ScopedValue value(scope); - for (quint32 ii = 0; ii < size; ++ii) { - name = deserialize(data, engine); - value = deserialize(data, engine); - n = name->asReturnedValue(); - o->put(n, value); - } - return o.asReturnedValue(); - } - case WorkerInt32: - return QV4::Encode((qint32)popUint32(data)); - case WorkerUint32: - return QV4::Encode(popUint32(data)); - case WorkerNumber: - return QV4::Encode(popDouble(data)); - case WorkerDate: - return QV4::Encode(engine->newDateObject(QV4::Value::fromDouble(popDouble(data)))); - case WorkerRegexp: - { - quint32 flags = headersize(header); - quint32 length = popUint32(data); - QString pattern = QString((const QChar *)data, length - 1); - data += ALIGN(length * sizeof(quint16)); - return Encode(engine->newRegExpObject(pattern, flags)); - } - case WorkerListModel: - { - QObject *agent = reinterpret_cast(popPtr(data)); - QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(engine, agent)); - // ### Find a better solution then the ugly property - VariantRef ref(agent); - QVariant var = QVariant::fromValue(ref); - QV4::ScopedValue v(scope, scope.engine->fromVariant(var)); - QV4::ScopedString s(scope, engine->newString(QStringLiteral("__qml:hidden:ref"))); - rv->as()->defineReadonlyProperty(s, v); - - QMetaObject::invokeMethod(agent, "release"); - agent->setProperty("engine", QVariant::fromValue(engine)); - return rv->asReturnedValue(); - } -#if QT_CONFIG(qml_sequence_object) - case WorkerSequence: - { - ScopedValue value(scope); - bool succeeded = false; - quint32 length = headersize(header); - quint32 seqLength = length - 1; - value = deserialize(data, engine); - int sequenceType = value->integerValue(); - ScopedArrayObject array(scope, engine->newArrayObject()); - array->arrayReserve(seqLength); - for (quint32 ii = 0; ii < seqLength; ++ii) { - value = deserialize(data, engine); - array->arrayPut(ii, value); - } - array->setArrayLengthUnchecked(seqLength); - QVariant seqVariant = QV4::SequencePrototype::toVariant(array, sequenceType, &succeeded); - return QV4::SequencePrototype::fromVariant(engine, seqVariant, &succeeded); - } -#endif - } - Q_ASSERT(!"Unreachable"); - return QV4::Encode::undefined(); -} - -QByteArray Serialize::serialize(const QV4::Value &value, ExecutionEngine *engine) -{ - QByteArray rv; - serialize(rv, value, engine); - return rv; -} - -ReturnedValue Serialize::deserialize(const QByteArray &data, ExecutionEngine *engine) -{ - const char *stream = data.constData(); - return deserialize(stream, engine); -} - -QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4serialize_p.h b/src/qml/jsruntime/qv4serialize_p.h deleted file mode 100644 index c8700c3ca5..0000000000 --- a/src/qml/jsruntime/qv4serialize_p.h +++ /dev/null @@ -1,76 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QV4SERIALIZE_P_H -#define QV4SERIALIZE_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include -#include - -QT_BEGIN_NAMESPACE - -namespace QV4 { - -class Serialize { -public: - - static QByteArray serialize(const Value &, ExecutionEngine *); - static ReturnedValue deserialize(const QByteArray &, ExecutionEngine *); - -private: - static void serialize(QByteArray &, const Value &, ExecutionEngine *); - static ReturnedValue deserialize(const char *&, ExecutionEngine *); -}; - -} - -QT_END_NAMESPACE - -#endif // QV8WORKER_P_H diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index bb2b3e462c..cb94ae379e 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -87,9 +87,6 @@ #include #endif #include -#if QT_CONFIG(qml_worker_script) -#include -#endif #include #ifdef Q_OS_WIN // for %APPDATA% @@ -239,9 +236,6 @@ void QQmlEnginePrivate::registerQuickTypes() #endif qmlRegisterType(uri, 2, 8, "LoggingCategory"); qmlRegisterType(uri, 2, 12, "LoggingCategory"); -#if QT_CONFIG(qml_worker_script) - qmlRegisterType(uri, 2, 0, "WorkerScript"); -#endif #if QT_CONFIG(qml_locale) qmlRegisterUncreatableType(uri, 2, 0, "Locale", QQmlEngine::tr("Locale cannot be instantiated. Use Qt.locale()")); #endif @@ -968,16 +962,6 @@ void QQmlEnginePrivate::init() rootContext = new QQmlContext(q,true); } -#if QT_CONFIG(qml_worker_script) -QQuickWorkerScriptEngine *QQmlEnginePrivate::getWorkerScriptEngine() -{ - Q_Q(QQmlEngine); - if (!workerScriptEngine) - workerScriptEngine = new QQuickWorkerScriptEngine(q); - return workerScriptEngine; -} -#endif - /*! \class QQmlEngine \since 5.0 diff --git a/src/qml/qml/qqmlengine_p.h b/src/qml/qml/qqmlengine_p.h index 4f7fb79593..6ef88404fd 100644 --- a/src/qml/qml/qqmlengine_p.h +++ b/src/qml/qml/qqmlengine_p.h @@ -94,7 +94,6 @@ class QQmlTypeNameCache; class QQmlComponentAttached; class QQmlCleanup; class QQmlDelayedError; -class QQuickWorkerScriptEngine; class QQmlObjectCreator; class QDir; class QQmlIncubator; @@ -154,8 +153,7 @@ public: QV4::ExecutionEngine *v4engine() const { return q_func()->handle(); } #if QT_CONFIG(qml_worker_script) - QQuickWorkerScriptEngine *getWorkerScriptEngine(); - QQuickWorkerScriptEngine *workerScriptEngine; + QThread *workerScriptEngine; #endif QUrl baseUrl; diff --git a/src/qml/types/qquickworkerscript.cpp b/src/qml/types/qquickworkerscript.cpp deleted file mode 100644 index c081c9e7fc..0000000000 --- a/src/qml/types/qquickworkerscript.cpp +++ /dev/null @@ -1,669 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qtqmlglobal_p.h" -#include "qquickworkerscript_p.h" -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if QT_CONFIG(qml_network) -#include -#include "qqmlnetworkaccessmanagerfactory.h" -#endif - -#include -#include - -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class WorkerDataEvent : public QEvent -{ -public: - enum Type { WorkerData = QEvent::User }; - - WorkerDataEvent(int workerId, const QByteArray &data); - virtual ~WorkerDataEvent(); - - int workerId() const; - QByteArray data() const; - -private: - int m_id; - QByteArray m_data; -}; - -class WorkerLoadEvent : public QEvent -{ -public: - enum Type { WorkerLoad = WorkerDataEvent::WorkerData + 1 }; - - WorkerLoadEvent(int workerId, const QUrl &url); - - int workerId() const; - QUrl url() const; - -private: - int m_id; - QUrl m_url; -}; - -class WorkerRemoveEvent : public QEvent -{ -public: - enum Type { WorkerRemove = WorkerLoadEvent::WorkerLoad + 1 }; - - WorkerRemoveEvent(int workerId); - - int workerId() const; - -private: - int m_id; -}; - -class WorkerErrorEvent : public QEvent -{ -public: - enum Type { WorkerError = WorkerRemoveEvent::WorkerRemove + 1 }; - - WorkerErrorEvent(const QQmlError &error); - - QQmlError error() const; - -private: - QQmlError m_error; -}; - -class QQuickWorkerScriptEnginePrivate : public QObject -{ - Q_OBJECT -public: - enum WorkerEventTypes { - WorkerDestroyEvent = QEvent::User + 100 - }; - - QQuickWorkerScriptEnginePrivate(QQmlEngine *eng); - - QQmlEngine *qmlengine; - - QMutex m_lock; - QWaitCondition m_wait; - - struct WorkerScript : public QV8Engine { - WorkerScript(int id, QQuickWorkerScriptEnginePrivate *parent); - ~WorkerScript() override; - -#if QT_CONFIG(qml_network) - QNetworkAccessManager *networkAccessManager() override; -#endif - - QQuickWorkerScriptEnginePrivate *p = nullptr; - QUrl source; - QQuickWorkerScript *owner = nullptr; -#if QT_CONFIG(qml_network) - QScopedPointer accessManager; -#endif - int id = -1; - }; - - QHash workers; - QV4::ReturnedValue getWorker(WorkerScript *); - - int m_nextId; - - static QV4::ReturnedValue method_sendMessage(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc); - -signals: - void stopThread(); - -protected: - bool event(QEvent *) override; - -private: - void processMessage(int, const QByteArray &); - void processLoad(int, const QUrl &); - void reportScriptException(WorkerScript *, const QQmlError &error); -}; - -QQuickWorkerScriptEnginePrivate::QQuickWorkerScriptEnginePrivate(QQmlEngine *engine) -: qmlengine(engine), m_nextId(0) -{ -} - -QV4::ReturnedValue QQuickWorkerScriptEnginePrivate::method_sendMessage(const QV4::FunctionObject *b, - const QV4::Value *, const QV4::Value *argv, int argc) -{ - QV4::Scope scope(b); - WorkerScript *script = static_cast(scope.engine->v8Engine); - - QV4::ScopedValue v(scope, argc > 0 ? argv[0] : QV4::Value::undefinedValue()); - QByteArray data = QV4::Serialize::serialize(v, scope.engine); - - QMutexLocker locker(&script->p->m_lock); - if (script && script->owner) - QCoreApplication::postEvent(script->owner, new WorkerDataEvent(0, data)); - - return QV4::Encode::undefined(); -} - -bool QQuickWorkerScriptEnginePrivate::event(QEvent *event) -{ - if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) { - WorkerDataEvent *workerEvent = static_cast(event); - processMessage(workerEvent->workerId(), workerEvent->data()); - return true; - } else if (event->type() == (QEvent::Type)WorkerLoadEvent::WorkerLoad) { - WorkerLoadEvent *workerEvent = static_cast(event); - processLoad(workerEvent->workerId(), workerEvent->url()); - return true; - } else if (event->type() == (QEvent::Type)WorkerDestroyEvent) { - emit stopThread(); - return true; - } else if (event->type() == (QEvent::Type)WorkerRemoveEvent::WorkerRemove) { - QMutexLocker locker(&m_lock); - WorkerRemoveEvent *workerEvent = static_cast(event); - QHash::iterator itr = workers.find(workerEvent->workerId()); - if (itr != workers.end()) { - delete itr.value(); - workers.erase(itr); - } - return true; - } else { - return QObject::event(event); - } -} - -void QQuickWorkerScriptEnginePrivate::processMessage(int id, const QByteArray &data) -{ - WorkerScript *script = workers.value(id); - if (!script) - return; - - QV4::ExecutionEngine *v4 = QV8Engine::getV4(script); - QV4::Scope scope(v4); - 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::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); - } -} - -void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url) -{ - if (url.isRelative()) - return; - - QString fileName = QQmlFile::urlToLocalFileOrQrc(url); - - WorkerScript *script = workers.value(id); - if (!script) - return; - - QV4::ExecutionEngine *v4 = QV8Engine::getV4(script); - - script->source = url; - - 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 { - QString error; - QV4::Scope scope(v4); - QScopedPointer program; - program.reset(QV4::Script::createFromFileOrCache(v4, /*qmlContext*/nullptr, fileName, url, &error)); - if (program.isNull()) { - if (!error.isEmpty()) - qWarning().nospace() << error; - return; - } - - if (!v4->hasException) - program->run(); - } - - if (v4->hasException) { - QQmlError error = v4->catchExceptionAsQmlError(); - reportScriptException(script, error); - } -} - -void QQuickWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script, - const QQmlError &error) -{ - QMutexLocker locker(&script->p->m_lock); - if (script->owner) - QCoreApplication::postEvent(script->owner, new WorkerErrorEvent(error)); -} - -WorkerDataEvent::WorkerDataEvent(int workerId, const QByteArray &data) -: QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data) -{ -} - -WorkerDataEvent::~WorkerDataEvent() -{ -} - -int WorkerDataEvent::workerId() const -{ - return m_id; -} - -QByteArray WorkerDataEvent::data() const -{ - return m_data; -} - -WorkerLoadEvent::WorkerLoadEvent(int workerId, const QUrl &url) -: QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url) -{ -} - -int WorkerLoadEvent::workerId() const -{ - return m_id; -} - -QUrl WorkerLoadEvent::url() const -{ - return m_url; -} - -WorkerRemoveEvent::WorkerRemoveEvent(int workerId) -: QEvent((QEvent::Type)WorkerRemove), m_id(workerId) -{ -} - -int WorkerRemoveEvent::workerId() const -{ - return m_id; -} - -WorkerErrorEvent::WorkerErrorEvent(const QQmlError &error) -: QEvent((QEvent::Type)WorkerError), m_error(error) -{ -} - -QQmlError WorkerErrorEvent::error() const -{ - return m_error; -} - -QQuickWorkerScriptEngine::QQuickWorkerScriptEngine(QQmlEngine *parent) -: QThread(parent), d(new QQuickWorkerScriptEnginePrivate(parent)) -{ - d->m_lock.lock(); - connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection); - start(QThread::LowestPriority); - d->m_wait.wait(&d->m_lock); - d->moveToThread(this); - d->m_lock.unlock(); -} - -QQuickWorkerScriptEngine::~QQuickWorkerScriptEngine() -{ - d->m_lock.lock(); - QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QQuickWorkerScriptEnginePrivate::WorkerDestroyEvent)); - d->m_lock.unlock(); - - //We have to force to cleanup the main thread's event queue here - //to make sure the main GUI release all pending locks/wait conditions which - //some worker script/agent are waiting for (QQmlListModelWorkerAgent::sync() for example). - while (!isFinished()) { - // We can't simply wait here, because the worker thread will not terminate - // until the main thread processes the last data event it generates - QCoreApplication::processEvents(); - yieldCurrentThread(); - } - - d->deleteLater(); -} - -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(d->m_nextId++, d); - - script->owner = owner; - - d->m_lock.lock(); - d->workers.insert(script->id, script); - d->m_lock.unlock(); - - return script->id; -} - -void QQuickWorkerScriptEngine::removeWorkerScript(int id) -{ - QQuickWorkerScriptEnginePrivate::WorkerScript* script = d->workers.value(id); - if (script) { - script->owner = nullptr; - QCoreApplication::postEvent(d, new WorkerRemoveEvent(id)); - } -} - -void QQuickWorkerScriptEngine::executeUrl(int id, const QUrl &url) -{ - QCoreApplication::postEvent(d, new WorkerLoadEvent(id, url)); -} - -void QQuickWorkerScriptEngine::sendMessage(int id, const QByteArray &data) -{ - QCoreApplication::postEvent(d, new WorkerDataEvent(id, data)); -} - -void QQuickWorkerScriptEngine::run() -{ - d->m_lock.lock(); - - d->m_wait.wakeAll(); - - d->m_lock.unlock(); - - exec(); - - qDeleteAll(d->workers); - d->workers.clear(); -} - - -/*! - \qmltype WorkerScript - \instantiates QQuickWorkerScript - \ingroup qtquick-threading - \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 - that the main GUI thread is not blocked. - - Messages can be passed between the new thread and the parent thread - using \l sendMessage() and the \c onMessage() handler. - - An example: - - \snippet qml/workerscript/workerscript.qml 0 - - 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.mjs - - When the user clicks anywhere within the rectangle, \c sendMessage() is - called, triggering the \tt WorkerScript.onMessage() handler in - \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.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. - - 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 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(nullptr), m_scriptId(-1), m_componentComplete(true) -{ -} - -QQuickWorkerScript::~QQuickWorkerScript() -{ - if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId); -} - -/*! - \qmlproperty url WorkerScript::source - - 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 -{ - return m_source; -} - -void QQuickWorkerScript::setSource(const QUrl &source) -{ - if (m_source == source) - return; - - m_source = source; - - if (engine()) - m_engine->executeUrl(m_scriptId, m_source); - - emit sourceChanged(); -} - -/*! - \qmlmethod WorkerScript::sendMessage(jsobject message) - - Sends the given \a message to a worker script handler in another - thread. The other worker script handler can receive this message - through the onMessage() handler. - - The \c message object may only contain values of the following - types: - - \list - \li boolean, number, string - \li JavaScript objects and arrays - \li ListModel objects (any other type of QObject* is not allowed) - \endlist - - All objects and arrays are copied to the \c message. With the exception - of ListModel objects, any modifications by the other thread to an object - passed in \c message will not be reflected in the original object. -*/ -void QQuickWorkerScript::sendMessage(QQmlV4Function *args) -{ - if (!engine()) { - qWarning("QQuickWorkerScript: Attempt to send message before WorkerScript establishment"); - return; - } - - QV4::Scope scope(args->v4engine()); - QV4::ScopedValue argument(scope, QV4::Value::undefinedValue()); - if (args->length() != 0) - argument = (*args)[0]; - - m_engine->sendMessage(m_scriptId, QV4::Serialize::serialize(argument, scope.engine)); -} - -void QQuickWorkerScript::classBegin() -{ - m_componentComplete = false; -} - -QQuickWorkerScriptEngine *QQuickWorkerScript::engine() -{ - if (m_engine) return m_engine; - if (m_componentComplete) { - QQmlEngine *engine = qmlEngine(this); - if (!engine) { - qWarning("QQuickWorkerScript: engine() called without qmlEngine() set"); - return nullptr; - } - - m_engine = QQmlEnginePrivate::get(engine)->getWorkerScriptEngine(); - m_scriptId = m_engine->registerWorkerScript(this); - - if (m_source.isValid()) - m_engine->executeUrl(m_scriptId, m_source); - - return m_engine; - } - return nullptr; -} - -void QQuickWorkerScript::componentComplete() -{ - m_componentComplete = true; - engine(); // Get it started now. -} - -/*! - \qmlsignal WorkerScript::message(jsobject msg) - - This signal is emitted when a message \a msg is received from a worker - script in another thread through a call to sendMessage(). - - The corresponding handler is \c onMessage. -*/ - -bool QQuickWorkerScript::event(QEvent *event) -{ - if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) { - if (QQmlEngine *engine = qmlEngine(this)) { - QV4::ExecutionEngine *v4 = engine->handle(); - WorkerDataEvent *workerEvent = static_cast(event); - emit message(QJSValue(v4, QV4::Serialize::deserialize(workerEvent->data(), v4))); - } - return true; - } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) { - WorkerErrorEvent *workerEvent = static_cast(event); - QQmlEnginePrivate::warning(qmlEngine(this), workerEvent->error()); - return true; - } else { - return QObject::event(event); - } -} - -QT_END_NAMESPACE - -#include - -#include "moc_qquickworkerscript_p.cpp" diff --git a/src/qml/types/qquickworkerscript_p.h b/src/qml/types/qquickworkerscript_p.h deleted file mode 100644 index 87cf2e9754..0000000000 --- a/src/qml/types/qquickworkerscript_p.h +++ /dev/null @@ -1,123 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QQUICKWORKERSCRIPT_P_H -#define QQUICKWORKERSCRIPT_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - - -class QQuickWorkerScript; -class QQuickWorkerScriptEnginePrivate; -class QQuickWorkerScriptEngine : public QThread -{ -Q_OBJECT -public: - QQuickWorkerScriptEngine(QQmlEngine *parent = nullptr); - ~QQuickWorkerScriptEngine(); - - int registerWorkerScript(QQuickWorkerScript *); - void removeWorkerScript(int); - void executeUrl(int, const QUrl &); - void sendMessage(int, const QByteArray &); - -protected: - void run() override; - -private: - QQuickWorkerScriptEnginePrivate *d; -}; - -class QQmlV4Function; -class Q_AUTOTEST_EXPORT QQuickWorkerScript : public QObject, public QQmlParserStatus -{ - Q_OBJECT - Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) - - Q_INTERFACES(QQmlParserStatus) -public: - QQuickWorkerScript(QObject *parent = nullptr); - ~QQuickWorkerScript(); - - QUrl source() const; - void setSource(const QUrl &); - -public Q_SLOTS: - void sendMessage(QQmlV4Function*); - -Q_SIGNALS: - void sourceChanged(); - void message(const QJSValue &messageObject); - -protected: - void classBegin() override; - void componentComplete() override; - bool event(QEvent *) override; - -private: - QQuickWorkerScriptEngine *engine(); - QQuickWorkerScriptEngine *m_engine; - int m_scriptId; - QUrl m_source; - bool m_componentComplete; -}; - -QT_END_NAMESPACE - -QML_DECLARE_TYPE(QQuickWorkerScript) - -#endif // QQUICKWORKERSCRIPT_P_H diff --git a/src/qml/types/types.pri b/src/qml/types/types.pri index ba11271d66..c50273071b 100644 --- a/src/qml/types/types.pri +++ b/src/qml/types/types.pri @@ -8,13 +8,6 @@ HEADERS += \ $$PWD/qqmlconnections_p.h \ $$PWD/qqmlmodelindexvaluetype_p.h -qtConfig(qml-worker-script) { - SOURCES += \ - $$PWD/qquickworkerscript.cpp - HEADERS += \ - $$PWD/qquickworkerscript_p.h -} - qtConfig(qml-animation) { SOURCES += \ $$PWD/qqmltimer.cpp diff --git a/src/qmlworkerscript/qmlworkerscript.pro b/src/qmlworkerscript/qmlworkerscript.pro new file mode 100644 index 0000000000..908caa4ed4 --- /dev/null +++ b/src/qmlworkerscript/qmlworkerscript.pro @@ -0,0 +1,20 @@ +TARGET = QtQmlWorkerScript +QT = core-private qml-private + +DEFINES += QT_NO_URL_CAST_FROM_STRING QT_NO_INTEGER_EVENT_COORDINATES QT_NO_FOREACH + +HEADERS += \ + qqmlworkerscriptmodule_p.h \ + qquickworkerscript_p.h \ + qtqmlworkerscriptglobal.h \ + qtqmlworkerscriptglobal_p.h \ + qv4serialize_p.h + +SOURCES += \ + qqmlworkerscriptmodule.cpp \ + qquickworkerscript.cpp \ + qv4serialize.cpp + +include(../3rdparty/masm/masm-defs.pri) + +load(qt_module) diff --git a/src/qmlworkerscript/qqmlworkerscriptmodule.cpp b/src/qmlworkerscript/qqmlworkerscriptmodule.cpp new file mode 100644 index 0000000000..98e82dbeba --- /dev/null +++ b/src/qmlworkerscript/qqmlworkerscriptmodule.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlworkerscriptmodule_p.h" +#include "qquickworkerscript_p.h" + +QT_BEGIN_NAMESPACE + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + +void QQmlWorkerScriptModule::registerQuickTypes() +{ + // Don't add anything here. These are only for backwards compatibility. + const char uri[] = "QtQuick"; + qmlRegisterType(uri, 2, 0, "WorkerScript"); +} + +#endif // QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + +void QQmlWorkerScriptModule::defineModule() +{ + const char uri[] = "QtQml.WorkerScript"; + qmlRegisterType(uri, 2, 0, "WorkerScript"); +} + +QT_END_NAMESPACE diff --git a/src/qmlworkerscript/qqmlworkerscriptmodule_p.h b/src/qmlworkerscript/qqmlworkerscriptmodule_p.h new file mode 100644 index 0000000000..a2efb304c1 --- /dev/null +++ b/src/qmlworkerscript/qqmlworkerscriptmodule_p.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLWORKERSCRIPTMODULE_P_H +#define QQMLWORKERSCRIPTMODULE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_BEGIN_NAMESPACE + +class Q_QMLWORKERSCRIPT_PRIVATE_EXPORT QQmlWorkerScriptModule +{ +public: +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + static void registerQuickTypes(); +#endif + static void defineModule(); +}; + +QT_END_NAMESPACE + +#endif // QQMLWORKERSCRIPTMODULE_P_H diff --git a/src/qmlworkerscript/qquickworkerscript.cpp b/src/qmlworkerscript/qquickworkerscript.cpp new file mode 100644 index 0000000000..273ec2a7a6 --- /dev/null +++ b/src/qmlworkerscript/qquickworkerscript.cpp @@ -0,0 +1,673 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtqmlworkerscriptglobal_p.h" +#include "qquickworkerscript_p.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_CONFIG(qml_network) +#include +#include "qqmlnetworkaccessmanagerfactory.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class WorkerDataEvent : public QEvent +{ +public: + enum Type { WorkerData = QEvent::User }; + + WorkerDataEvent(int workerId, const QByteArray &data); + virtual ~WorkerDataEvent(); + + int workerId() const; + QByteArray data() const; + +private: + int m_id; + QByteArray m_data; +}; + +class WorkerLoadEvent : public QEvent +{ +public: + enum Type { WorkerLoad = WorkerDataEvent::WorkerData + 1 }; + + WorkerLoadEvent(int workerId, const QUrl &url); + + int workerId() const; + QUrl url() const; + +private: + int m_id; + QUrl m_url; +}; + +class WorkerRemoveEvent : public QEvent +{ +public: + enum Type { WorkerRemove = WorkerLoadEvent::WorkerLoad + 1 }; + + WorkerRemoveEvent(int workerId); + + int workerId() const; + +private: + int m_id; +}; + +class WorkerErrorEvent : public QEvent +{ +public: + enum Type { WorkerError = WorkerRemoveEvent::WorkerRemove + 1 }; + + WorkerErrorEvent(const QQmlError &error); + + QQmlError error() const; + +private: + QQmlError m_error; +}; + +class QQuickWorkerScriptEnginePrivate : public QObject +{ + Q_OBJECT +public: + enum WorkerEventTypes { + WorkerDestroyEvent = QEvent::User + 100 + }; + + QQuickWorkerScriptEnginePrivate(QQmlEngine *eng); + + QQmlEngine *qmlengine; + + QMutex m_lock; + QWaitCondition m_wait; + + struct WorkerScript : public QV8Engine { + WorkerScript(int id, QQuickWorkerScriptEnginePrivate *parent); + ~WorkerScript() override; + +#if QT_CONFIG(qml_network) + QNetworkAccessManager *networkAccessManager() override; +#endif + + QQuickWorkerScriptEnginePrivate *p = nullptr; + QUrl source; + QQuickWorkerScript *owner = nullptr; +#if QT_CONFIG(qml_network) + QScopedPointer accessManager; +#endif + int id = -1; + }; + + QHash workers; + QV4::ReturnedValue getWorker(WorkerScript *); + + int m_nextId; + + static QV4::ReturnedValue method_sendMessage(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc); + +signals: + void stopThread(); + +protected: + bool event(QEvent *) override; + +private: + void processMessage(int, const QByteArray &); + void processLoad(int, const QUrl &); + void reportScriptException(WorkerScript *, const QQmlError &error); +}; + +QQuickWorkerScriptEnginePrivate::QQuickWorkerScriptEnginePrivate(QQmlEngine *engine) +: qmlengine(engine), m_nextId(0) +{ +} + +QV4::ReturnedValue QQuickWorkerScriptEnginePrivate::method_sendMessage(const QV4::FunctionObject *b, + const QV4::Value *, const QV4::Value *argv, int argc) +{ + QV4::Scope scope(b); + WorkerScript *script = static_cast(scope.engine->v8Engine); + + QV4::ScopedValue v(scope, argc > 0 ? argv[0] : QV4::Value::undefinedValue()); + QByteArray data = QV4::Serialize::serialize(v, scope.engine); + + QMutexLocker locker(&script->p->m_lock); + if (script && script->owner) + QCoreApplication::postEvent(script->owner, new WorkerDataEvent(0, data)); + + return QV4::Encode::undefined(); +} + +bool QQuickWorkerScriptEnginePrivate::event(QEvent *event) +{ + if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) { + WorkerDataEvent *workerEvent = static_cast(event); + processMessage(workerEvent->workerId(), workerEvent->data()); + return true; + } else if (event->type() == (QEvent::Type)WorkerLoadEvent::WorkerLoad) { + WorkerLoadEvent *workerEvent = static_cast(event); + processLoad(workerEvent->workerId(), workerEvent->url()); + return true; + } else if (event->type() == (QEvent::Type)WorkerDestroyEvent) { + emit stopThread(); + return true; + } else if (event->type() == (QEvent::Type)WorkerRemoveEvent::WorkerRemove) { + QMutexLocker locker(&m_lock); + WorkerRemoveEvent *workerEvent = static_cast(event); + QHash::iterator itr = workers.find(workerEvent->workerId()); + if (itr != workers.end()) { + delete itr.value(); + workers.erase(itr); + } + return true; + } else { + return QObject::event(event); + } +} + +void QQuickWorkerScriptEnginePrivate::processMessage(int id, const QByteArray &data) +{ + WorkerScript *script = workers.value(id); + if (!script) + return; + + QV4::ExecutionEngine *v4 = QV8Engine::getV4(script); + QV4::Scope scope(v4); + 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::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); + } +} + +void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url) +{ + if (url.isRelative()) + return; + + QString fileName = QQmlFile::urlToLocalFileOrQrc(url); + + WorkerScript *script = workers.value(id); + if (!script) + return; + + QV4::ExecutionEngine *v4 = QV8Engine::getV4(script); + + script->source = url; + + 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 { + QString error; + QV4::Scope scope(v4); + QScopedPointer program; + program.reset(QV4::Script::createFromFileOrCache(v4, /*qmlContext*/nullptr, fileName, url, &error)); + if (program.isNull()) { + if (!error.isEmpty()) + qWarning().nospace() << error; + return; + } + + if (!v4->hasException) + program->run(); + } + + if (v4->hasException) { + QQmlError error = v4->catchExceptionAsQmlError(); + reportScriptException(script, error); + } +} + +void QQuickWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script, + const QQmlError &error) +{ + QMutexLocker locker(&script->p->m_lock); + if (script->owner) + QCoreApplication::postEvent(script->owner, new WorkerErrorEvent(error)); +} + +WorkerDataEvent::WorkerDataEvent(int workerId, const QByteArray &data) +: QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data) +{ +} + +WorkerDataEvent::~WorkerDataEvent() +{ +} + +int WorkerDataEvent::workerId() const +{ + return m_id; +} + +QByteArray WorkerDataEvent::data() const +{ + return m_data; +} + +WorkerLoadEvent::WorkerLoadEvent(int workerId, const QUrl &url) +: QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url) +{ +} + +int WorkerLoadEvent::workerId() const +{ + return m_id; +} + +QUrl WorkerLoadEvent::url() const +{ + return m_url; +} + +WorkerRemoveEvent::WorkerRemoveEvent(int workerId) +: QEvent((QEvent::Type)WorkerRemove), m_id(workerId) +{ +} + +int WorkerRemoveEvent::workerId() const +{ + return m_id; +} + +WorkerErrorEvent::WorkerErrorEvent(const QQmlError &error) +: QEvent((QEvent::Type)WorkerError), m_error(error) +{ +} + +QQmlError WorkerErrorEvent::error() const +{ + return m_error; +} + +QQuickWorkerScriptEngine::QQuickWorkerScriptEngine(QQmlEngine *parent) +: QThread(parent), d(new QQuickWorkerScriptEnginePrivate(parent)) +{ + d->m_lock.lock(); + connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection); + start(QThread::LowestPriority); + d->m_wait.wait(&d->m_lock); + d->moveToThread(this); + d->m_lock.unlock(); +} + +QQuickWorkerScriptEngine::~QQuickWorkerScriptEngine() +{ + d->m_lock.lock(); + QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QQuickWorkerScriptEnginePrivate::WorkerDestroyEvent)); + d->m_lock.unlock(); + + //We have to force to cleanup the main thread's event queue here + //to make sure the main GUI release all pending locks/wait conditions which + //some worker script/agent are waiting for (QQmlListModelWorkerAgent::sync() for example). + while (!isFinished()) { + // We can't simply wait here, because the worker thread will not terminate + // until the main thread processes the last data event it generates + QCoreApplication::processEvents(); + yieldCurrentThread(); + } + + d->deleteLater(); +} + +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(d->m_nextId++, d); + + script->owner = owner; + + d->m_lock.lock(); + d->workers.insert(script->id, script); + d->m_lock.unlock(); + + return script->id; +} + +void QQuickWorkerScriptEngine::removeWorkerScript(int id) +{ + QQuickWorkerScriptEnginePrivate::WorkerScript* script = d->workers.value(id); + if (script) { + script->owner = nullptr; + QCoreApplication::postEvent(d, new WorkerRemoveEvent(id)); + } +} + +void QQuickWorkerScriptEngine::executeUrl(int id, const QUrl &url) +{ + QCoreApplication::postEvent(d, new WorkerLoadEvent(id, url)); +} + +void QQuickWorkerScriptEngine::sendMessage(int id, const QByteArray &data) +{ + QCoreApplication::postEvent(d, new WorkerDataEvent(id, data)); +} + +void QQuickWorkerScriptEngine::run() +{ + d->m_lock.lock(); + + d->m_wait.wakeAll(); + + d->m_lock.unlock(); + + exec(); + + qDeleteAll(d->workers); + d->workers.clear(); +} + + +/*! + \qmltype WorkerScript + \instantiates QQuickWorkerScript + \ingroup qtquick-threading + \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 + that the main GUI thread is not blocked. + + Messages can be passed between the new thread and the parent thread + using \l sendMessage() and the \c onMessage() handler. + + An example: + + \snippet qml/workerscript/workerscript.qml 0 + + 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.mjs + + When the user clicks anywhere within the rectangle, \c sendMessage() is + called, triggering the \tt WorkerScript.onMessage() handler in + \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.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. + + 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 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(nullptr), m_scriptId(-1), m_componentComplete(true) +{ +} + +QQuickWorkerScript::~QQuickWorkerScript() +{ + if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId); +} + +/*! + \qmlproperty url WorkerScript::source + + 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 +{ + return m_source; +} + +void QQuickWorkerScript::setSource(const QUrl &source) +{ + if (m_source == source) + return; + + m_source = source; + + if (engine()) + m_engine->executeUrl(m_scriptId, m_source); + + emit sourceChanged(); +} + +/*! + \qmlmethod WorkerScript::sendMessage(jsobject message) + + Sends the given \a message to a worker script handler in another + thread. The other worker script handler can receive this message + through the onMessage() handler. + + The \c message object may only contain values of the following + types: + + \list + \li boolean, number, string + \li JavaScript objects and arrays + \li ListModel objects (any other type of QObject* is not allowed) + \endlist + + All objects and arrays are copied to the \c message. With the exception + of ListModel objects, any modifications by the other thread to an object + passed in \c message will not be reflected in the original object. +*/ +void QQuickWorkerScript::sendMessage(QQmlV4Function *args) +{ + if (!engine()) { + qWarning("QQuickWorkerScript: Attempt to send message before WorkerScript establishment"); + return; + } + + QV4::Scope scope(args->v4engine()); + QV4::ScopedValue argument(scope, QV4::Value::undefinedValue()); + if (args->length() != 0) + argument = (*args)[0]; + + m_engine->sendMessage(m_scriptId, QV4::Serialize::serialize(argument, scope.engine)); +} + +void QQuickWorkerScript::classBegin() +{ + m_componentComplete = false; +} + +QQuickWorkerScriptEngine *QQuickWorkerScript::engine() +{ + if (m_engine) return m_engine; + if (m_componentComplete) { + QQmlEngine *engine = qmlEngine(this); + if (!engine) { + qWarning("QQuickWorkerScript: engine() called without qmlEngine() set"); + return nullptr; + } + + QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine); + if (enginePrivate->workerScriptEngine == nullptr) + enginePrivate->workerScriptEngine = new QQuickWorkerScriptEngine(engine); + m_engine = qobject_cast(enginePrivate->workerScriptEngine); + Q_ASSERT(m_engine); + m_scriptId = m_engine->registerWorkerScript(this); + + if (m_source.isValid()) + m_engine->executeUrl(m_scriptId, m_source); + + return m_engine; + } + return nullptr; +} + +void QQuickWorkerScript::componentComplete() +{ + m_componentComplete = true; + engine(); // Get it started now. +} + +/*! + \qmlsignal WorkerScript::message(jsobject msg) + + This signal is emitted when a message \a msg is received from a worker + script in another thread through a call to sendMessage(). + + The corresponding handler is \c onMessage. +*/ + +bool QQuickWorkerScript::event(QEvent *event) +{ + if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) { + if (QQmlEngine *engine = qmlEngine(this)) { + QV4::ExecutionEngine *v4 = engine->handle(); + WorkerDataEvent *workerEvent = static_cast(event); + emit message(QJSValue(v4, QV4::Serialize::deserialize(workerEvent->data(), v4))); + } + return true; + } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) { + WorkerErrorEvent *workerEvent = static_cast(event); + QQmlEnginePrivate::warning(qmlEngine(this), workerEvent->error()); + return true; + } else { + return QObject::event(event); + } +} + +QT_END_NAMESPACE + +#include + +#include "moc_qquickworkerscript_p.cpp" diff --git a/src/qmlworkerscript/qquickworkerscript_p.h b/src/qmlworkerscript/qquickworkerscript_p.h new file mode 100644 index 0000000000..87cf2e9754 --- /dev/null +++ b/src/qmlworkerscript/qquickworkerscript_p.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKWORKERSCRIPT_P_H +#define QQUICKWORKERSCRIPT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + + +class QQuickWorkerScript; +class QQuickWorkerScriptEnginePrivate; +class QQuickWorkerScriptEngine : public QThread +{ +Q_OBJECT +public: + QQuickWorkerScriptEngine(QQmlEngine *parent = nullptr); + ~QQuickWorkerScriptEngine(); + + int registerWorkerScript(QQuickWorkerScript *); + void removeWorkerScript(int); + void executeUrl(int, const QUrl &); + void sendMessage(int, const QByteArray &); + +protected: + void run() override; + +private: + QQuickWorkerScriptEnginePrivate *d; +}; + +class QQmlV4Function; +class Q_AUTOTEST_EXPORT QQuickWorkerScript : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + + Q_INTERFACES(QQmlParserStatus) +public: + QQuickWorkerScript(QObject *parent = nullptr); + ~QQuickWorkerScript(); + + QUrl source() const; + void setSource(const QUrl &); + +public Q_SLOTS: + void sendMessage(QQmlV4Function*); + +Q_SIGNALS: + void sourceChanged(); + void message(const QJSValue &messageObject); + +protected: + void classBegin() override; + void componentComplete() override; + bool event(QEvent *) override; + +private: + QQuickWorkerScriptEngine *engine(); + QQuickWorkerScriptEngine *m_engine; + int m_scriptId; + QUrl m_source; + bool m_componentComplete; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickWorkerScript) + +#endif // QQUICKWORKERSCRIPT_P_H diff --git a/src/qmlworkerscript/qtqmlworkerscriptglobal.h b/src/qmlworkerscript/qtqmlworkerscriptglobal.h new file mode 100644 index 0000000000..be3ea4e12a --- /dev/null +++ b/src/qmlworkerscript/qtqmlworkerscriptglobal.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTQMLWORKERSCRIPTGLOBAL_H +#define QTQMLWORKERSCRIPTGLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#if !defined(QT_STATIC) +# if defined(QT_BUILD_QMLWORKERSCRIPT_LIB) +# define Q_QMLWORKERSCRIPT_EXPORT Q_DECL_EXPORT +# else +# define Q_QMLWORKERSCRIPT_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_QMLWORKERSCRIPT_EXPORT +#endif + +QT_END_NAMESPACE +#endif // QTQMLWORKERSCRIPTGLOBAL_H diff --git a/src/qmlworkerscript/qtqmlworkerscriptglobal_p.h b/src/qmlworkerscript/qtqmlworkerscriptglobal_p.h new file mode 100644 index 0000000000..34236cd79e --- /dev/null +++ b/src/qmlworkerscript/qtqmlworkerscriptglobal_p.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTQMLWORKERSCRIPTGLOBAL_P_H +#define QTQMLWORKERSCRIPTGLOBAL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#define Q_QMLWORKERSCRIPT_PRIVATE_EXPORT Q_QMLWORKERSCRIPT_EXPORT +#define Q_QMLWORKERSCRIPT_AUTOTEST_EXPORT Q_AUTOTEST_EXPORT + +#endif // QTQMLWORKERSCRIPTGLOBAL_P_H diff --git a/src/qmlworkerscript/qv4serialize.cpp b/src/qmlworkerscript/qv4serialize.cpp new file mode 100644 index 0000000000..a5e62d3e35 --- /dev/null +++ b/src/qmlworkerscript/qv4serialize.cpp @@ -0,0 +1,447 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4serialize_p.h" + +#include +#include +#include +#if QT_CONFIG(qml_sequence_object) +#include +#endif +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace QV4; + +// We allow the following JavaScript types to be passed between the main and +// the secondary thread: +// + undefined +// + null +// + Boolean +// + String +// + Function +// + Array +// + "Simple" Objects +// + Number +// + Date +// + RegExp +// + +enum Type { + WorkerUndefined, + WorkerNull, + WorkerTrue, + WorkerFalse, + WorkerString, + WorkerFunction, + WorkerArray, + WorkerObject, + WorkerInt32, + WorkerUint32, + WorkerNumber, + WorkerDate, + WorkerRegexp, + WorkerListModel, +#if QT_CONFIG(qml_sequence_object) + WorkerSequence +#endif +}; + +static inline quint32 valueheader(Type type, quint32 size = 0) +{ + return quint8(type) << 24 | (size & 0xFFFFFF); +} + +static inline Type headertype(quint32 header) +{ + return (Type)(header >> 24); +} + +static inline quint32 headersize(quint32 header) +{ + return header & 0xFFFFFF; +} + +static inline void push(QByteArray &data, quint32 value) +{ + data.append((const char *)&value, sizeof(quint32)); +} + +static inline void push(QByteArray &data, double value) +{ + data.append((const char *)&value, sizeof(double)); +} + +static inline void push(QByteArray &data, void *ptr) +{ + data.append((const char *)&ptr, sizeof(void *)); +} + +static inline void reserve(QByteArray &data, int extra) +{ + data.reserve(data.size() + extra); +} + +static inline quint32 popUint32(const char *&data) +{ + quint32 rv = *((const quint32 *)data); + data += sizeof(quint32); + return rv; +} + +static inline double popDouble(const char *&data) +{ + double rv = *((const double *)data); + data += sizeof(double); + return rv; +} + +static inline void *popPtr(const char *&data) +{ + void *rv = *((void *const *)data); + data += sizeof(void *); + return rv; +} + +// XXX TODO: Check that worker script is exception safe in the case of +// serialization/deserialization failures + +#define ALIGN(size) (((size) + 3) & ~3) +void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine *engine) +{ + QV4::Scope scope(engine); + + if (v.isEmpty()) { + Q_ASSERT(!"Serialize: got empty value"); + } else if (v.isUndefined()) { + push(data, valueheader(WorkerUndefined)); + } else if (v.isNull()) { + push(data, valueheader(WorkerNull)); + } else if (v.isBoolean()) { + push(data, valueheader(v.booleanValue() == true ? WorkerTrue : WorkerFalse)); + } else if (v.isString()) { + const QString &qstr = v.toQString(); + int length = qstr.length(); + if (length > 0xFFFFFF) { + push(data, valueheader(WorkerUndefined)); + return; + } + int utf16size = ALIGN(length * sizeof(quint16)); + + reserve(data, utf16size + sizeof(quint32)); + push(data, valueheader(WorkerString, length)); + + int offset = data.size(); + data.resize(data.size() + utf16size); + char *buffer = data.data() + offset; + + memcpy(buffer, qstr.constData(), length*sizeof(QChar)); + } else if (v.as()) { + // XXX TODO: Implement passing function objects between the main and + // worker scripts + push(data, valueheader(WorkerUndefined)); + } else if (const QV4::ArrayObject *array = v.as()) { + uint length = array->getLength(); + if (length > 0xFFFFFF) { + push(data, valueheader(WorkerUndefined)); + return; + } + reserve(data, sizeof(quint32) + length * sizeof(quint32)); + push(data, valueheader(WorkerArray, length)); + ScopedValue val(scope); + for (uint ii = 0; ii < length; ++ii) + serialize(data, (val = array->get(ii)), engine); + } else if (v.isInteger()) { + reserve(data, 2 * sizeof(quint32)); + push(data, valueheader(WorkerInt32)); + push(data, (quint32)v.integerValue()); +// } else if (v.IsUint32()) { +// reserve(data, 2 * sizeof(quint32)); +// push(data, valueheader(WorkerUint32)); +// push(data, v.Uint32Value()); + } else if (v.isNumber()) { + reserve(data, sizeof(quint32) + sizeof(double)); + push(data, valueheader(WorkerNumber)); + push(data, v.asDouble()); + } else if (const QV4::DateObject *d = v.as()) { + reserve(data, sizeof(quint32) + sizeof(double)); + push(data, valueheader(WorkerDate)); + push(data, d->date()); + } else if (const RegExpObject *re = v.as()) { + quint32 flags = re->flags(); + QString pattern = re->source(); + int length = pattern.length() + 1; + if (length > 0xFFFFFF) { + push(data, valueheader(WorkerUndefined)); + return; + } + int utf16size = ALIGN(length * sizeof(quint16)); + + reserve(data, sizeof(quint32) + utf16size); + push(data, valueheader(WorkerRegexp, flags)); + push(data, (quint32)length); + + int offset = data.size(); + data.resize(data.size() + utf16size); + char *buffer = data.data() + offset; + + memcpy(buffer, pattern.constData(), length*sizeof(QChar)); + } else if (const QObjectWrapper *qobjectWrapper = v.as()) { + // XXX TODO: Generalize passing objects between the main thread and worker scripts so + // that others can trivially plug in their elements. + if (QObject *lm = qobjectWrapper->object()) { + if (QObject *agent = qvariant_cast(lm->property("agent"))) { + if (QMetaObject::invokeMethod(agent, "addref")) { + push(data, valueheader(WorkerListModel)); + push(data, (void *)agent); + return; + } + } + } + // No other QObject's are allowed to be sent + push(data, valueheader(WorkerUndefined)); + } else if (const Object *o = v.as()) { +#if QT_CONFIG(qml_sequence_object) + if (o->isListType()) { + // valid sequence. we generate a length (sequence length + 1 for the sequence type) + uint seqLength = ScopedValue(scope, o->get(engine->id_length()))->toUInt32(); + uint length = seqLength + 1; + if (length > 0xFFFFFF) { + push(data, valueheader(WorkerUndefined)); + return; + } + reserve(data, sizeof(quint32) + length * sizeof(quint32)); + push(data, valueheader(WorkerSequence, length)); + serialize(data, QV4::Value::fromInt32(QV4::SequencePrototype::metaTypeForSequence(o)), engine); // sequence type + ScopedValue val(scope); + for (uint ii = 0; ii < seqLength; ++ii) + serialize(data, (val = o->get(ii)), engine); // sequence elements + + return; + } +#endif + + // regular object + QV4::ScopedValue val(scope, v); + QV4::ScopedArrayObject properties(scope, QV4::ObjectPrototype::getOwnPropertyNames(engine, val)); + quint32 length = properties->getLength(); + if (length > 0xFFFFFF) { + push(data, valueheader(WorkerUndefined)); + return; + } + push(data, valueheader(WorkerObject, length)); + + QV4::ScopedValue s(scope); + for (quint32 ii = 0; ii < length; ++ii) { + s = properties->get(ii); + serialize(data, s, engine); + + QV4::String *str = s->as(); + val = o->get(str); + if (scope.hasException()) + scope.engine->catchException(); + + serialize(data, val, engine); + } + return; + } else { + push(data, valueheader(WorkerUndefined)); + } +} + +struct VariantRef +{ + VariantRef() : obj(nullptr) {} + VariantRef(const VariantRef &r) : obj(r.obj) { addref(); } + VariantRef(QObject *a) : obj(a) { addref(); } + ~VariantRef() { release(); } + + VariantRef &operator=(const VariantRef &o) { + o.addref(); + release(); + obj = o.obj; + return *this; + } + + void addref() const + { + if (obj) + QMetaObject::invokeMethod(obj, "addref"); + } + + void release() const + { + if (obj) + QMetaObject::invokeMethod(obj, "release"); + + } + + QObject *obj; +}; + +QT_END_NAMESPACE +Q_DECLARE_METATYPE(VariantRef) +Q_DECLARE_METATYPE(QV4::ExecutionEngine *) +QT_BEGIN_NAMESPACE + +ReturnedValue Serialize::deserialize(const char *&data, ExecutionEngine *engine) +{ + quint32 header = popUint32(data); + Type type = headertype(header); + + Scope scope(engine); + + switch (type) { + case WorkerUndefined: + return QV4::Encode::undefined(); + case WorkerNull: + return QV4::Encode::null(); + case WorkerTrue: + return QV4::Encode(true); + case WorkerFalse: + return QV4::Encode(false); + case WorkerString: + { + quint32 size = headersize(header); + QString qstr((const QChar *)data, size); + data += ALIGN(size * sizeof(quint16)); + return QV4::Encode(engine->newString(qstr)); + } + case WorkerFunction: + Q_ASSERT(!"Unreachable"); + break; + case WorkerArray: + { + quint32 size = headersize(header); + ScopedArrayObject a(scope, engine->newArrayObject()); + ScopedValue v(scope); + for (quint32 ii = 0; ii < size; ++ii) { + v = deserialize(data, engine); + a->put(ii, v); + } + return a.asReturnedValue(); + } + case WorkerObject: + { + quint32 size = headersize(header); + ScopedObject o(scope, engine->newObject()); + ScopedValue name(scope); + ScopedString n(scope); + ScopedValue value(scope); + for (quint32 ii = 0; ii < size; ++ii) { + name = deserialize(data, engine); + value = deserialize(data, engine); + n = name->asReturnedValue(); + o->put(n, value); + } + return o.asReturnedValue(); + } + case WorkerInt32: + return QV4::Encode((qint32)popUint32(data)); + case WorkerUint32: + return QV4::Encode(popUint32(data)); + case WorkerNumber: + return QV4::Encode(popDouble(data)); + case WorkerDate: + return QV4::Encode(engine->newDateObject(QV4::Value::fromDouble(popDouble(data)))); + case WorkerRegexp: + { + quint32 flags = headersize(header); + quint32 length = popUint32(data); + QString pattern = QString((const QChar *)data, length - 1); + data += ALIGN(length * sizeof(quint16)); + return Encode(engine->newRegExpObject(pattern, flags)); + } + case WorkerListModel: + { + QObject *agent = reinterpret_cast(popPtr(data)); + QV4::ScopedValue rv(scope, QV4::QObjectWrapper::wrap(engine, agent)); + // ### Find a better solution then the ugly property + VariantRef ref(agent); + QVariant var = QVariant::fromValue(ref); + QV4::ScopedValue v(scope, scope.engine->fromVariant(var)); + QV4::ScopedString s(scope, engine->newString(QStringLiteral("__qml:hidden:ref"))); + rv->as()->defineReadonlyProperty(s, v); + + QMetaObject::invokeMethod(agent, "release"); + agent->setProperty("engine", QVariant::fromValue(engine)); + return rv->asReturnedValue(); + } +#if QT_CONFIG(qml_sequence_object) + case WorkerSequence: + { + ScopedValue value(scope); + bool succeeded = false; + quint32 length = headersize(header); + quint32 seqLength = length - 1; + value = deserialize(data, engine); + int sequenceType = value->integerValue(); + ScopedArrayObject array(scope, engine->newArrayObject()); + array->arrayReserve(seqLength); + for (quint32 ii = 0; ii < seqLength; ++ii) { + value = deserialize(data, engine); + array->arrayPut(ii, value); + } + array->setArrayLengthUnchecked(seqLength); + QVariant seqVariant = QV4::SequencePrototype::toVariant(array, sequenceType, &succeeded); + return QV4::SequencePrototype::fromVariant(engine, seqVariant, &succeeded); + } +#endif + } + Q_ASSERT(!"Unreachable"); + return QV4::Encode::undefined(); +} + +QByteArray Serialize::serialize(const QV4::Value &value, ExecutionEngine *engine) +{ + QByteArray rv; + serialize(rv, value, engine); + return rv; +} + +ReturnedValue Serialize::deserialize(const QByteArray &data, ExecutionEngine *engine) +{ + const char *stream = data.constData(); + return deserialize(stream, engine); +} + +QT_END_NAMESPACE diff --git a/src/qmlworkerscript/qv4serialize_p.h b/src/qmlworkerscript/qv4serialize_p.h new file mode 100644 index 0000000000..c8700c3ca5 --- /dev/null +++ b/src/qmlworkerscript/qv4serialize_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4SERIALIZE_P_H +#define QV4SERIALIZE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QV4 { + +class Serialize { +public: + + static QByteArray serialize(const Value &, ExecutionEngine *); + static ReturnedValue deserialize(const QByteArray &, ExecutionEngine *); + +private: + static void serialize(QByteArray &, const Value &, ExecutionEngine *); + static ReturnedValue deserialize(const char *&, ExecutionEngine *); +}; + +} + +QT_END_NAMESPACE + +#endif // QV8WORKER_P_H diff --git a/src/src.pro b/src/src.pro index bd634247e7..b1e0a72c9f 100644 --- a/src/src.pro +++ b/src/src.pro @@ -7,6 +7,8 @@ SUBDIRS += \ qml \ qmlmodels +qtConfig(qml-worker-script): \ + SUBDIRS += qmlworkerscript qtHaveModule(gui):qtConfig(qml-animation) { SUBDIRS += \ -- cgit v1.2.3