Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquickworkerscript.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include <private/qqmlengine_p.h>
7#include <private/qqmlexpression_p.h>
8#include <private/qjsvalue_p.h>
9
10#include <QtCore/qcoreevent.h>
11#include <QtCore/qcoreapplication.h>
12#include <QtCore/qdebug.h>
13#include <QtQml/qjsengine.h>
14#include <QtCore/qmutex.h>
15#include <QtCore/qwaitcondition.h>
16#include <QtCore/qfile.h>
17#include <QtCore/qdatetime.h>
18#include <QtQml/qqmlinfo.h>
19#include <QtQml/qqmlfile.h>
20#if QT_CONFIG(qml_network)
21#include <QtNetwork/qnetworkaccessmanager.h>
23#endif
24
25#include <private/qv4serialize_p.h>
26
27#include <private/qv4value_p.h>
28#include <private/qv4functionobject_p.h>
29#include <private/qv4script_p.h>
30#include <private/qv4scopedvalue_p.h>
31#include <private/qv4jscall_p.h>
32
34
35class WorkerDataEvent : public QEvent
36{
37public:
39
41 virtual ~WorkerDataEvent();
42
43 int workerId() const;
44 QByteArray data() const;
45
46private:
47 int m_id;
48 QByteArray m_data;
49};
50
51class WorkerLoadEvent : public QEvent
52{
53public:
55
56 WorkerLoadEvent(int workerId, const QUrl &url);
57
58 int workerId() const;
59 QUrl url() const;
60
61private:
62 int m_id;
63 QUrl m_url;
64};
65
67{
68public:
70
72
73 int workerId() const;
74
75private:
76 int m_id;
77};
78
80{
81public:
83
85
86 QQmlError error() const;
87
88private:
89 QQmlError m_error;
90};
91
93{
95 ~WorkerScript() = default;
96
100#if QT_CONFIG(qml_network)
101 QScopedPointer<QNetworkAccessManager> scriptLocalNAM;
102#endif
103};
104
105V4_DEFINE_EXTENSION(WorkerScript, workerScriptExtension);
106
108{
110public:
114
116
118
121
122 // ExecutionEngines are owned by the worker script and created and deleted
123 // in the worker thread. QQuickWorkerScript instances, however, belong to
124 // the main thread. They are only inserted as place holders when creating
125 // the worker script.
126 QHash<int, QBiPointer<QV4::ExecutionEngine, QQuickWorkerScript>> workers;
127
129
130 static QV4::ReturnedValue method_sendMessage(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
132
133signals:
135
136protected:
137 bool event(QEvent *) override;
138
139private:
140 void processMessage(int, const QByteArray &);
141 void processLoad(int, const QUrl &);
142 void reportScriptException(WorkerScript *, const QQmlError &error);
143};
144
149
151 const QV4::Value *, const QV4::Value *argv, int argc)
152{
153 QV4::Scope scope(b);
154 const WorkerScript *script = workerScriptExtension(scope.engine);
155 Q_ASSERT(script);
156
157 QV4::ScopedValue v(scope, argc > 0 ? argv[0] : QV4::Value::undefinedValue());
159
160 QMutexLocker locker(&script->p->m_lock);
161 if (script->owner)
163
164 return QV4::Encode::undefined();
165}
166
168{
170 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
171 processMessage(workerEvent->workerId(), workerEvent->data());
172 return true;
173 } else if (event->type() == (QEvent::Type)WorkerLoadEvent::WorkerLoad) {
174 WorkerLoadEvent *workerEvent = static_cast<WorkerLoadEvent *>(event);
175 processLoad(workerEvent->workerId(), workerEvent->url());
176 return true;
177 } else if (event->type() == (QEvent::Type)WorkerDestroyEvent) {
179 return true;
180 } else if (event->type() == (QEvent::Type)WorkerRemoveEvent::WorkerRemove) {
181 QMutexLocker locker(&m_lock);
182 WorkerRemoveEvent *workerEvent = static_cast<WorkerRemoveEvent *>(event);
183 auto itr = workers.constFind(workerEvent->workerId());
184 if (itr != workers.cend()) {
185 if (itr->isT1())
186 delete itr->asT1();
187 workers.erase(itr);
188 }
189 return true;
190 } else {
191 return QObject::event(event);
192 }
193}
194
196{
197 const auto it = workers.find(id);
198 if (it == workers.end())
199 return nullptr;
200 if (it->isT1())
201 return it->asT1();
202
203 QQuickWorkerScript *owner = it->asT2();
204 auto *engine = new QV4::ExecutionEngine;
205 WorkerScript *script = workerScriptExtension(engine);
206 script->owner = owner;
207 script->p = this;
208 *it = engine;
209 return engine;
210}
211
212void QQuickWorkerScriptEnginePrivate::processMessage(int id, const QByteArray &data)
213{
215 if (!engine)
216 return;
217
218 QV4::Scope scope(engine);
219 QV4::ScopedString v(scope, engine->newString(QStringLiteral("WorkerScript")));
220 QV4::ScopedObject worker(scope, engine->globalObject->get(v));
221 QV4::ScopedFunctionObject onmessage(scope);
222 if (worker)
223 onmessage = worker->get((v = engine->newString(QStringLiteral("onMessage"))));
224
225 if (!onmessage)
226 return;
227
229
230 QV4::JSCallArguments jsCallData(scope, 1);
231 *jsCallData.thisObject = engine->global();
232 jsCallData.args[0] = value;
233 onmessage->call(jsCallData);
234 if (scope.hasException()) {
235 QQmlError error = scope.engine->catchExceptionAsQmlError();
236 WorkerScript *script = workerScriptExtension(engine);
237 reportScriptException(script, error);
238 }
239}
240
241void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
242{
243 if (url.isRelative())
244 return;
245
247
249 if (!engine)
250 return;
251
252 WorkerScript *script = workerScriptExtension(engine);
253 script->source = url;
254
255 if (fileName.endsWith(QLatin1String(".mjs"))) {
256 auto module = engine->loadModule(url);
257 if (module.compiled) {
258 if (module.compiled->instantiate())
259 module.compiled->evaluate();
260 } else if (module.native) {
261 // Nothing to do. There is no global code in a native module.
262 } else {
263 engine->throwError(QStringLiteral("Could not load module file"));
264 }
265 } else {
267 QV4::Scope scope(engine);
268 QScopedPointer<QV4::Script> program;
270 engine, /*qmlContext*/nullptr, fileName, url, &error));
271 if (program.isNull()) {
272 if (!error.isEmpty())
273 qWarning().nospace() << error;
274 return;
275 }
276
277 if (!engine->hasException)
278 program->run();
279 }
280
281 if (engine->hasException)
282 reportScriptException(script, engine->catchExceptionAsQmlError());
283}
284
285void QQuickWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script,
286 const QQmlError &error)
287{
288 QMutexLocker locker(&script->p->m_lock);
289 if (script->owner)
291}
292
294: QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data)
295{
296}
297
301
303{
304 return m_id;
305}
306
308{
309 return m_data;
310}
311
313: QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url)
314{
315}
316
318{
319 return m_id;
320}
321
323{
324 return m_url;
325}
326
328: QEvent((QEvent::Type)WorkerRemove), m_id(workerId)
329{
330}
331
333{
334 return m_id;
335}
336
338: QEvent((QEvent::Type)WorkerError), m_error(error)
339{
340}
341
343{
344 return m_error;
345}
346
348: QThread(parent), d(new QQuickWorkerScriptEnginePrivate(parent))
349{
350 d->m_lock.lock();
351 connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection);
353 d->m_wait.wait(&d->m_lock);
354 d->moveToThread(this);
355 d->m_lock.unlock();
356}
357
359{
360 d->m_lock.lock();
362 d->m_lock.unlock();
363
364 //We have to force to cleanup the main thread's event queue here
365 //to make sure the main GUI release all pending locks/wait conditions which
366 //some worker script/agent are waiting for (QQmlListModelWorkerAgent::sync() for example).
367 while (!isFinished()) {
368 // We can't simply wait here, because the worker thread will not terminate
369 // until the main thread processes the last data event it generates
372 }
373
374 delete d;
375}
376
377
379{
380 engine->initQmlGlobalObject();
381
382 QV4::Scope scope(engine);
384 QV4::ScopedString sendMessageName(scope, engine->newString(QStringLiteral("sendMessage")));
385 QV4::ScopedFunctionObject sendMessage(
387 engine, sendMessageName,
389 api->put(sendMessageName, sendMessage);
390 QV4::ScopedString workerScriptName(scope, engine->newString(QStringLiteral("WorkerScript")));
391 engine->globalObject->put(workerScriptName, api);
392
393#if QT_CONFIG(qml_network)
394 engine->networkAccessManager = [](QV4::ExecutionEngine *engine) {
395 WorkerScript *workerScript = workerScriptExtension(engine);
396 if (workerScript->scriptLocalNAM)
397 return workerScript->scriptLocalNAM.get();
398 if (auto *namFactory = workerScript->p->qmlengine->networkAccessManagerFactory())
399 workerScript->scriptLocalNAM.reset(namFactory->create(workerScript->p));
400 else
401 workerScript->scriptLocalNAM.reset(new QNetworkAccessManager(workerScript->p));
402 return workerScript->scriptLocalNAM.get();
403 };
404#endif // qml_network
405}
406
408{
409 const int id = d->m_nextId++;
410
411 d->m_lock.lock();
412 d->workers.insert(id, owner);
413 d->m_lock.unlock();
414
415 return id;
416}
417
419{
420 const auto it = d->workers.constFind(id);
421 if (it == d->workers.cend())
422 return;
423
424 if (it->isT1()) {
425 QV4::ExecutionEngine *engine = it->asT1();
426 workerScriptExtension(engine)->owner = nullptr;
427 }
429}
430
435
440
442{
443 d->m_lock.lock();
444 d->m_wait.wakeAll();
445 d->m_lock.unlock();
446
447 exec();
448
449 for (auto it = d->workers.begin(), end = d->workers.end(); it != end; ++it) {
450 if (it->isT1())
451 delete it->asT1();
452 }
453
454 d->workers.clear();
455}
456
457
513: QObject(parent), m_engine(nullptr), m_scriptId(-1), m_componentComplete(true)
514{
515}
516
518{
519 if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId);
520}
521
533{
534 return m_source;
535}
536
538{
539 if (m_source == source)
540 return;
541
542 m_source = source;
543
544 if (engine()) {
545 const QQmlContext *context = qmlContext(this);
546 m_engine->executeUrl(m_scriptId, context ? context->resolvedUrl(m_source) : m_source);
547 }
548
550}
551
559{
560 return m_engine != nullptr;
561}
562
584{
585 if (!engine()) {
586 qWarning("QQuickWorkerScript: Attempt to send message before WorkerScript establishment");
587 return;
588 }
589
590 QV4::Scope scope(args->v4engine());
592 if (args->length() != 0)
593 argument = (*args)[0];
594
595 m_engine->sendMessage(m_scriptId, QV4::Serialize::serialize(argument, scope.engine));
596}
597
599{
600 m_componentComplete = false;
601}
602
603QQuickWorkerScriptEngine *QQuickWorkerScript::engine()
604{
605 if (m_engine) return m_engine;
606 if (m_componentComplete) {
607 const QQmlContext *context = qmlContext(this);
608 QQmlEngine *engine = context ? context->engine() : nullptr;
609 if (!engine) {
610 qWarning("QQuickWorkerScript: engine() called without qmlEngine() set");
611 return nullptr;
612 }
613
614 QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(engine);
615 if (enginePrivate->workerScriptEngine == nullptr)
616 enginePrivate->workerScriptEngine = new QQuickWorkerScriptEngine(engine);
617 m_engine = qobject_cast<QQuickWorkerScriptEngine *>(enginePrivate->workerScriptEngine);
618 Q_ASSERT(m_engine);
619 m_scriptId = m_engine->registerWorkerScript(this);
620
621 if (m_source.isValid())
622 m_engine->executeUrl(m_scriptId, context->resolvedUrl(m_source));
623
624 emit readyChanged();
625
626 return m_engine;
627 }
628 return nullptr;
629}
630
632{
633 m_componentComplete = true;
634 engine(); // Get it started now.
635}
636
645{
647 if (QQmlEngine *engine = qmlEngine(this)) {
649 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
651 QV4::Serialize::deserialize(workerEvent->data(), v4)));
652 }
653 return true;
654 } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) {
655 WorkerErrorEvent *workerEvent = static_cast<WorkerErrorEvent *>(event);
656 QQmlEnginePrivate::warning(qmlEngine(this), workerEvent->error());
657 return true;
658 } else {
659 return QObject::event(event);
660 }
661}
662
664
665#include <qquickworkerscript.moc>
666
667#include "moc_qquickworkerscript_p.cpp"
NSData * m_data
\inmodule QtCore
Definition qbytearray.h:57
static void processEvents(QEventLoop::ProcessEventsFlags flags=QEventLoop::AllEvents)
Processes some pending events for the calling thread according to the specified flags.
static void postEvent(QObject *receiver, QEvent *event, int priority=Qt::NormalEventPriority)
\inmodule QtCore
Definition qcoreevent.h:45
Type
This enum type defines the valid event types in Qt.
Definition qcoreevent.h:51
iterator begin()
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first item in the hash.
Definition qhash.h:1212
const_iterator constFind(const Key &key) const noexcept
Definition qhash.h:1299
iterator find(const Key &key)
Returns an iterator pointing to the item with the key in the hash.
Definition qhash.h:1291
iterator erase(const_iterator it)
Definition qhash.h:1233
iterator end() noexcept
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last ...
Definition qhash.h:1216
const_iterator cend() const noexcept
Definition qhash.h:1218
void clear() noexcept(std::is_nothrow_destructible< Node >::value)
Removes all items from the hash and frees up all memory used by it.
Definition qhash.h:951
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1303
QV4::ExecutionEngine * handle() const
Definition qjsengine.h:298
void throwError(const QString &message)
Throws a run-time error (exception) with the given message.
QJSValue globalObject() const
Returns this engine's Global Object.
QJSValue newObject()
Creates a JavaScript object of class Object.
static QJSValue fromReturnedValue(QV4::ReturnedValue d)
Definition qjsvalue_p.h:197
qsizetype length() const noexcept
Definition qlist.h:399
\inmodule QtCore
Definition qmutex.h:313
\inmodule QtCore
Definition qmutex.h:281
void unlock() noexcept
Unlocks the mutex.
Definition qmutex.h:289
void lock() noexcept
Locks the mutex.
Definition qmutex.h:286
The QNetworkAccessManager class allows the application to send network requests and receive replies.
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
virtual bool event(QEvent *event)
This virtual function receives events to an object and should return true if the event e was recogniz...
Definition qobject.cpp:1389
bool moveToThread(QThread *thread QT6_DECL_NEW_OVERLOAD_TAIL)
Changes the thread affinity for this object and its children and returns true on success.
Definition qobject.cpp:1643
The QQmlContext class defines a context within a QML engine.
Definition qqmlcontext.h:25
void warning(const QQmlError &)
static QQmlEnginePrivate * get(QQmlEngine *e)
The QQmlEngine class provides an environment for instantiating QML components.
Definition qqmlengine.h:57
The QQmlError class encapsulates a QML error.
Definition qqmlerror.h:18
static QString urlToLocalFileOrQrc(const QString &)
If url is a local file returns a path suitable for passing to \l{QFile}.
Definition qqmlfile.cpp:742
bool event(QEvent *) override
This virtual function receives events to an object and should return true if the event e was recogniz...
static QV4::ReturnedValue method_sendMessage(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
QV4::ExecutionEngine * workerEngine(int id)
QHash< int, QBiPointer< QV4::ExecutionEngine, QQuickWorkerScript > > workers
void executeUrl(int, const QUrl &)
QQuickWorkerScriptEngine(QQmlEngine *parent=nullptr)
void sendMessage(int, const QByteArray &)
int registerWorkerScript(QQuickWorkerScript *)
void componentComplete() override
Invoked after the root component that caused this instantiation has completed construction.
void sendMessage(QQmlV4FunctionPtr)
\qmlmethod WorkerScript::sendMessage(jsobject message)
bool event(QEvent *) override
\qmlsignal WorkerScript::message(jsobject msg)
QQuickWorkerScript(QObject *parent=nullptr)
\qmltype WorkerScript \instantiates QQuickWorkerScript\inqmlmodule QtQml.WorkerScript
void classBegin() override
Invoked after class creation, but before any properties have been set.
void setSource(const QUrl &)
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool isFinished() const
Definition qthread.cpp:1059
@ LowestPriority
Definition qthread.h:43
static void yieldCurrentThread()
Definition qthread.cpp:1054
int exec()
Definition qthread.cpp:991
void quit()
Definition qthread.cpp:1008
\inmodule QtCore
Definition qurl.h:94
bool isRelative() const
Returns true if the URL is relative; otherwise returns false.
Definition qurl.cpp:2800
bool isValid() const
Returns true if the URL is non-empty and valid; otherwise returns false.
Definition qurl.cpp:1882
static ReturnedValue deserialize(const QByteArray &, ExecutionEngine *)
static QByteArray serialize(const Value &, ExecutionEngine *)
bool wait(QMutex *, QDeadlineTimer=QDeadlineTimer(QDeadlineTimer::Forever))
QByteArray data() const
WorkerDataEvent(int workerId, const QByteArray &data)
WorkerErrorEvent(const QQmlError &error)
QQmlError error() const
WorkerLoadEvent(int workerId, const QUrl &url)
WorkerRemoveEvent(int workerId)
QSet< QString >::iterator it
Combined button and popup list for selecting options.
Scoped< FunctionObject > ScopedFunctionObject
quint64 ReturnedValue
Scoped< String > ScopedString
@ DirectConnection
static void * context
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qWarning
Definition qlogging.h:166
#define SLOT(a)
Definition qobjectdefs.h:52
#define SIGNAL(a)
Definition qobjectdefs.h:53
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLuint GLuint end
GLenum GLuint id
[7]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint program
GLuint GLsizei const GLchar * message
GLuint start
GLsizei GLsizei GLchar * source
struct _cl_event * event
GLfloat GLfloat p
[1]
QQmlEngine * qmlEngine(const QObject *obj)
Definition qqml.cpp:80
QQmlContext * qmlContext(const QObject *obj)
Definition qqml.cpp:75
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define QStringLiteral(str)
#define Q_OBJECT
#define signals
#define emit
#define V4_DEFINE_EXTENSION(dataclass, datafunction)
Definition qv4engine_p.h:38
QUrl url("example.com")
[constructor-url-reference]
QObject::connect nullptr
QNetworkRequestFactory api
[0]
QDBusArgument argument
QJSValueList args
QJSEngine engine
[0]
static constexpr ReturnedValue undefined()
static Heap::FunctionObject * createBuiltinFunction(ExecutionEngine *engine, StringOrSymbol *nameOrSymbol, VTable::Call code, int argumentCount)
ExecutionEngine * engine
static Script * createFromFileOrCache(ExecutionEngine *engine, QmlContext *qmlContext, const QString &fileName, const QUrl &originalUrl, QString *error)
static constexpr Value undefinedValue()
Definition qv4value_p.h:191
Definition moc.h:23
~WorkerScript()=default
QQuickWorkerScriptEnginePrivate * p
QQuickWorkerScript * owner
WorkerScript(QV4::ExecutionEngine *)