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>
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 QDeclarativeScriptParser::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::IdlePriority);
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));
485 QDeclarativeWorkerScriptEnginePrivate::WorkerScript::WorkerScript()
486 : id(-1), initialized(false), owner(0)
490 QDeclarativeWorkerScriptEnginePrivate::WorkerScript::~WorkerScript()
492 qPersistentDispose(object);
495 int QDeclarativeWorkerScriptEngine::registerWorkerScript(QDeclarativeWorkerScript *owner)
497 typedef QDeclarativeWorkerScriptEnginePrivate::WorkerScript WorkerScript;
498 WorkerScript *script = new WorkerScript;
500 script->id = d->m_nextId++;
501 script->owner = owner;
504 d->workers.insert(script->id, script);
510 void QDeclarativeWorkerScriptEngine::removeWorkerScript(int id)
512 QCoreApplication::postEvent(d, new WorkerRemoveEvent(id));
515 void QDeclarativeWorkerScriptEngine::executeUrl(int id, const QUrl &url)
517 QCoreApplication::postEvent(d, new WorkerLoadEvent(id, url));
520 void QDeclarativeWorkerScriptEngine::sendMessage(int id, const QByteArray &data)
522 QCoreApplication::postEvent(d, new WorkerDataEvent(id, data));
525 void QDeclarativeWorkerScriptEngine::run()
529 v8::Isolate *isolate = v8::Isolate::New();
532 d->workerEngine = new QDeclarativeWorkerScriptEnginePrivate::WorkerEngine(d);
533 d->workerEngine->init();
541 qDeleteAll(d->workers);
544 delete d->workerEngine; d->workerEngine = 0;
552 \qmlclass WorkerScript QDeclarativeWorkerScript
553 \ingroup qml-utility-elements
554 \brief The WorkerScript element enables the use of threads in QML.
556 Use WorkerScript to run operations in a new thread.
557 This is useful for running operations in the background so
558 that the main GUI thread is not blocked.
560 Messages can be passed between the new thread and the parent thread
561 using \l sendMessage() and the \l {WorkerScript::onMessage}{onMessage()} handler.
565 \snippet doc/src/snippets/declarative/workerscript.qml 0
567 The above worker script specifies a JavaScript file, "script.js", that handles
568 the operations to be performed in the new thread. Here is \c script.js:
570 \quotefile doc/src/snippets/declarative/script.js
572 When the user clicks anywhere within the rectangle, \c sendMessage() is
573 called, triggering the \tt WorkerScript.onMessage() handler in
574 \tt script.js. This in turn sends a reply message that is then received
575 by the \tt onMessage() handler of \tt myWorker.
578 \section3 Restrictions
580 Since the \c WorkerScript.onMessage() function is run in a separate thread, the
581 JavaScript file is evaluated in a context separate from the main QML engine. This means
582 that unlike an ordinary JavaScript file that is imported into QML, the \c script.js
583 in the above example cannot access the properties, methods or other attributes
584 of the QML item, nor can it access any context properties set on the QML object
585 through QDeclarativeContext.
587 Additionally, there are restrictions on the types of values that can be passed to and
588 from the worker script. See the sendMessage() documentation for details.
590 \sa {declarative/threading/workerscript}{WorkerScript example},
591 {declarative/threading/threadedlistmodel}{Threaded ListModel example}
593 QDeclarativeWorkerScript::QDeclarativeWorkerScript(QObject *parent)
594 : QObject(parent), m_engine(0), m_scriptId(-1), m_componentComplete(true)
598 QDeclarativeWorkerScript::~QDeclarativeWorkerScript()
600 if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId);
604 \qmlproperty url WorkerScript::source
606 This holds the url of the JavaScript file that implements the
607 \tt WorkerScript.onMessage() handler for threaded operations.
609 QUrl QDeclarativeWorkerScript::source() const
614 void QDeclarativeWorkerScript::setSource(const QUrl &source)
616 if (m_source == source)
622 m_engine->executeUrl(m_scriptId, m_source);
624 emit sourceChanged();
628 \qmlmethod WorkerScript::sendMessage(jsobject message)
630 Sends the given \a message to a worker script handler in another
631 thread. The other worker script handler can receive this message
632 through the onMessage() handler.
634 The \c message object may only contain values of the following
638 \o boolean, number, string
639 \o JavaScript objects and arrays
640 \o ListModel objects (any other type of QObject* is not allowed)
643 All objects and arrays are copied to the \c message. With the exception
644 of ListModel objects, any modifications by the other thread to an object
645 passed in \c message will not be reflected in the original object.
647 void QDeclarativeWorkerScript::sendMessage(QDeclarativeV8Function *args)
650 qWarning("QDeclarativeWorkerScript: Attempt to send message before WorkerScript establishment");
654 v8::Handle<v8::Value> argument = v8::Undefined();
655 if (args->Length() != 0)
656 argument = (*args)[0];
658 m_engine->sendMessage(m_scriptId, QV8Worker::serialize(argument, args->engine()));
661 void QDeclarativeWorkerScript::classBegin()
663 m_componentComplete = false;
666 QDeclarativeWorkerScriptEngine *QDeclarativeWorkerScript::engine()
668 if (m_engine) return m_engine;
669 if (m_componentComplete) {
670 QDeclarativeEngine *engine = qmlEngine(this);
672 qWarning("QDeclarativeWorkerScript: engine() called without qmlEngine() set");
676 m_engine = QDeclarativeEnginePrivate::get(engine)->getWorkerScriptEngine();
677 m_scriptId = m_engine->registerWorkerScript(this);
679 if (m_source.isValid())
680 m_engine->executeUrl(m_scriptId, m_source);
687 void QDeclarativeWorkerScript::componentComplete()
689 m_componentComplete = true;
690 engine(); // Get it started now.
694 \qmlsignal WorkerScript::onMessage(jsobject msg)
696 This handler is called when a message \a msg is received from a worker
697 script in another thread through a call to sendMessage().
700 bool QDeclarativeWorkerScript::event(QEvent *event)
702 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
703 QDeclarativeEngine *engine = qmlEngine(this);
705 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
706 QV8Engine *v8engine = QDeclarativeEnginePrivate::get(engine)->v8engine();
707 v8::HandleScope handle_scope;
708 v8::Context::Scope scope(v8engine->context());
709 v8::Handle<v8::Value> value = QV8Worker::deserialize(workerEvent->data(), v8engine);
710 emit message(QDeclarativeV8Handle::fromHandle(value));
713 } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) {
714 WorkerErrorEvent *workerEvent = static_cast<WorkerErrorEvent *>(event);
715 QDeclarativeEnginePrivate::warning(qmlEngine(this), workerEvent->error());
718 return QObject::event(event);
724 #include <qdeclarativeworkerscript.moc>