1 /****************************************************************************
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
40 ****************************************************************************/
42 #include "qquickworkerscript_p.h"
43 #include "qquicklistmodel_p.h"
44 #include "qquicklistmodelworkeragent_p.h"
45 #include "qqmlengine_p.h"
46 #include "qqmlexpression_p.h"
48 #include <QtCore/qcoreevent.h>
49 #include <QtCore/qcoreapplication.h>
50 #include <QtCore/qdebug.h>
51 #include <QtQml/qjsengine.h>
52 #include <QtCore/qmutex.h>
53 #include <QtCore/qwaitcondition.h>
54 #include <QtCore/qfile.h>
55 #include <QtCore/qdatetime.h>
56 #include <QtNetwork/qnetworkaccessmanager.h>
57 #include <QtQml/qqmlinfo.h>
58 #include <QtQml/qqmlfile.h>
59 #include "qqmlnetworkaccessmanagerfactory.h"
61 #include <private/qv8engine_p.h>
62 #include <private/qv8worker_p.h>
66 class WorkerDataEvent : public QEvent
69 enum Type { WorkerData = QEvent::User };
71 WorkerDataEvent(int workerId, const QByteArray &data);
72 virtual ~WorkerDataEvent();
75 QByteArray data() const;
82 class WorkerLoadEvent : public QEvent
85 enum Type { WorkerLoad = WorkerDataEvent::WorkerData + 1 };
87 WorkerLoadEvent(int workerId, const QUrl &url);
97 class WorkerRemoveEvent : public QEvent
100 enum Type { WorkerRemove = WorkerLoadEvent::WorkerLoad + 1 };
102 WorkerRemoveEvent(int workerId);
104 int workerId() const;
110 class WorkerErrorEvent : public QEvent
113 enum Type { WorkerError = WorkerRemoveEvent::WorkerRemove + 1 };
115 WorkerErrorEvent(const QQmlError &error);
117 QQmlError error() const;
123 class QQuickWorkerScriptEnginePrivate : public QObject
127 enum WorkerEventTypes {
128 WorkerDestroyEvent = QEvent::User + 100
131 QQuickWorkerScriptEnginePrivate(QQmlEngine *eng);
133 class WorkerEngine : public QV8Engine
136 WorkerEngine(QQuickWorkerScriptEnginePrivate *parent);
140 virtual QNetworkAccessManager *networkAccessManager();
142 QQuickWorkerScriptEnginePrivate *p;
144 v8::Local<v8::Function> sendFunction(int id);
145 void callOnMessage(v8::Handle<v8::Object> object, v8::Handle<v8::Value> arg);
147 v8::Persistent<v8::Function> onmessage;
148 v8::Persistent<v8::Function> createsend;
149 QNetworkAccessManager *accessManager;
152 WorkerEngine *workerEngine;
153 static QQuickWorkerScriptEnginePrivate *get(QV8Engine *e) {
154 return static_cast<WorkerEngine *>(e)->p;
157 QQmlEngine *qmlengine;
160 QWaitCondition m_wait;
162 struct WorkerScript {
169 QQuickWorkerScript *owner;
170 v8::Persistent<v8::Object> object;
173 QHash<int, WorkerScript *> workers;
174 v8::Handle<v8::Object> getWorker(WorkerScript *);
178 static v8::Handle<v8::Value> sendMessage(const v8::Arguments &args);
184 virtual bool event(QEvent *);
187 void processMessage(int, const QByteArray &);
188 void processLoad(int, const QUrl &);
189 void reportScriptException(WorkerScript *, const QQmlError &error);
192 QQuickWorkerScriptEnginePrivate::WorkerEngine::WorkerEngine(QQuickWorkerScriptEnginePrivate *parent)
193 : QV8Engine(0), p(parent), accessManager(0)
197 QQuickWorkerScriptEnginePrivate::WorkerEngine::~WorkerEngine()
199 qPersistentDispose(createsend);
200 qPersistentDispose(onmessage);
201 delete accessManager;
204 void QQuickWorkerScriptEnginePrivate::WorkerEngine::init()
206 initQmlGlobalObject();
207 #define CALL_ONMESSAGE_SCRIPT \
208 "(function(object, message) { "\
209 "var isfunction = false; "\
211 "isfunction = object.WorkerScript.onMessage instanceof Function; "\
214 "object.WorkerScript.onMessage(message); "\
217 #define SEND_MESSAGE_CREATE_SCRIPT \
218 "(function(method, engine) { "\
219 "return (function(id) { "\
220 "return (function(message) { "\
221 "if (arguments.length) method(engine, id, message); "\
226 v8::HandleScope handle_scope;
227 v8::Context::Scope scope(context());
230 v8::Local<v8::Script> onmessagescript = v8::Script::New(v8::String::New(CALL_ONMESSAGE_SCRIPT));
231 onmessage = qPersistentNew<v8::Function>(v8::Handle<v8::Function>::Cast(onmessagescript->Run()));
234 v8::Local<v8::Script> createsendscript = v8::Script::New(v8::String::New(SEND_MESSAGE_CREATE_SCRIPT));
235 v8::Local<v8::Function> createsendconstructor = v8::Local<v8::Function>::Cast(createsendscript->Run());
237 v8::Handle<v8::Value> args[] = {
238 V8FUNCTION(QQuickWorkerScriptEnginePrivate::sendMessage, this)
240 v8::Local<v8::Value> createsendvalue = createsendconstructor->Call(global(), 1, args);
242 createsend = qPersistentNew<v8::Function>(v8::Handle<v8::Function>::Cast(createsendvalue));
246 // Requires handle and context scope
247 v8::Local<v8::Function> QQuickWorkerScriptEnginePrivate::WorkerEngine::sendFunction(int id)
249 v8::Handle<v8::Value> args[] = { v8::Integer::New(id) };
250 return v8::Local<v8::Function>::Cast(createsend->Call(global(), 1, args));
253 // Requires handle and context scope
254 void QQuickWorkerScriptEnginePrivate::WorkerEngine::callOnMessage(v8::Handle<v8::Object> object,
255 v8::Handle<v8::Value> arg)
257 v8::Handle<v8::Value> args[] = { object, arg };
258 onmessage->Call(global(), 2, args);
261 QNetworkAccessManager *QQuickWorkerScriptEnginePrivate::WorkerEngine::networkAccessManager()
263 if (!accessManager) {
264 if (p->qmlengine && p->qmlengine->networkAccessManagerFactory()) {
265 accessManager = p->qmlengine->networkAccessManagerFactory()->create(p);
267 accessManager = new QNetworkAccessManager(p);
270 return accessManager;
273 QQuickWorkerScriptEnginePrivate::QQuickWorkerScriptEnginePrivate(QQmlEngine *engine)
274 : workerEngine(0), qmlengine(engine), m_nextId(0)
278 v8::Handle<v8::Value> QQuickWorkerScriptEnginePrivate::sendMessage(const v8::Arguments &args)
280 WorkerEngine *engine = (WorkerEngine*)V8ENGINE();
282 int id = args[1]->Int32Value();
284 QByteArray data = QV8Worker::serialize(args[2], engine);
286 QMutexLocker locker(&engine->p->m_lock);
287 WorkerScript *script = engine->p->workers.value(id);
289 return v8::Undefined();
292 QCoreApplication::postEvent(script->owner, new WorkerDataEvent(0, data));
294 return v8::Undefined();
297 // Requires handle scope and context scope
298 v8::Handle<v8::Object> QQuickWorkerScriptEnginePrivate::getWorker(WorkerScript *script)
300 if (!script->initialized) {
301 script->initialized = true;
303 script->object = qPersistentNew<v8::Object>(workerEngine->contextWrapper()->urlScope(script->source));
305 workerEngine->contextWrapper()->setReadOnly(script->object, false);
307 v8::Local<v8::Object> api = v8::Object::New();
308 api->Set(v8::String::New("sendMessage"), workerEngine->sendFunction(script->id));
310 script->object->Set(v8::String::New("WorkerScript"), api);
312 workerEngine->contextWrapper()->setReadOnly(script->object, true);
315 return script->object;
318 bool QQuickWorkerScriptEnginePrivate::event(QEvent *event)
320 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
321 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
322 processMessage(workerEvent->workerId(), workerEvent->data());
324 } else if (event->type() == (QEvent::Type)WorkerLoadEvent::WorkerLoad) {
325 WorkerLoadEvent *workerEvent = static_cast<WorkerLoadEvent *>(event);
326 processLoad(workerEvent->workerId(), workerEvent->url());
328 } else if (event->type() == (QEvent::Type)WorkerDestroyEvent) {
331 } else if (event->type() == (QEvent::Type)WorkerRemoveEvent::WorkerRemove) {
332 WorkerRemoveEvent *workerEvent = static_cast<WorkerRemoveEvent *>(event);
333 workers.remove(workerEvent->workerId());
336 return QObject::event(event);
340 void QQuickWorkerScriptEnginePrivate::processMessage(int id, const QByteArray &data)
342 WorkerScript *script = workers.value(id);
346 v8::HandleScope handle_scope;
347 v8::Context::Scope scope(workerEngine->context());
349 v8::Handle<v8::Value> value = QV8Worker::deserialize(data, workerEngine);
352 workerEngine->callOnMessage(script->object, value);
354 if (tc.HasCaught()) {
356 QQmlExpressionPrivate::exceptionToError(tc.Message(), error);
357 reportScriptException(script, error);
361 void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
363 if (url.isRelative())
366 QString fileName = QQmlFile::urlToLocalFileOrQrc(url);
369 if (f.open(QIODevice::ReadOnly)) {
370 QByteArray data = f.readAll();
371 QString sourceCode = QString::fromUtf8(data);
372 QQmlScript::Parser::extractPragmas(sourceCode);
374 v8::HandleScope handle_scope;
375 v8::Context::Scope scope(workerEngine->context());
377 WorkerScript *script = workers.value(id);
380 script->source = url;
381 v8::Handle<v8::Object> activation = getWorker(script);
382 if (activation.IsEmpty())
386 // workerEngine->baseUrl = url;
389 v8::Local<v8::Script> program = workerEngine->qmlModeCompile(sourceCode, url.toString());
392 program->Run(activation);
394 if (tc.HasCaught()) {
396 QQmlExpressionPrivate::exceptionToError(tc.Message(), error);
397 reportScriptException(script, error);
400 qWarning().nospace() << "WorkerScript: Cannot find source file " << url.toString();
404 void QQuickWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script,
405 const QQmlError &error)
407 QQuickWorkerScriptEnginePrivate *p = QQuickWorkerScriptEnginePrivate::get(workerEngine);
409 QMutexLocker locker(&p->m_lock);
411 QCoreApplication::postEvent(script->owner, new WorkerErrorEvent(error));
414 WorkerDataEvent::WorkerDataEvent(int workerId, const QByteArray &data)
415 : QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data)
419 WorkerDataEvent::~WorkerDataEvent()
423 int WorkerDataEvent::workerId() const
428 QByteArray WorkerDataEvent::data() const
433 WorkerLoadEvent::WorkerLoadEvent(int workerId, const QUrl &url)
434 : QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url)
438 int WorkerLoadEvent::workerId() const
443 QUrl WorkerLoadEvent::url() const
448 WorkerRemoveEvent::WorkerRemoveEvent(int workerId)
449 : QEvent((QEvent::Type)WorkerRemove), m_id(workerId)
453 int WorkerRemoveEvent::workerId() const
458 WorkerErrorEvent::WorkerErrorEvent(const QQmlError &error)
459 : QEvent((QEvent::Type)WorkerError), m_error(error)
463 QQmlError WorkerErrorEvent::error() const
468 QQuickWorkerScriptEngine::QQuickWorkerScriptEngine(QQmlEngine *parent)
469 : QThread(parent), d(new QQuickWorkerScriptEnginePrivate(parent))
472 connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection);
473 start(QThread::LowestPriority);
474 d->m_wait.wait(&d->m_lock);
475 d->moveToThread(this);
479 QQuickWorkerScriptEngine::~QQuickWorkerScriptEngine()
482 QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QQuickWorkerScriptEnginePrivate::WorkerDestroyEvent));
485 //We have to force to cleanup the main thread's event queue here
486 //to make sure the main GUI release all pending locks/wait conditions which
487 //some worker script/agent are waiting for (QQuickListModelWorkerAgent::sync() for example).
488 while (!isFinished()) {
489 // We can't simply wait here, because the worker thread will not terminate
490 // until the main thread processes the last data event it generates
491 QCoreApplication::processEvents();
492 yieldCurrentThread();
498 QQuickWorkerScriptEnginePrivate::WorkerScript::WorkerScript()
499 : id(-1), initialized(false), owner(0)
503 QQuickWorkerScriptEnginePrivate::WorkerScript::~WorkerScript()
505 qPersistentDispose(object);
508 int QQuickWorkerScriptEngine::registerWorkerScript(QQuickWorkerScript *owner)
510 typedef QQuickWorkerScriptEnginePrivate::WorkerScript WorkerScript;
511 WorkerScript *script = new WorkerScript;
513 script->id = d->m_nextId++;
514 script->owner = owner;
517 d->workers.insert(script->id, script);
523 void QQuickWorkerScriptEngine::removeWorkerScript(int id)
525 QQuickWorkerScriptEnginePrivate::WorkerScript* script = d->workers.value(id);
528 QCoreApplication::postEvent(d, new WorkerRemoveEvent(id));
532 void QQuickWorkerScriptEngine::executeUrl(int id, const QUrl &url)
534 QCoreApplication::postEvent(d, new WorkerLoadEvent(id, url));
537 void QQuickWorkerScriptEngine::sendMessage(int id, const QByteArray &data)
539 QCoreApplication::postEvent(d, new WorkerDataEvent(id, data));
542 void QQuickWorkerScriptEngine::run()
546 d->workerEngine = new QQuickWorkerScriptEnginePrivate::WorkerEngine(d);
547 d->workerEngine->init();
555 qDeleteAll(d->workers);
558 delete d->workerEngine; d->workerEngine = 0;
563 \qmltype WorkerScript
564 \instantiates QQuickWorkerScript
565 \ingroup qtquick-threading
566 \inqmlmodule QtQuick 2
567 \brief Enables the use of threads in a Qt Quick application
569 Use WorkerScript to run operations in a new thread.
570 This is useful for running operations in the background so
571 that the main GUI thread is not blocked.
573 Messages can be passed between the new thread and the parent thread
574 using \l sendMessage() and the \l {WorkerScript::onMessage}{onMessage()} handler.
578 \snippet qml/workerscript/workerscript.qml 0
580 The above worker script specifies a JavaScript file, "script.js", that handles
581 the operations to be performed in the new thread. Here is \c script.js:
583 \quotefile qml/workerscript/script.js
585 When the user clicks anywhere within the rectangle, \c sendMessage() is
586 called, triggering the \tt WorkerScript.onMessage() handler in
587 \tt script.js. This in turn sends a reply message that is then received
588 by the \tt onMessage() handler of \tt myWorker.
591 \section3 Restrictions
593 Since the \c WorkerScript.onMessage() function is run in a separate thread, the
594 JavaScript file is evaluated in a context separate from the main QML engine. This means
595 that unlike an ordinary JavaScript file that is imported into QML, the \c script.js
596 in the above example cannot access the properties, methods or other attributes
597 of the QML item, nor can it access any context properties set on the QML object
600 Additionally, there are restrictions on the types of values that can be passed to and
601 from the worker script. See the sendMessage() documentation for details.
603 \sa {declarative/threading/workerscript}{WorkerScript example},
604 {declarative/threading/threadedlistmodel}{Threaded ListModel example}
606 QQuickWorkerScript::QQuickWorkerScript(QObject *parent)
607 : QObject(parent), m_engine(0), m_scriptId(-1), m_componentComplete(true)
611 QQuickWorkerScript::~QQuickWorkerScript()
613 if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId);
617 \qmlproperty url WorkerScript::source
619 This holds the url of the JavaScript file that implements the
620 \tt WorkerScript.onMessage() handler for threaded operations.
622 QUrl QQuickWorkerScript::source() const
627 void QQuickWorkerScript::setSource(const QUrl &source)
629 if (m_source == source)
635 m_engine->executeUrl(m_scriptId, m_source);
637 emit sourceChanged();
641 \qmlmethod WorkerScript::sendMessage(jsobject message)
643 Sends the given \a message to a worker script handler in another
644 thread. The other worker script handler can receive this message
645 through the onMessage() handler.
647 The \c message object may only contain values of the following
651 \li boolean, number, string
652 \li JavaScript objects and arrays
653 \li ListModel objects (any other type of QObject* is not allowed)
656 All objects and arrays are copied to the \c message. With the exception
657 of ListModel objects, any modifications by the other thread to an object
658 passed in \c message will not be reflected in the original object.
660 void QQuickWorkerScript::sendMessage(QQmlV8Function *args)
663 qWarning("QQuickWorkerScript: Attempt to send message before WorkerScript establishment");
667 v8::Handle<v8::Value> argument = v8::Undefined();
668 if (args->Length() != 0)
669 argument = (*args)[0];
671 m_engine->sendMessage(m_scriptId, QV8Worker::serialize(argument, args->engine()));
674 void QQuickWorkerScript::classBegin()
676 m_componentComplete = false;
679 QQuickWorkerScriptEngine *QQuickWorkerScript::engine()
681 if (m_engine) return m_engine;
682 if (m_componentComplete) {
683 QQmlEngine *engine = qmlEngine(this);
685 qWarning("QQuickWorkerScript: engine() called without qmlEngine() set");
689 m_engine = QQmlEnginePrivate::get(engine)->getWorkerScriptEngine();
690 m_scriptId = m_engine->registerWorkerScript(this);
692 if (m_source.isValid())
693 m_engine->executeUrl(m_scriptId, m_source);
700 void QQuickWorkerScript::componentComplete()
702 m_componentComplete = true;
703 engine(); // Get it started now.
707 \qmlsignal WorkerScript::onMessage(jsobject msg)
709 This handler is called when a message \a msg is received from a worker
710 script in another thread through a call to sendMessage().
713 bool QQuickWorkerScript::event(QEvent *event)
715 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
716 QQmlEngine *engine = qmlEngine(this);
718 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
719 QV8Engine *v8engine = QQmlEnginePrivate::get(engine)->v8engine();
720 v8::HandleScope handle_scope;
721 v8::Context::Scope scope(v8engine->context());
722 v8::Handle<v8::Value> value = QV8Worker::deserialize(workerEvent->data(), v8engine);
723 emit message(QQmlV8Handle::fromHandle(value));
726 } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) {
727 WorkerErrorEvent *workerEvent = static_cast<WorkerErrorEvent *>(event);
728 QQmlEnginePrivate::warning(qmlEngine(this), workerEvent->error());
731 return QObject::event(event);
737 #include <qquickworkerscript.moc>