1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "private/qdeclarativeworkerscript_p.h"
43 #include "private/qdeclarativelistmodel_p.h"
44 #include "private/qdeclarativelistmodelworkeragent_p.h"
45 #include "private/qdeclarativeengine_p.h"
46 #include "private/qdeclarativeexpression_p.h"
48 #include <QtCore/qcoreevent.h>
49 #include <QtCore/qcoreapplication.h>
50 #include <QtCore/qdebug.h>
51 #include <QtScript/qscriptengine.h>
52 #include <QtCore/qmutex.h>
53 #include <QtCore/qwaitcondition.h>
54 #include <QtScript/qscriptvalueiterator.h>
55 #include <QtCore/qfile.h>
56 #include <QtCore/qdatetime.h>
57 #include <QtNetwork/qnetworkaccessmanager.h>
58 #include <QtDeclarative/qdeclarativeinfo.h>
59 #include "qdeclarativenetworkaccessmanagerfactory.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 QDeclarativeError &error);
117 QDeclarativeError error() const;
120 QDeclarativeError m_error;
123 class QDeclarativeWorkerScriptEnginePrivate : public QObject
127 enum WorkerEventTypes {
128 WorkerDestroyEvent = QEvent::User + 100
131 QDeclarativeWorkerScriptEnginePrivate(QDeclarativeEngine *eng);
133 class WorkerEngine : public QV8Engine
136 WorkerEngine(QDeclarativeWorkerScriptEnginePrivate *parent);
140 virtual QNetworkAccessManager *networkAccessManager();
142 QDeclarativeWorkerScriptEnginePrivate *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 QDeclarativeWorkerScriptEnginePrivate *get(QV8Engine *e) {
154 return static_cast<WorkerEngine *>(e)->p;
157 QDeclarativeEngine *qmlengine;
160 QWaitCondition m_wait;
162 struct WorkerScript {
169 QDeclarativeWorkerScript *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 QDeclarativeError &error);
192 QDeclarativeWorkerScriptEnginePrivate::WorkerEngine::WorkerEngine(QDeclarativeWorkerScriptEnginePrivate *parent)
193 : p(parent), accessManager(0)
197 QDeclarativeWorkerScriptEnginePrivate::WorkerEngine::~WorkerEngine()
199 qPersistentDispose(createsend);
200 qPersistentDispose(onmessage);
201 delete accessManager;
204 void QDeclarativeWorkerScriptEnginePrivate::WorkerEngine::init()
208 #define CALL_ONMESSAGE_SCRIPT \
209 "(function(object, message) { "\
210 "var isfunction = false; "\
212 "isfunction = object.WorkerScript.onMessage instanceof Function; "\
215 "object.WorkerScript.onMessage(message); "\
218 #define SEND_MESSAGE_CREATE_SCRIPT \
219 "(function(method, engine) { "\
220 "return (function(id) { "\
221 "return (function(message) { "\
222 "if (arguments.length) method(engine, id, message); "\
227 v8::HandleScope handle_scope;
228 v8::Context::Scope scope(context());
231 v8::Local<v8::Script> onmessagescript = v8::Script::New(v8::String::New(CALL_ONMESSAGE_SCRIPT));
232 onmessage = qPersistentNew<v8::Function>(v8::Handle<v8::Function>::Cast(onmessagescript->Run()));
235 v8::Local<v8::Script> createsendscript = v8::Script::New(v8::String::New(SEND_MESSAGE_CREATE_SCRIPT));
236 v8::Local<v8::Function> createsendconstructor = v8::Local<v8::Function>::Cast(createsendscript->Run());
238 v8::Handle<v8::Value> args[] = {
239 V8FUNCTION(QDeclarativeWorkerScriptEnginePrivate::sendMessage, this)
241 v8::Local<v8::Value> createsendvalue = createsendconstructor->Call(global(), 1, args);
243 createsend = qPersistentNew<v8::Function>(v8::Handle<v8::Function>::Cast(createsendvalue));
247 // Requires handle and context scope
248 v8::Local<v8::Function> QDeclarativeWorkerScriptEnginePrivate::WorkerEngine::sendFunction(int id)
250 v8::Handle<v8::Value> args[] = { v8::Integer::New(id) };
251 return v8::Local<v8::Function>::Cast(createsend->Call(global(), 1, args));
254 // Requires handle and context scope
255 void QDeclarativeWorkerScriptEnginePrivate::WorkerEngine::callOnMessage(v8::Handle<v8::Object> object,
256 v8::Handle<v8::Value> arg)
258 v8::Handle<v8::Value> args[] = { object, arg };
259 onmessage->Call(global(), 2, args);
262 QNetworkAccessManager *QDeclarativeWorkerScriptEnginePrivate::WorkerEngine::networkAccessManager()
264 if (!accessManager) {
265 if (p->qmlengine && p->qmlengine->networkAccessManagerFactory()) {
266 accessManager = p->qmlengine->networkAccessManagerFactory()->create(p);
268 accessManager = new QNetworkAccessManager(p);
271 return accessManager;
274 QDeclarativeWorkerScriptEnginePrivate::QDeclarativeWorkerScriptEnginePrivate(QDeclarativeEngine *engine)
275 : workerEngine(0), qmlengine(engine), m_nextId(0)
279 v8::Handle<v8::Value> QDeclarativeWorkerScriptEnginePrivate::sendMessage(const v8::Arguments &args)
281 WorkerEngine *engine = (WorkerEngine*)V8ENGINE();
283 int id = args[1]->Int32Value();
285 QByteArray data = QV8Worker::serialize(args[2], engine);
287 QMutexLocker(&engine->p->m_lock);
288 WorkerScript *script = engine->p->workers.value(id);
290 return v8::Undefined();
293 QCoreApplication::postEvent(script->owner, new WorkerDataEvent(0, data));
295 return v8::Undefined();
298 // Requires handle scope and context scope
299 v8::Handle<v8::Object> QDeclarativeWorkerScriptEnginePrivate::getWorker(WorkerScript *script)
301 if (!script->initialized) {
302 script->initialized = true;
304 script->object = qPersistentNew<v8::Object>(workerEngine->contextWrapper()->urlScope(script->source));
306 workerEngine->contextWrapper()->setReadOnly(script->object, false);
308 v8::Local<v8::Object> api = v8::Object::New();
309 api->Set(v8::String::New("sendMessage"), workerEngine->sendFunction(script->id));
311 script->object->Set(v8::String::New("WorkerScript"), api);
313 workerEngine->contextWrapper()->setReadOnly(script->object, true);
316 return script->object;
319 bool QDeclarativeWorkerScriptEnginePrivate::event(QEvent *event)
321 // XXX must handle remove request
322 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
323 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
324 processMessage(workerEvent->workerId(), workerEvent->data());
326 } else if (event->type() == (QEvent::Type)WorkerLoadEvent::WorkerLoad) {
327 WorkerLoadEvent *workerEvent = static_cast<WorkerLoadEvent *>(event);
328 processLoad(workerEvent->workerId(), workerEvent->url());
330 } else if (event->type() == (QEvent::Type)WorkerDestroyEvent) {
334 return QObject::event(event);
338 void QDeclarativeWorkerScriptEnginePrivate::processMessage(int id, const QByteArray &data)
340 WorkerScript *script = workers.value(id);
344 v8::HandleScope handle_scope;
345 v8::Context::Scope scope(workerEngine->context());
347 v8::Handle<v8::Value> value = QV8Worker::deserialize(data, workerEngine);
350 workerEngine->callOnMessage(script->object, value);
352 if (tc.HasCaught()) {
353 QDeclarativeError error;
354 QDeclarativeExpressionPrivate::exceptionToError(tc.Message(), error);
355 reportScriptException(script, error);
359 void QDeclarativeWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
361 if (url.isRelative())
364 QString fileName = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url);
367 if (f.open(QIODevice::ReadOnly)) {
368 QByteArray data = f.readAll();
369 QString sourceCode = QString::fromUtf8(data);
370 QDeclarativeScriptParser::extractPragmas(sourceCode);
372 v8::HandleScope handle_scope;
373 v8::Context::Scope scope(workerEngine->context());
375 WorkerScript *script = workers.value(id);
378 script->source = url;
379 v8::Handle<v8::Object> activation = getWorker(script);
380 if (activation.IsEmpty())
384 // workerEngine->baseUrl = url;
387 v8::Local<v8::Script> program = workerEngine->qmlModeCompile(sourceCode, url.toString());
390 program->Run(activation);
392 if (tc.HasCaught()) {
393 QDeclarativeError error;
394 QDeclarativeExpressionPrivate::exceptionToError(tc.Message(), error);
395 reportScriptException(script, error);
398 qWarning().nospace() << "WorkerScript: Cannot find source file " << url.toString();
402 void QDeclarativeWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script,
403 const QDeclarativeError &error)
405 QDeclarativeWorkerScriptEnginePrivate *p = QDeclarativeWorkerScriptEnginePrivate::get(workerEngine);
407 QMutexLocker(&p->m_lock);
409 QCoreApplication::postEvent(script->owner, new WorkerErrorEvent(error));
412 WorkerDataEvent::WorkerDataEvent(int workerId, const QByteArray &data)
413 : QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data)
417 WorkerDataEvent::~WorkerDataEvent()
421 int WorkerDataEvent::workerId() const
426 QByteArray WorkerDataEvent::data() const
431 WorkerLoadEvent::WorkerLoadEvent(int workerId, const QUrl &url)
432 : QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url)
436 int WorkerLoadEvent::workerId() const
441 QUrl WorkerLoadEvent::url() const
446 WorkerRemoveEvent::WorkerRemoveEvent(int workerId)
447 : QEvent((QEvent::Type)WorkerRemove), m_id(workerId)
451 int WorkerRemoveEvent::workerId() const
456 WorkerErrorEvent::WorkerErrorEvent(const QDeclarativeError &error)
457 : QEvent((QEvent::Type)WorkerError), m_error(error)
461 QDeclarativeError WorkerErrorEvent::error() const
466 QDeclarativeWorkerScriptEngine::QDeclarativeWorkerScriptEngine(QDeclarativeEngine *parent)
467 : QThread(parent), d(new QDeclarativeWorkerScriptEnginePrivate(parent))
470 connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection);
471 start(QThread::IdlePriority);
472 d->m_wait.wait(&d->m_lock);
473 d->moveToThread(this);
477 QDeclarativeWorkerScriptEngine::~QDeclarativeWorkerScriptEngine()
480 QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QDeclarativeWorkerScriptEnginePrivate::WorkerDestroyEvent));
487 QDeclarativeWorkerScriptEnginePrivate::WorkerScript::WorkerScript()
488 : id(-1), initialized(false), owner(0)
492 QDeclarativeWorkerScriptEnginePrivate::WorkerScript::~WorkerScript()
494 qPersistentDispose(object);
497 int QDeclarativeWorkerScriptEngine::registerWorkerScript(QDeclarativeWorkerScript *owner)
499 typedef QDeclarativeWorkerScriptEnginePrivate::WorkerScript WorkerScript;
500 WorkerScript *script = new WorkerScript;
502 script->id = d->m_nextId++;
503 script->owner = owner;
506 d->workers.insert(script->id, script);
512 void QDeclarativeWorkerScriptEngine::removeWorkerScript(int id)
514 QCoreApplication::postEvent(d, new WorkerRemoveEvent(id));
517 void QDeclarativeWorkerScriptEngine::executeUrl(int id, const QUrl &url)
519 QCoreApplication::postEvent(d, new WorkerLoadEvent(id, url));
522 void QDeclarativeWorkerScriptEngine::sendMessage(int id, const QByteArray &data)
524 QCoreApplication::postEvent(d, new WorkerDataEvent(id, data));
527 void QDeclarativeWorkerScriptEngine::run()
531 v8::Isolate *isolate = v8::Isolate::New();
534 d->workerEngine = new QDeclarativeWorkerScriptEnginePrivate::WorkerEngine(d);
535 d->workerEngine->init();
543 qDeleteAll(d->workers);
546 delete d->workerEngine; d->workerEngine = 0;
554 \qmlclass WorkerScript QDeclarativeWorkerScript
555 \ingroup qml-utility-elements
556 \brief The WorkerScript element enables the use of threads in QML.
558 Use WorkerScript to run operations in a new thread.
559 This is useful for running operations in the background so
560 that the main GUI thread is not blocked.
562 Messages can be passed between the new thread and the parent thread
563 using \l sendMessage() and the \l {WorkerScript::onMessage}{onMessage()} handler.
567 \snippet doc/src/snippets/declarative/workerscript.qml 0
569 The above worker script specifies a JavaScript file, "script.js", that handles
570 the operations to be performed in the new thread. Here is \c script.js:
572 \quotefile doc/src/snippets/declarative/script.js
574 When the user clicks anywhere within the rectangle, \c sendMessage() is
575 called, triggering the \tt WorkerScript.onMessage() handler in
576 \tt script.js. This in turn sends a reply message that is then received
577 by the \tt onMessage() handler of \tt myWorker.
580 \section3 Restrictions
582 Since the \c WorkerScript.onMessage() function is run in a separate thread, the
583 JavaScript file is evaluated in a context separate from the main QML engine. This means
584 that unlike an ordinary JavaScript file that is imported into QML, the \c script.js
585 in the above example cannot access the properties, methods or other attributes
586 of the QML item, nor can it access any context properties set on the QML object
587 through QDeclarativeContext.
589 Additionally, there are restrictions on the types of values that can be passed to and
590 from the worker script. See the sendMessage() documentation for details.
592 \sa {declarative/threading/workerscript}{WorkerScript example},
593 {declarative/threading/threadedlistmodel}{Threaded ListModel example}
595 QDeclarativeWorkerScript::QDeclarativeWorkerScript(QObject *parent)
596 : QObject(parent), m_engine(0), m_scriptId(-1), m_componentComplete(true)
600 QDeclarativeWorkerScript::~QDeclarativeWorkerScript()
602 if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId);
606 \qmlproperty url WorkerScript::source
608 This holds the url of the JavaScript file that implements the
609 \tt WorkerScript.onMessage() handler for threaded operations.
611 QUrl QDeclarativeWorkerScript::source() const
616 void QDeclarativeWorkerScript::setSource(const QUrl &source)
618 if (m_source == source)
624 m_engine->executeUrl(m_scriptId, m_source);
626 emit sourceChanged();
630 \qmlmethod WorkerScript::sendMessage(jsobject message)
632 Sends the given \a message to a worker script handler in another
633 thread. The other worker script handler can receive this message
634 through the onMessage() handler.
636 The \c message object may only contain values of the following
640 \o boolean, number, string
641 \o JavaScript objects and arrays
642 \o ListModel objects (any other type of QObject* is not allowed)
645 All objects and arrays are copied to the \c message. With the exception
646 of ListModel objects, any modifications by the other thread to an object
647 passed in \c message will not be reflected in the original object.
649 void QDeclarativeWorkerScript::sendMessage(QDeclarativeV8Function *args)
652 qWarning("QDeclarativeWorkerScript: Attempt to send message before WorkerScript establishment");
656 v8::Handle<v8::Value> argument = v8::Undefined();
657 if (args->Length() != 0)
658 argument = (*args)[0];
660 m_engine->sendMessage(m_scriptId, QV8Worker::serialize(argument, args->engine()));
663 void QDeclarativeWorkerScript::classBegin()
665 m_componentComplete = false;
668 QDeclarativeWorkerScriptEngine *QDeclarativeWorkerScript::engine()
670 if (m_engine) return m_engine;
671 if (m_componentComplete) {
672 QDeclarativeEngine *engine = qmlEngine(this);
674 qWarning("QDeclarativeWorkerScript: engine() called without qmlEngine() set");
678 m_engine = QDeclarativeEnginePrivate::get(engine)->getWorkerScriptEngine();
679 m_scriptId = m_engine->registerWorkerScript(this);
681 if (m_source.isValid())
682 m_engine->executeUrl(m_scriptId, m_source);
689 void QDeclarativeWorkerScript::componentComplete()
691 m_componentComplete = true;
692 engine(); // Get it started now.
696 \qmlsignal WorkerScript::onMessage(jsobject msg)
698 This handler is called when a message \a msg is received from a worker
699 script in another thread through a call to sendMessage().
702 bool QDeclarativeWorkerScript::event(QEvent *event)
704 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
705 QDeclarativeEngine *engine = qmlEngine(this);
707 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
708 QV8Engine *v8engine = &QDeclarativeEnginePrivate::get(engine)->v8engine;
709 v8::HandleScope handle_scope;
710 v8::Context::Scope scope(v8engine->context());
711 v8::Handle<v8::Value> value = QV8Worker::deserialize(workerEvent->data(), v8engine);
712 emit message(QDeclarativeV8Handle::fromHandle(value));
715 } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) {
716 WorkerErrorEvent *workerEvent = static_cast<WorkerErrorEvent *>(event);
717 QDeclarativeEnginePrivate::warning(qmlEngine(this), workerEvent->error());
720 return QObject::event(event);
726 #include <qdeclarativeworkerscript.moc>