1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
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 "qqmlnetworkaccessmanagerfactory.h"
60 #include <private/qv8engine_p.h>
61 #include <private/qv8worker_p.h>
65 class WorkerDataEvent : public QEvent
68 enum Type { WorkerData = QEvent::User };
70 WorkerDataEvent(int workerId, const QByteArray &data);
71 virtual ~WorkerDataEvent();
74 QByteArray data() const;
81 class WorkerLoadEvent : public QEvent
84 enum Type { WorkerLoad = WorkerDataEvent::WorkerData + 1 };
86 WorkerLoadEvent(int workerId, const QUrl &url);
96 class WorkerRemoveEvent : public QEvent
99 enum Type { WorkerRemove = WorkerLoadEvent::WorkerLoad + 1 };
101 WorkerRemoveEvent(int workerId);
103 int workerId() const;
109 class WorkerErrorEvent : public QEvent
112 enum Type { WorkerError = WorkerRemoveEvent::WorkerRemove + 1 };
114 WorkerErrorEvent(const QQmlError &error);
116 QQmlError error() const;
122 class QQuickWorkerScriptEnginePrivate : public QObject
126 enum WorkerEventTypes {
127 WorkerDestroyEvent = QEvent::User + 100
130 QQuickWorkerScriptEnginePrivate(QQmlEngine *eng);
132 class WorkerEngine : public QV8Engine
135 WorkerEngine(QQuickWorkerScriptEnginePrivate *parent);
139 virtual QNetworkAccessManager *networkAccessManager();
141 QQuickWorkerScriptEnginePrivate *p;
143 v8::Local<v8::Function> sendFunction(int id);
144 void callOnMessage(v8::Handle<v8::Object> object, v8::Handle<v8::Value> arg);
146 v8::Persistent<v8::Function> onmessage;
147 v8::Persistent<v8::Function> createsend;
148 QNetworkAccessManager *accessManager;
151 WorkerEngine *workerEngine;
152 static QQuickWorkerScriptEnginePrivate *get(QV8Engine *e) {
153 return static_cast<WorkerEngine *>(e)->p;
156 QQmlEngine *qmlengine;
159 QWaitCondition m_wait;
161 struct WorkerScript {
168 QQuickWorkerScript *owner;
169 v8::Persistent<v8::Object> object;
172 QHash<int, WorkerScript *> workers;
173 v8::Handle<v8::Object> getWorker(WorkerScript *);
177 static v8::Handle<v8::Value> sendMessage(const v8::Arguments &args);
183 virtual bool event(QEvent *);
186 void processMessage(int, const QByteArray &);
187 void processLoad(int, const QUrl &);
188 void reportScriptException(WorkerScript *, const QQmlError &error);
191 QQuickWorkerScriptEnginePrivate::WorkerEngine::WorkerEngine(QQuickWorkerScriptEnginePrivate *parent)
192 : QV8Engine(0), p(parent), accessManager(0)
196 QQuickWorkerScriptEnginePrivate::WorkerEngine::~WorkerEngine()
198 qPersistentDispose(createsend);
199 qPersistentDispose(onmessage);
200 delete accessManager;
203 void QQuickWorkerScriptEnginePrivate::WorkerEngine::init()
205 initDeclarativeGlobalObject();
206 #define CALL_ONMESSAGE_SCRIPT \
207 "(function(object, message) { "\
208 "var isfunction = false; "\
210 "isfunction = object.WorkerScript.onMessage instanceof Function; "\
213 "object.WorkerScript.onMessage(message); "\
216 #define SEND_MESSAGE_CREATE_SCRIPT \
217 "(function(method, engine) { "\
218 "return (function(id) { "\
219 "return (function(message) { "\
220 "if (arguments.length) method(engine, id, message); "\
225 v8::HandleScope handle_scope;
226 v8::Context::Scope scope(context());
229 v8::Local<v8::Script> onmessagescript = v8::Script::New(v8::String::New(CALL_ONMESSAGE_SCRIPT));
230 onmessage = qPersistentNew<v8::Function>(v8::Handle<v8::Function>::Cast(onmessagescript->Run()));
233 v8::Local<v8::Script> createsendscript = v8::Script::New(v8::String::New(SEND_MESSAGE_CREATE_SCRIPT));
234 v8::Local<v8::Function> createsendconstructor = v8::Local<v8::Function>::Cast(createsendscript->Run());
236 v8::Handle<v8::Value> args[] = {
237 V8FUNCTION(QQuickWorkerScriptEnginePrivate::sendMessage, this)
239 v8::Local<v8::Value> createsendvalue = createsendconstructor->Call(global(), 1, args);
241 createsend = qPersistentNew<v8::Function>(v8::Handle<v8::Function>::Cast(createsendvalue));
245 // Requires handle and context scope
246 v8::Local<v8::Function> QQuickWorkerScriptEnginePrivate::WorkerEngine::sendFunction(int id)
248 v8::Handle<v8::Value> args[] = { v8::Integer::New(id) };
249 return v8::Local<v8::Function>::Cast(createsend->Call(global(), 1, args));
252 // Requires handle and context scope
253 void QQuickWorkerScriptEnginePrivate::WorkerEngine::callOnMessage(v8::Handle<v8::Object> object,
254 v8::Handle<v8::Value> arg)
256 v8::Handle<v8::Value> args[] = { object, arg };
257 onmessage->Call(global(), 2, args);
260 QNetworkAccessManager *QQuickWorkerScriptEnginePrivate::WorkerEngine::networkAccessManager()
262 if (!accessManager) {
263 if (p->qmlengine && p->qmlengine->networkAccessManagerFactory()) {
264 accessManager = p->qmlengine->networkAccessManagerFactory()->create(p);
266 accessManager = new QNetworkAccessManager(p);
269 return accessManager;
272 QQuickWorkerScriptEnginePrivate::QQuickWorkerScriptEnginePrivate(QQmlEngine *engine)
273 : workerEngine(0), qmlengine(engine), m_nextId(0)
277 v8::Handle<v8::Value> QQuickWorkerScriptEnginePrivate::sendMessage(const v8::Arguments &args)
279 WorkerEngine *engine = (WorkerEngine*)V8ENGINE();
281 int id = args[1]->Int32Value();
283 QByteArray data = QV8Worker::serialize(args[2], engine);
285 QMutexLocker(&engine->p->m_lock);
286 WorkerScript *script = engine->p->workers.value(id);
288 return v8::Undefined();
291 QCoreApplication::postEvent(script->owner, new WorkerDataEvent(0, data));
293 return v8::Undefined();
296 // Requires handle scope and context scope
297 v8::Handle<v8::Object> QQuickWorkerScriptEnginePrivate::getWorker(WorkerScript *script)
299 if (!script->initialized) {
300 script->initialized = true;
302 script->object = qPersistentNew<v8::Object>(workerEngine->contextWrapper()->urlScope(script->source));
304 workerEngine->contextWrapper()->setReadOnly(script->object, false);
306 v8::Local<v8::Object> api = v8::Object::New();
307 api->Set(v8::String::New("sendMessage"), workerEngine->sendFunction(script->id));
309 script->object->Set(v8::String::New("WorkerScript"), api);
311 workerEngine->contextWrapper()->setReadOnly(script->object, true);
314 return script->object;
317 bool QQuickWorkerScriptEnginePrivate::event(QEvent *event)
319 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
320 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
321 processMessage(workerEvent->workerId(), workerEvent->data());
323 } else if (event->type() == (QEvent::Type)WorkerLoadEvent::WorkerLoad) {
324 WorkerLoadEvent *workerEvent = static_cast<WorkerLoadEvent *>(event);
325 processLoad(workerEvent->workerId(), workerEvent->url());
327 } else if (event->type() == (QEvent::Type)WorkerDestroyEvent) {
330 } else if (event->type() == (QEvent::Type)WorkerRemoveEvent::WorkerRemove) {
331 WorkerRemoveEvent *workerEvent = static_cast<WorkerRemoveEvent *>(event);
332 workers.remove(workerEvent->workerId());
335 return QObject::event(event);
339 void QQuickWorkerScriptEnginePrivate::processMessage(int id, const QByteArray &data)
341 WorkerScript *script = workers.value(id);
345 v8::HandleScope handle_scope;
346 v8::Context::Scope scope(workerEngine->context());
348 v8::Handle<v8::Value> value = QV8Worker::deserialize(data, workerEngine);
351 workerEngine->callOnMessage(script->object, value);
353 if (tc.HasCaught()) {
355 QQmlExpressionPrivate::exceptionToError(tc.Message(), error);
356 reportScriptException(script, error);
360 void QQuickWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
362 if (url.isRelative())
365 QString fileName = QQmlEnginePrivate::urlToLocalFileOrQrc(url);
368 if (f.open(QIODevice::ReadOnly)) {
369 QByteArray data = f.readAll();
370 QString sourceCode = QString::fromUtf8(data);
371 QQmlScript::Parser::extractPragmas(sourceCode);
373 v8::HandleScope handle_scope;
374 v8::Context::Scope scope(workerEngine->context());
376 WorkerScript *script = workers.value(id);
379 script->source = url;
380 v8::Handle<v8::Object> activation = getWorker(script);
381 if (activation.IsEmpty())
385 // workerEngine->baseUrl = url;
388 v8::Local<v8::Script> program = workerEngine->qmlModeCompile(sourceCode, url.toString());
391 program->Run(activation);
393 if (tc.HasCaught()) {
395 QQmlExpressionPrivate::exceptionToError(tc.Message(), error);
396 reportScriptException(script, error);
399 qWarning().nospace() << "WorkerScript: Cannot find source file " << url.toString();
403 void QQuickWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script,
404 const QQmlError &error)
406 QQuickWorkerScriptEnginePrivate *p = QQuickWorkerScriptEnginePrivate::get(workerEngine);
408 QMutexLocker(&p->m_lock);
410 QCoreApplication::postEvent(script->owner, new WorkerErrorEvent(error));
413 WorkerDataEvent::WorkerDataEvent(int workerId, const QByteArray &data)
414 : QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data)
418 WorkerDataEvent::~WorkerDataEvent()
422 int WorkerDataEvent::workerId() const
427 QByteArray WorkerDataEvent::data() const
432 WorkerLoadEvent::WorkerLoadEvent(int workerId, const QUrl &url)
433 : QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url)
437 int WorkerLoadEvent::workerId() const
442 QUrl WorkerLoadEvent::url() const
447 WorkerRemoveEvent::WorkerRemoveEvent(int workerId)
448 : QEvent((QEvent::Type)WorkerRemove), m_id(workerId)
452 int WorkerRemoveEvent::workerId() const
457 WorkerErrorEvent::WorkerErrorEvent(const QQmlError &error)
458 : QEvent((QEvent::Type)WorkerError), m_error(error)
462 QQmlError WorkerErrorEvent::error() const
467 QQuickWorkerScriptEngine::QQuickWorkerScriptEngine(QQmlEngine *parent)
468 : QThread(parent), d(new QQuickWorkerScriptEnginePrivate(parent))
471 connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection);
472 start(QThread::LowestPriority);
473 d->m_wait.wait(&d->m_lock);
474 d->moveToThread(this);
478 QQuickWorkerScriptEngine::~QQuickWorkerScriptEngine()
481 QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QQuickWorkerScriptEnginePrivate::WorkerDestroyEvent));
484 //We have to force to cleanup the main thread's event queue here
485 //to make sure the main GUI release all pending locks/wait conditions which
486 //some worker script/agent are waiting for (QQuickListModelWorkerAgent::sync() for example).
487 QCoreApplication::processEvents();
492 QQuickWorkerScriptEnginePrivate::WorkerScript::WorkerScript()
493 : id(-1), initialized(false), owner(0)
497 QQuickWorkerScriptEnginePrivate::WorkerScript::~WorkerScript()
499 qPersistentDispose(object);
502 int QQuickWorkerScriptEngine::registerWorkerScript(QQuickWorkerScript *owner)
504 typedef QQuickWorkerScriptEnginePrivate::WorkerScript WorkerScript;
505 WorkerScript *script = new WorkerScript;
507 script->id = d->m_nextId++;
508 script->owner = owner;
511 d->workers.insert(script->id, script);
517 void QQuickWorkerScriptEngine::removeWorkerScript(int id)
519 QQuickWorkerScriptEnginePrivate::WorkerScript* script = d->workers.value(id);
522 QCoreApplication::postEvent(d, new WorkerRemoveEvent(id));
526 void QQuickWorkerScriptEngine::executeUrl(int id, const QUrl &url)
528 QCoreApplication::postEvent(d, new WorkerLoadEvent(id, url));
531 void QQuickWorkerScriptEngine::sendMessage(int id, const QByteArray &data)
533 QCoreApplication::postEvent(d, new WorkerDataEvent(id, data));
536 void QQuickWorkerScriptEngine::run()
540 d->workerEngine = new QQuickWorkerScriptEnginePrivate::WorkerEngine(d);
541 d->workerEngine->init();
549 qDeleteAll(d->workers);
552 delete d->workerEngine; d->workerEngine = 0;
557 \qmlclass WorkerScript QQuickWorkerScript
558 \ingroup qml-utility-elements
559 \brief The WorkerScript element enables the use of threads in QML.
561 Use WorkerScript to run operations in a new thread.
562 This is useful for running operations in the background so
563 that the main GUI thread is not blocked.
565 Messages can be passed between the new thread and the parent thread
566 using \l sendMessage() and the \l {WorkerScript::onMessage}{onMessage()} handler.
570 \snippet doc/src/snippets/qml/workerscript.qml 0
572 The above worker script specifies a JavaScript file, "script.js", that handles
573 the operations to be performed in the new thread. Here is \c script.js:
575 \quotefile doc/src/snippets/qml/script.js
577 When the user clicks anywhere within the rectangle, \c sendMessage() is
578 called, triggering the \tt WorkerScript.onMessage() handler in
579 \tt script.js. This in turn sends a reply message that is then received
580 by the \tt onMessage() handler of \tt myWorker.
583 \section3 Restrictions
585 Since the \c WorkerScript.onMessage() function is run in a separate thread, the
586 JavaScript file is evaluated in a context separate from the main QML engine. This means
587 that unlike an ordinary JavaScript file that is imported into QML, the \c script.js
588 in the above example cannot access the properties, methods or other attributes
589 of the QML item, nor can it access any context properties set on the QML object
592 Additionally, there are restrictions on the types of values that can be passed to and
593 from the worker script. See the sendMessage() documentation for details.
595 \sa {declarative/threading/workerscript}{WorkerScript example},
596 {declarative/threading/threadedlistmodel}{Threaded ListModel example}
598 QQuickWorkerScript::QQuickWorkerScript(QObject *parent)
599 : QObject(parent), m_engine(0), m_scriptId(-1), m_componentComplete(true)
603 QQuickWorkerScript::~QQuickWorkerScript()
605 if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId);
609 \qmlproperty url WorkerScript::source
611 This holds the url of the JavaScript file that implements the
612 \tt WorkerScript.onMessage() handler for threaded operations.
614 QUrl QQuickWorkerScript::source() const
619 void QQuickWorkerScript::setSource(const QUrl &source)
621 if (m_source == source)
627 m_engine->executeUrl(m_scriptId, m_source);
629 emit sourceChanged();
633 \qmlmethod WorkerScript::sendMessage(jsobject message)
635 Sends the given \a message to a worker script handler in another
636 thread. The other worker script handler can receive this message
637 through the onMessage() handler.
639 The \c message object may only contain values of the following
643 \o boolean, number, string
644 \o JavaScript objects and arrays
645 \o ListModel objects (any other type of QObject* is not allowed)
648 All objects and arrays are copied to the \c message. With the exception
649 of ListModel objects, any modifications by the other thread to an object
650 passed in \c message will not be reflected in the original object.
652 void QQuickWorkerScript::sendMessage(QQmlV8Function *args)
655 qWarning("QQuickWorkerScript: Attempt to send message before WorkerScript establishment");
659 v8::Handle<v8::Value> argument = v8::Undefined();
660 if (args->Length() != 0)
661 argument = (*args)[0];
663 m_engine->sendMessage(m_scriptId, QV8Worker::serialize(argument, args->engine()));
666 void QQuickWorkerScript::classBegin()
668 m_componentComplete = false;
671 QQuickWorkerScriptEngine *QQuickWorkerScript::engine()
673 if (m_engine) return m_engine;
674 if (m_componentComplete) {
675 QQmlEngine *engine = qmlEngine(this);
677 qWarning("QQuickWorkerScript: engine() called without qmlEngine() set");
681 m_engine = QQmlEnginePrivate::get(engine)->getWorkerScriptEngine();
682 m_scriptId = m_engine->registerWorkerScript(this);
684 if (m_source.isValid())
685 m_engine->executeUrl(m_scriptId, m_source);
692 void QQuickWorkerScript::componentComplete()
694 m_componentComplete = true;
695 engine(); // Get it started now.
699 \qmlsignal WorkerScript::onMessage(jsobject msg)
701 This handler is called when a message \a msg is received from a worker
702 script in another thread through a call to sendMessage().
705 bool QQuickWorkerScript::event(QEvent *event)
707 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
708 QQmlEngine *engine = qmlEngine(this);
710 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
711 QV8Engine *v8engine = QQmlEnginePrivate::get(engine)->v8engine();
712 v8::HandleScope handle_scope;
713 v8::Context::Scope scope(v8engine->context());
714 v8::Handle<v8::Value> value = QV8Worker::deserialize(workerEvent->data(), v8engine);
715 emit message(QQmlV8Handle::fromHandle(value));
718 } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) {
719 WorkerErrorEvent *workerEvent = static_cast<WorkerErrorEvent *>(event);
720 QQmlEnginePrivate::warning(qmlEngine(this), workerEvent->error());
723 return QObject::event(event);
729 #include <qquickworkerscript.moc>