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 createsend.Dispose(); createsend.Clear();
200 onmessage.Dispose(); onmessage.Clear();
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 = v8::Persistent<v8::Function>::New(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 = v8::Persistent<v8::Function>::New(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 = v8::Persistent<v8::Object>::New(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 qDeleteAll(d->workers);
482 QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QDeclarativeWorkerScriptEnginePrivate::WorkerDestroyEvent));
489 QDeclarativeWorkerScriptEnginePrivate::WorkerScript::WorkerScript()
490 : id(-1), initialized(false), owner(0)
494 QDeclarativeWorkerScriptEnginePrivate::WorkerScript::~WorkerScript()
496 object.Dispose(); object.Clear();
499 int QDeclarativeWorkerScriptEngine::registerWorkerScript(QDeclarativeWorkerScript *owner)
501 typedef QDeclarativeWorkerScriptEnginePrivate::WorkerScript WorkerScript;
502 WorkerScript *script = new WorkerScript;
504 script->id = d->m_nextId++;
505 script->owner = owner;
508 d->workers.insert(script->id, script);
514 void QDeclarativeWorkerScriptEngine::removeWorkerScript(int id)
516 QCoreApplication::postEvent(d, new WorkerRemoveEvent(id));
519 void QDeclarativeWorkerScriptEngine::executeUrl(int id, const QUrl &url)
521 QCoreApplication::postEvent(d, new WorkerLoadEvent(id, url));
524 void QDeclarativeWorkerScriptEngine::sendMessage(int id, const QByteArray &data)
526 QCoreApplication::postEvent(d, new WorkerDataEvent(id, data));
529 void QDeclarativeWorkerScriptEngine::run()
533 v8::Isolate *isolate = v8::Isolate::New();
536 d->workerEngine = new QDeclarativeWorkerScriptEnginePrivate::WorkerEngine(d);
537 d->workerEngine->init();
545 delete d->workerEngine; d->workerEngine = 0;
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>