1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
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 "qdeclarativeworkerscript_p.h"
43 #include "qdeclarativelistmodel_p.h"
44 #include "qdeclarativelistmodelworkeragent_p.h"
45 #include "qdeclarativeengine_p.h"
46 #include "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>
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 QDeclarativeError &error);
116 QDeclarativeError error() const;
119 QDeclarativeError m_error;
122 class QDeclarativeWorkerScriptEnginePrivate : public QObject
126 enum WorkerEventTypes {
127 WorkerDestroyEvent = QEvent::User + 100
130 QDeclarativeWorkerScriptEnginePrivate(QDeclarativeEngine *eng);
132 class WorkerEngine : public QV8Engine
135 WorkerEngine(QDeclarativeWorkerScriptEnginePrivate *parent);
139 virtual QNetworkAccessManager *networkAccessManager();
141 QDeclarativeWorkerScriptEnginePrivate *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 QDeclarativeWorkerScriptEnginePrivate *get(QV8Engine *e) {
153 return static_cast<WorkerEngine *>(e)->p;
156 QDeclarativeEngine *qmlengine;
159 QWaitCondition m_wait;
161 struct WorkerScript {
168 QDeclarativeWorkerScript *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 QDeclarativeError &error);
191 QDeclarativeWorkerScriptEnginePrivate::WorkerEngine::WorkerEngine(QDeclarativeWorkerScriptEnginePrivate *parent)
192 : QV8Engine(0), p(parent), accessManager(0)
196 QDeclarativeWorkerScriptEnginePrivate::WorkerEngine::~WorkerEngine()
198 qPersistentDispose(createsend);
199 qPersistentDispose(onmessage);
200 delete accessManager;
203 void QDeclarativeWorkerScriptEnginePrivate::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(QDeclarativeWorkerScriptEnginePrivate::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> QDeclarativeWorkerScriptEnginePrivate::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 QDeclarativeWorkerScriptEnginePrivate::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 *QDeclarativeWorkerScriptEnginePrivate::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 QDeclarativeWorkerScriptEnginePrivate::QDeclarativeWorkerScriptEnginePrivate(QDeclarativeEngine *engine)
273 : workerEngine(0), qmlengine(engine), m_nextId(0)
277 v8::Handle<v8::Value> QDeclarativeWorkerScriptEnginePrivate::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> QDeclarativeWorkerScriptEnginePrivate::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 QDeclarativeWorkerScriptEnginePrivate::event(QEvent *event)
319 // XXX must handle remove request
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) {
332 return QObject::event(event);
336 void QDeclarativeWorkerScriptEnginePrivate::processMessage(int id, const QByteArray &data)
338 WorkerScript *script = workers.value(id);
342 v8::HandleScope handle_scope;
343 v8::Context::Scope scope(workerEngine->context());
345 v8::Handle<v8::Value> value = QV8Worker::deserialize(data, workerEngine);
348 workerEngine->callOnMessage(script->object, value);
350 if (tc.HasCaught()) {
351 QDeclarativeError error;
352 QDeclarativeExpressionPrivate::exceptionToError(tc.Message(), error);
353 reportScriptException(script, error);
357 void QDeclarativeWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
359 if (url.isRelative())
362 QString fileName = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url);
365 if (f.open(QIODevice::ReadOnly)) {
366 QByteArray data = f.readAll();
367 QString sourceCode = QString::fromUtf8(data);
368 QDeclarativeScript::Parser::extractPragmas(sourceCode);
370 v8::HandleScope handle_scope;
371 v8::Context::Scope scope(workerEngine->context());
373 WorkerScript *script = workers.value(id);
376 script->source = url;
377 v8::Handle<v8::Object> activation = getWorker(script);
378 if (activation.IsEmpty())
382 // workerEngine->baseUrl = url;
385 v8::Local<v8::Script> program = workerEngine->qmlModeCompile(sourceCode, url.toString());
388 program->Run(activation);
390 if (tc.HasCaught()) {
391 QDeclarativeError error;
392 QDeclarativeExpressionPrivate::exceptionToError(tc.Message(), error);
393 reportScriptException(script, error);
396 qWarning().nospace() << "WorkerScript: Cannot find source file " << url.toString();
400 void QDeclarativeWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script,
401 const QDeclarativeError &error)
403 QDeclarativeWorkerScriptEnginePrivate *p = QDeclarativeWorkerScriptEnginePrivate::get(workerEngine);
405 QMutexLocker(&p->m_lock);
407 QCoreApplication::postEvent(script->owner, new WorkerErrorEvent(error));
410 WorkerDataEvent::WorkerDataEvent(int workerId, const QByteArray &data)
411 : QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data)
415 WorkerDataEvent::~WorkerDataEvent()
419 int WorkerDataEvent::workerId() const
424 QByteArray WorkerDataEvent::data() const
429 WorkerLoadEvent::WorkerLoadEvent(int workerId, const QUrl &url)
430 : QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url)
434 int WorkerLoadEvent::workerId() const
439 QUrl WorkerLoadEvent::url() const
444 WorkerRemoveEvent::WorkerRemoveEvent(int workerId)
445 : QEvent((QEvent::Type)WorkerRemove), m_id(workerId)
449 int WorkerRemoveEvent::workerId() const
454 WorkerErrorEvent::WorkerErrorEvent(const QDeclarativeError &error)
455 : QEvent((QEvent::Type)WorkerError), m_error(error)
459 QDeclarativeError WorkerErrorEvent::error() const
464 QDeclarativeWorkerScriptEngine::QDeclarativeWorkerScriptEngine(QDeclarativeEngine *parent)
465 : QThread(parent), d(new QDeclarativeWorkerScriptEnginePrivate(parent))
468 connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection);
469 start(QThread::LowestPriority);
470 d->m_wait.wait(&d->m_lock);
471 d->moveToThread(this);
475 QDeclarativeWorkerScriptEngine::~QDeclarativeWorkerScriptEngine()
478 QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QDeclarativeWorkerScriptEnginePrivate::WorkerDestroyEvent));
481 //We have to force to cleanup the main thread's event queue here
482 //to make sure the main GUI release all pending locks/wait conditions which
483 //some worker script/agent are waiting for (QDeclarativeListModelWorkerAgent::sync() for example).
484 QCoreApplication::processEvents();
489 QDeclarativeWorkerScriptEnginePrivate::WorkerScript::WorkerScript()
490 : id(-1), initialized(false), owner(0)
494 QDeclarativeWorkerScriptEnginePrivate::WorkerScript::~WorkerScript()
496 qPersistentDispose(object);
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 d->workerEngine = new QDeclarativeWorkerScriptEnginePrivate::WorkerEngine(d);
534 d->workerEngine->init();
542 qDeleteAll(d->workers);
545 delete d->workerEngine; d->workerEngine = 0;
550 \qmlclass WorkerScript QDeclarativeWorkerScript
551 \ingroup qml-utility-elements
552 \brief The WorkerScript element enables the use of threads in QML.
554 Use WorkerScript to run operations in a new thread.
555 This is useful for running operations in the background so
556 that the main GUI thread is not blocked.
558 Messages can be passed between the new thread and the parent thread
559 using \l sendMessage() and the \l {WorkerScript::onMessage}{onMessage()} handler.
563 \snippet doc/src/snippets/declarative/workerscript.qml 0
565 The above worker script specifies a JavaScript file, "script.js", that handles
566 the operations to be performed in the new thread. Here is \c script.js:
568 \quotefile doc/src/snippets/declarative/script.js
570 When the user clicks anywhere within the rectangle, \c sendMessage() is
571 called, triggering the \tt WorkerScript.onMessage() handler in
572 \tt script.js. This in turn sends a reply message that is then received
573 by the \tt onMessage() handler of \tt myWorker.
576 \section3 Restrictions
578 Since the \c WorkerScript.onMessage() function is run in a separate thread, the
579 JavaScript file is evaluated in a context separate from the main QML engine. This means
580 that unlike an ordinary JavaScript file that is imported into QML, the \c script.js
581 in the above example cannot access the properties, methods or other attributes
582 of the QML item, nor can it access any context properties set on the QML object
583 through QDeclarativeContext.
585 Additionally, there are restrictions on the types of values that can be passed to and
586 from the worker script. See the sendMessage() documentation for details.
588 \sa {declarative/threading/workerscript}{WorkerScript example},
589 {declarative/threading/threadedlistmodel}{Threaded ListModel example}
591 QDeclarativeWorkerScript::QDeclarativeWorkerScript(QObject *parent)
592 : QObject(parent), m_engine(0), m_scriptId(-1), m_componentComplete(true)
596 QDeclarativeWorkerScript::~QDeclarativeWorkerScript()
598 if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId);
602 \qmlproperty url WorkerScript::source
604 This holds the url of the JavaScript file that implements the
605 \tt WorkerScript.onMessage() handler for threaded operations.
607 QUrl QDeclarativeWorkerScript::source() const
612 void QDeclarativeWorkerScript::setSource(const QUrl &source)
614 if (m_source == source)
620 m_engine->executeUrl(m_scriptId, m_source);
622 emit sourceChanged();
626 \qmlmethod WorkerScript::sendMessage(jsobject message)
628 Sends the given \a message to a worker script handler in another
629 thread. The other worker script handler can receive this message
630 through the onMessage() handler.
632 The \c message object may only contain values of the following
636 \o boolean, number, string
637 \o JavaScript objects and arrays
638 \o ListModel objects (any other type of QObject* is not allowed)
641 All objects and arrays are copied to the \c message. With the exception
642 of ListModel objects, any modifications by the other thread to an object
643 passed in \c message will not be reflected in the original object.
645 void QDeclarativeWorkerScript::sendMessage(QDeclarativeV8Function *args)
648 qWarning("QDeclarativeWorkerScript: Attempt to send message before WorkerScript establishment");
652 v8::Handle<v8::Value> argument = v8::Undefined();
653 if (args->Length() != 0)
654 argument = (*args)[0];
656 m_engine->sendMessage(m_scriptId, QV8Worker::serialize(argument, args->engine()));
659 void QDeclarativeWorkerScript::classBegin()
661 m_componentComplete = false;
664 QDeclarativeWorkerScriptEngine *QDeclarativeWorkerScript::engine()
666 if (m_engine) return m_engine;
667 if (m_componentComplete) {
668 QDeclarativeEngine *engine = qmlEngine(this);
670 qWarning("QDeclarativeWorkerScript: engine() called without qmlEngine() set");
674 m_engine = QDeclarativeEnginePrivate::get(engine)->getWorkerScriptEngine();
675 m_scriptId = m_engine->registerWorkerScript(this);
677 if (m_source.isValid())
678 m_engine->executeUrl(m_scriptId, m_source);
685 void QDeclarativeWorkerScript::componentComplete()
687 m_componentComplete = true;
688 engine(); // Get it started now.
692 \qmlsignal WorkerScript::onMessage(jsobject msg)
694 This handler is called when a message \a msg is received from a worker
695 script in another thread through a call to sendMessage().
698 bool QDeclarativeWorkerScript::event(QEvent *event)
700 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
701 QDeclarativeEngine *engine = qmlEngine(this);
703 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
704 QV8Engine *v8engine = QDeclarativeEnginePrivate::get(engine)->v8engine();
705 v8::HandleScope handle_scope;
706 v8::Context::Scope scope(v8engine->context());
707 v8::Handle<v8::Value> value = QV8Worker::deserialize(workerEvent->data(), v8engine);
708 emit message(QDeclarativeV8Handle::fromHandle(value));
711 } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) {
712 WorkerErrorEvent *workerEvent = static_cast<WorkerErrorEvent *>(event);
713 QDeclarativeEnginePrivate::warning(qmlEngine(this), workerEvent->error());
716 return QObject::event(event);
722 #include <qdeclarativeworkerscript.moc>