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 <QtDeclarative/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 <QtDeclarative/qdeclarativeinfo.h>
58 #include "qdeclarativenetworkaccessmanagerfactory.h"
60 #include <private/qv8engine_p.h>
61 #include <private/qv8worker_p.h>
62 #include <private/qv8gccallback_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 : QV8Engine(0), p(parent), accessManager(0)
197 QDeclarativeWorkerScriptEnginePrivate::WorkerEngine::~WorkerEngine()
199 qPersistentDispose(createsend);
200 qPersistentDispose(onmessage);
201 delete accessManager;
204 void QDeclarativeWorkerScriptEnginePrivate::WorkerEngine::init()
206 initDeclarativeGlobalObject();
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(QDeclarativeWorkerScriptEnginePrivate::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> QDeclarativeWorkerScriptEnginePrivate::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 QDeclarativeWorkerScriptEnginePrivate::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 *QDeclarativeWorkerScriptEnginePrivate::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 QDeclarativeWorkerScriptEnginePrivate::QDeclarativeWorkerScriptEnginePrivate(QDeclarativeEngine *engine)
274 : workerEngine(0), qmlengine(engine), m_nextId(0)
278 v8::Handle<v8::Value> QDeclarativeWorkerScriptEnginePrivate::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(&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> QDeclarativeWorkerScriptEnginePrivate::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 QDeclarativeWorkerScriptEnginePrivate::event(QEvent *event)
320 // XXX must handle remove request
321 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
322 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
323 processMessage(workerEvent->workerId(), workerEvent->data());
325 } else if (event->type() == (QEvent::Type)WorkerLoadEvent::WorkerLoad) {
326 WorkerLoadEvent *workerEvent = static_cast<WorkerLoadEvent *>(event);
327 processLoad(workerEvent->workerId(), workerEvent->url());
329 } else if (event->type() == (QEvent::Type)WorkerDestroyEvent) {
333 return QObject::event(event);
337 void QDeclarativeWorkerScriptEnginePrivate::processMessage(int id, const QByteArray &data)
339 WorkerScript *script = workers.value(id);
343 v8::HandleScope handle_scope;
344 v8::Context::Scope scope(workerEngine->context());
346 v8::Handle<v8::Value> value = QV8Worker::deserialize(data, workerEngine);
349 workerEngine->callOnMessage(script->object, value);
351 if (tc.HasCaught()) {
352 QDeclarativeError error;
353 QDeclarativeExpressionPrivate::exceptionToError(tc.Message(), error);
354 reportScriptException(script, error);
358 void QDeclarativeWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
360 if (url.isRelative())
363 QString fileName = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url);
366 if (f.open(QIODevice::ReadOnly)) {
367 QByteArray data = f.readAll();
368 QString sourceCode = QString::fromUtf8(data);
369 QDeclarativeScript::Parser::extractPragmas(sourceCode);
371 v8::HandleScope handle_scope;
372 v8::Context::Scope scope(workerEngine->context());
374 WorkerScript *script = workers.value(id);
377 script->source = url;
378 v8::Handle<v8::Object> activation = getWorker(script);
379 if (activation.IsEmpty())
383 // workerEngine->baseUrl = url;
386 v8::Local<v8::Script> program = workerEngine->qmlModeCompile(sourceCode, url.toString());
389 program->Run(activation);
391 if (tc.HasCaught()) {
392 QDeclarativeError error;
393 QDeclarativeExpressionPrivate::exceptionToError(tc.Message(), error);
394 reportScriptException(script, error);
397 qWarning().nospace() << "WorkerScript: Cannot find source file " << url.toString();
401 void QDeclarativeWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script,
402 const QDeclarativeError &error)
404 QDeclarativeWorkerScriptEnginePrivate *p = QDeclarativeWorkerScriptEnginePrivate::get(workerEngine);
406 QMutexLocker(&p->m_lock);
408 QCoreApplication::postEvent(script->owner, new WorkerErrorEvent(error));
411 WorkerDataEvent::WorkerDataEvent(int workerId, const QByteArray &data)
412 : QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data)
416 WorkerDataEvent::~WorkerDataEvent()
420 int WorkerDataEvent::workerId() const
425 QByteArray WorkerDataEvent::data() const
430 WorkerLoadEvent::WorkerLoadEvent(int workerId, const QUrl &url)
431 : QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url)
435 int WorkerLoadEvent::workerId() const
440 QUrl WorkerLoadEvent::url() const
445 WorkerRemoveEvent::WorkerRemoveEvent(int workerId)
446 : QEvent((QEvent::Type)WorkerRemove), m_id(workerId)
450 int WorkerRemoveEvent::workerId() const
455 WorkerErrorEvent::WorkerErrorEvent(const QDeclarativeError &error)
456 : QEvent((QEvent::Type)WorkerError), m_error(error)
460 QDeclarativeError WorkerErrorEvent::error() const
465 QDeclarativeWorkerScriptEngine::QDeclarativeWorkerScriptEngine(QDeclarativeEngine *parent)
466 : QThread(parent), d(new QDeclarativeWorkerScriptEnginePrivate(parent))
469 connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection);
470 start(QThread::IdlePriority);
471 d->m_wait.wait(&d->m_lock);
472 d->moveToThread(this);
476 QDeclarativeWorkerScriptEngine::~QDeclarativeWorkerScriptEngine()
479 QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QDeclarativeWorkerScriptEnginePrivate::WorkerDestroyEvent));
486 QDeclarativeWorkerScriptEnginePrivate::WorkerScript::WorkerScript()
487 : id(-1), initialized(false), owner(0)
491 QDeclarativeWorkerScriptEnginePrivate::WorkerScript::~WorkerScript()
493 qPersistentDispose(object);
496 int QDeclarativeWorkerScriptEngine::registerWorkerScript(QDeclarativeWorkerScript *owner)
498 typedef QDeclarativeWorkerScriptEnginePrivate::WorkerScript WorkerScript;
499 WorkerScript *script = new WorkerScript;
501 script->id = d->m_nextId++;
502 script->owner = owner;
505 d->workers.insert(script->id, script);
511 void QDeclarativeWorkerScriptEngine::removeWorkerScript(int id)
513 QCoreApplication::postEvent(d, new WorkerRemoveEvent(id));
516 void QDeclarativeWorkerScriptEngine::executeUrl(int id, const QUrl &url)
518 QCoreApplication::postEvent(d, new WorkerLoadEvent(id, url));
521 void QDeclarativeWorkerScriptEngine::sendMessage(int id, const QByteArray &data)
523 QCoreApplication::postEvent(d, new WorkerDataEvent(id, data));
526 void QDeclarativeWorkerScriptEngine::run()
530 v8::Isolate *isolate = v8::Isolate::New();
533 d->workerEngine = new QDeclarativeWorkerScriptEnginePrivate::WorkerEngine(d);
534 d->workerEngine->init();
542 qDeleteAll(d->workers);
545 delete d->workerEngine; d->workerEngine = 0;
546 QV8GCCallback::releaseWorkerThreadGcPrologueCallbackData();
553 \qmlclass WorkerScript QDeclarativeWorkerScript
554 \ingroup qml-utility-elements
555 \brief The WorkerScript element enables the use of threads in QML.
557 Use WorkerScript to run operations in a new thread.
558 This is useful for running operations in the background so
559 that the main GUI thread is not blocked.
561 Messages can be passed between the new thread and the parent thread
562 using \l sendMessage() and the \l {WorkerScript::onMessage}{onMessage()} handler.
566 \snippet doc/src/snippets/declarative/workerscript.qml 0
568 The above worker script specifies a JavaScript file, "script.js", that handles
569 the operations to be performed in the new thread. Here is \c script.js:
571 \quotefile doc/src/snippets/declarative/script.js
573 When the user clicks anywhere within the rectangle, \c sendMessage() is
574 called, triggering the \tt WorkerScript.onMessage() handler in
575 \tt script.js. This in turn sends a reply message that is then received
576 by the \tt onMessage() handler of \tt myWorker.
579 \section3 Restrictions
581 Since the \c WorkerScript.onMessage() function is run in a separate thread, the
582 JavaScript file is evaluated in a context separate from the main QML engine. This means
583 that unlike an ordinary JavaScript file that is imported into QML, the \c script.js
584 in the above example cannot access the properties, methods or other attributes
585 of the QML item, nor can it access any context properties set on the QML object
586 through QDeclarativeContext.
588 Additionally, there are restrictions on the types of values that can be passed to and
589 from the worker script. See the sendMessage() documentation for details.
591 \sa {declarative/threading/workerscript}{WorkerScript example},
592 {declarative/threading/threadedlistmodel}{Threaded ListModel example}
594 QDeclarativeWorkerScript::QDeclarativeWorkerScript(QObject *parent)
595 : QObject(parent), m_engine(0), m_scriptId(-1), m_componentComplete(true)
599 QDeclarativeWorkerScript::~QDeclarativeWorkerScript()
601 if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId);
605 \qmlproperty url WorkerScript::source
607 This holds the url of the JavaScript file that implements the
608 \tt WorkerScript.onMessage() handler for threaded operations.
610 QUrl QDeclarativeWorkerScript::source() const
615 void QDeclarativeWorkerScript::setSource(const QUrl &source)
617 if (m_source == source)
623 m_engine->executeUrl(m_scriptId, m_source);
625 emit sourceChanged();
629 \qmlmethod WorkerScript::sendMessage(jsobject message)
631 Sends the given \a message to a worker script handler in another
632 thread. The other worker script handler can receive this message
633 through the onMessage() handler.
635 The \c message object may only contain values of the following
639 \o boolean, number, string
640 \o JavaScript objects and arrays
641 \o ListModel objects (any other type of QObject* is not allowed)
644 All objects and arrays are copied to the \c message. With the exception
645 of ListModel objects, any modifications by the other thread to an object
646 passed in \c message will not be reflected in the original object.
648 void QDeclarativeWorkerScript::sendMessage(QDeclarativeV8Function *args)
651 qWarning("QDeclarativeWorkerScript: Attempt to send message before WorkerScript establishment");
655 v8::Handle<v8::Value> argument = v8::Undefined();
656 if (args->Length() != 0)
657 argument = (*args)[0];
659 m_engine->sendMessage(m_scriptId, QV8Worker::serialize(argument, args->engine()));
662 void QDeclarativeWorkerScript::classBegin()
664 m_componentComplete = false;
667 QDeclarativeWorkerScriptEngine *QDeclarativeWorkerScript::engine()
669 if (m_engine) return m_engine;
670 if (m_componentComplete) {
671 QDeclarativeEngine *engine = qmlEngine(this);
673 qWarning("QDeclarativeWorkerScript: engine() called without qmlEngine() set");
677 m_engine = QDeclarativeEnginePrivate::get(engine)->getWorkerScriptEngine();
678 m_scriptId = m_engine->registerWorkerScript(this);
680 if (m_source.isValid())
681 m_engine->executeUrl(m_scriptId, m_source);
688 void QDeclarativeWorkerScript::componentComplete()
690 m_componentComplete = true;
691 engine(); // Get it started now.
695 \qmlsignal WorkerScript::onMessage(jsobject msg)
697 This handler is called when a message \a msg is received from a worker
698 script in another thread through a call to sendMessage().
701 bool QDeclarativeWorkerScript::event(QEvent *event)
703 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
704 QDeclarativeEngine *engine = qmlEngine(this);
706 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
707 QV8Engine *v8engine = QDeclarativeEnginePrivate::get(engine)->v8engine();
708 v8::HandleScope handle_scope;
709 v8::Context::Scope scope(v8engine->context());
710 v8::Handle<v8::Value> value = QV8Worker::deserialize(workerEvent->data(), v8engine);
711 emit message(QDeclarativeV8Handle::fromHandle(value));
714 } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) {
715 WorkerErrorEvent *workerEvent = static_cast<WorkerErrorEvent *>(event);
716 QDeclarativeEnginePrivate::warning(qmlEngine(this), workerEvent->error());
719 return QObject::event(event);
725 #include <qdeclarativeworkerscript.moc>