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 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
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"
64 class WorkerDataEvent : public QEvent
67 enum Type { WorkerData = QEvent::User };
69 WorkerDataEvent(int workerId, const QVariant &data);
70 virtual ~WorkerDataEvent();
73 QVariant data() const;
80 class WorkerLoadEvent : public QEvent
83 enum Type { WorkerLoad = WorkerDataEvent::WorkerData + 1 };
85 WorkerLoadEvent(int workerId, const QUrl &url);
95 class WorkerRemoveEvent : public QEvent
98 enum Type { WorkerRemove = WorkerLoadEvent::WorkerLoad + 1 };
100 WorkerRemoveEvent(int workerId);
102 int workerId() const;
108 class WorkerErrorEvent : public QEvent
111 enum Type { WorkerError = WorkerRemoveEvent::WorkerRemove + 1 };
113 WorkerErrorEvent(const QDeclarativeError &error);
115 QDeclarativeError error() const;
118 QDeclarativeError m_error;
121 class QDeclarativeWorkerScriptEnginePrivate : public QObject
125 enum WorkerEventTypes {
126 WorkerDestroyEvent = QEvent::User + 100
129 QDeclarativeWorkerScriptEnginePrivate(QDeclarativeEngine *eng);
131 struct ScriptEngine : public QDeclarativeScriptEngine
133 ScriptEngine(QDeclarativeWorkerScriptEnginePrivate *parent) : QDeclarativeScriptEngine(0), p(parent), accessManager(0) {}
134 ~ScriptEngine() { delete accessManager; }
135 QDeclarativeWorkerScriptEnginePrivate *p;
136 QNetworkAccessManager *accessManager;
138 virtual QNetworkAccessManager *networkAccessManager() {
139 if (!accessManager) {
140 if (p->qmlengine && p->qmlengine->networkAccessManagerFactory()) {
141 accessManager = p->qmlengine->networkAccessManagerFactory()->create(this);
143 accessManager = new QNetworkAccessManager(this);
146 return accessManager;
149 ScriptEngine *workerEngine;
150 static QDeclarativeWorkerScriptEnginePrivate *get(QScriptEngine *e) {
151 return static_cast<ScriptEngine *>(e)->p;
154 QDeclarativeEngine *qmlengine;
157 QWaitCondition m_wait;
159 struct WorkerScript {
165 QDeclarativeWorkerScript *owner;
168 QScriptValue callback;
171 QHash<int, WorkerScript *> workers;
172 QScriptValue getWorker(int);
176 static QVariant scriptValueToVariant(const QScriptValue &);
177 static QScriptValue variantToScriptValue(const QVariant &, QScriptEngine *);
179 static QScriptValue onMessage(QScriptContext *ctxt, QScriptEngine *engine);
180 static QScriptValue sendMessage(QScriptContext *ctxt, QScriptEngine *engine);
186 virtual bool event(QEvent *);
189 void processMessage(int, const QVariant &);
190 void processLoad(int, const QUrl &);
191 void reportScriptException(WorkerScript *);
194 QDeclarativeWorkerScriptEnginePrivate::QDeclarativeWorkerScriptEnginePrivate(QDeclarativeEngine *engine)
195 : workerEngine(0), qmlengine(engine), m_nextId(0)
199 QScriptValue QDeclarativeWorkerScriptEnginePrivate::onMessage(QScriptContext *ctxt, QScriptEngine *engine)
201 QDeclarativeWorkerScriptEnginePrivate *p = QDeclarativeWorkerScriptEnginePrivate::get(engine);
203 int id = ctxt->thisObject().data().toVariant().toInt();
205 WorkerScript *script = p->workers.value(id);
207 return engine->undefinedValue();
209 if (ctxt->argumentCount() >= 1)
210 script->callback = ctxt->argument(0);
212 return script->callback;
215 QScriptValue QDeclarativeWorkerScriptEnginePrivate::sendMessage(QScriptContext *ctxt, QScriptEngine *engine)
217 if (!ctxt->argumentCount())
218 return engine->undefinedValue();
220 QDeclarativeWorkerScriptEnginePrivate *p = QDeclarativeWorkerScriptEnginePrivate::get(engine);
222 int id = ctxt->thisObject().data().toVariant().toInt();
224 WorkerScript *script = p->workers.value(id);
226 return engine->undefinedValue();
228 QMutexLocker(&p->m_lock);
231 QCoreApplication::postEvent(script->owner,
232 new WorkerDataEvent(0, scriptValueToVariant(ctxt->argument(0))));
234 return engine->undefinedValue();
237 QScriptValue QDeclarativeWorkerScriptEnginePrivate::getWorker(int id)
239 QHash<int, WorkerScript *>::ConstIterator iter = workers.find(id);
241 if (iter == workers.end())
242 return workerEngine->nullValue();
244 WorkerScript *script = *iter;
245 if (!script->initialized) {
247 script->initialized = true;
248 script->object = workerEngine->newObject();
250 QScriptValue api = workerEngine->newObject();
251 api.setData(script->id);
253 api.setProperty(QLatin1String("onMessage"), workerEngine->newFunction(onMessage),
254 QScriptValue::PropertyGetter | QScriptValue::PropertySetter);
255 api.setProperty(QLatin1String("sendMessage"), workerEngine->newFunction(sendMessage));
257 script->object.setProperty(QLatin1String("WorkerScript"), api);
260 return script->object;
263 bool QDeclarativeWorkerScriptEnginePrivate::event(QEvent *event)
265 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
266 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
267 processMessage(workerEvent->workerId(), workerEvent->data());
269 } else if (event->type() == (QEvent::Type)WorkerLoadEvent::WorkerLoad) {
270 WorkerLoadEvent *workerEvent = static_cast<WorkerLoadEvent *>(event);
271 processLoad(workerEvent->workerId(), workerEvent->url());
273 } else if (event->type() == (QEvent::Type)WorkerDestroyEvent) {
277 return QObject::event(event);
281 void QDeclarativeWorkerScriptEnginePrivate::processMessage(int id, const QVariant &data)
283 WorkerScript *script = workers.value(id);
287 if (script->callback.isFunction()) {
288 QScriptValue args = workerEngine->newArray(1);
289 args.setProperty(0, variantToScriptValue(data, workerEngine));
291 script->callback.call(script->object, args);
293 if (workerEngine->hasUncaughtException()) {
294 reportScriptException(script);
295 workerEngine->clearExceptions();
300 void QDeclarativeWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
302 if (url.isRelative())
305 QString fileName = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url);
308 if (f.open(QIODevice::ReadOnly)) {
309 QByteArray data = f.readAll();
310 QString sourceCode = QString::fromUtf8(data);
312 QScriptValue activation = getWorker(id);
314 QScriptContext *ctxt = QScriptDeclarativeClass::pushCleanContext(workerEngine);
315 QScriptValue urlContext = workerEngine->newObject();
316 urlContext.setData(QScriptValue(workerEngine, url.toString()));
317 ctxt->pushScope(urlContext);
318 ctxt->pushScope(activation);
319 ctxt->setActivationObject(activation);
320 QDeclarativeScriptParser::extractPragmas(sourceCode);
322 workerEngine->baseUrl = url;
323 workerEngine->evaluate(sourceCode);
325 WorkerScript *script = workers.value(id);
327 script->source = url;
328 if (workerEngine->hasUncaughtException()) {
329 reportScriptException(script);
330 workerEngine->clearExceptions();
334 workerEngine->popContext();
336 qWarning().nospace() << "WorkerScript: Cannot find source file " << url.toString();
340 void QDeclarativeWorkerScriptEnginePrivate::reportScriptException(WorkerScript *script)
342 if (!script || !workerEngine->hasUncaughtException())
345 QDeclarativeError error;
346 QDeclarativeExpressionPrivate::exceptionToError(workerEngine, error);
347 error.setUrl(script->source);
349 QDeclarativeWorkerScriptEnginePrivate *p = QDeclarativeWorkerScriptEnginePrivate::get(workerEngine);
351 QMutexLocker(&p->m_lock);
353 QCoreApplication::postEvent(script->owner, new WorkerErrorEvent(error));
356 QVariant QDeclarativeWorkerScriptEnginePrivate::scriptValueToVariant(const QScriptValue &value)
358 if (value.isBool()) {
359 return QVariant(value.toBool());
360 } else if (value.isString()) {
361 return QVariant(value.toString());
362 } else if (value.isNumber()) {
363 return QVariant((qreal)value.toNumber());
364 } else if (value.isDate()) {
365 return QVariant(value.toDateTime());
367 } else if (value.isRegExp()) {
368 return QVariant(value.toRegExp());
370 } else if (value.isArray()) {
373 quint32 length = (quint32)value.property(QLatin1String("length")).toNumber();
375 for (quint32 ii = 0; ii < length; ++ii) {
376 QVariant v = scriptValueToVariant(value.property(ii));
380 return QVariant(list);
381 } else if (value.isQObject()) {
382 QDeclarativeListModel *lm = qobject_cast<QDeclarativeListModel *>(value.toQObject());
384 QDeclarativeListModelWorkerAgent *agent = lm->agent();
386 QDeclarativeListModelWorkerAgent::VariantRef v(agent);
387 return QVariant::fromValue(v);
392 // No other QObject's are allowed to be sent
395 } else if (value.isObject()) {
398 QScriptValueIterator iter(value);
400 while (iter.hasNext()) {
402 hash.insert(iter.name(), scriptValueToVariant(iter.value()));
405 return QVariant(hash);
412 QScriptValue QDeclarativeWorkerScriptEnginePrivate::variantToScriptValue(const QVariant &value, QScriptEngine *engine)
414 if (value.userType() == QVariant::Bool) {
415 return QScriptValue(value.toBool());
416 } else if (value.userType() == QVariant::String) {
417 return QScriptValue(value.toString());
418 } else if (value.userType() == QMetaType::QReal) {
419 return QScriptValue(value.toReal());
420 } else if (value.userType() == QVariant::DateTime) {
421 return engine->newDate(value.toDateTime());
423 } else if (value.userType() == QVariant::RegExp) {
424 return engine->newRegExp(value.toRegExp());
426 } else if (value.userType() == qMetaTypeId<QDeclarativeListModelWorkerAgent::VariantRef>()) {
427 QDeclarativeListModelWorkerAgent::VariantRef vr = qvariant_cast<QDeclarativeListModelWorkerAgent::VariantRef>(value);
428 if (vr.a->scriptEngine() == 0)
429 vr.a->setScriptEngine(engine);
430 else if (vr.a->scriptEngine() != engine)
431 return engine->nullValue();
432 QScriptValue o = engine->newQObject(vr.a);
433 o.setData(engine->newVariant(value)); // Keeps the agent ref so that it is cleaned up on gc
435 } else if (value.userType() == QMetaType::QVariantList) {
436 QVariantList list = qvariant_cast<QVariantList>(value);
437 QScriptValue rv = engine->newArray(list.count());
439 for (quint32 ii = 0; ii < quint32(list.count()); ++ii)
440 rv.setProperty(ii, variantToScriptValue(list.at(ii), engine));
443 } else if (value.userType() == QMetaType::QVariantHash) {
445 QVariantHash hash = qvariant_cast<QVariantHash>(value);
447 QScriptValue rv = engine->newObject();
449 for (QVariantHash::ConstIterator iter = hash.begin(); iter != hash.end(); ++iter)
450 rv.setProperty(iter.key(), variantToScriptValue(iter.value(), engine));
454 return engine->nullValue();
458 WorkerDataEvent::WorkerDataEvent(int workerId, const QVariant &data)
459 : QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data)
463 WorkerDataEvent::~WorkerDataEvent()
467 int WorkerDataEvent::workerId() const
472 QVariant WorkerDataEvent::data() const
477 WorkerLoadEvent::WorkerLoadEvent(int workerId, const QUrl &url)
478 : QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url)
482 int WorkerLoadEvent::workerId() const
487 QUrl WorkerLoadEvent::url() const
492 WorkerRemoveEvent::WorkerRemoveEvent(int workerId)
493 : QEvent((QEvent::Type)WorkerRemove), m_id(workerId)
497 int WorkerRemoveEvent::workerId() const
502 WorkerErrorEvent::WorkerErrorEvent(const QDeclarativeError &error)
503 : QEvent((QEvent::Type)WorkerError), m_error(error)
507 QDeclarativeError WorkerErrorEvent::error() const
512 QDeclarativeWorkerScriptEngine::QDeclarativeWorkerScriptEngine(QDeclarativeEngine *parent)
513 : QThread(parent), d(new QDeclarativeWorkerScriptEnginePrivate(parent))
516 connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection);
517 start(QThread::IdlePriority);
518 d->m_wait.wait(&d->m_lock);
519 d->moveToThread(this);
523 QDeclarativeWorkerScriptEngine::~QDeclarativeWorkerScriptEngine()
526 qDeleteAll(d->workers);
528 QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QDeclarativeWorkerScriptEnginePrivate::WorkerDestroyEvent));
535 QDeclarativeWorkerScriptEnginePrivate::WorkerScript::WorkerScript()
536 : id(-1), initialized(false), owner(0)
540 int QDeclarativeWorkerScriptEngine::registerWorkerScript(QDeclarativeWorkerScript *owner)
542 QDeclarativeWorkerScriptEnginePrivate::WorkerScript *script = new QDeclarativeWorkerScriptEnginePrivate::WorkerScript;
543 script->id = d->m_nextId++;
544 script->owner = owner;
547 d->workers.insert(script->id, script);
553 void QDeclarativeWorkerScriptEngine::removeWorkerScript(int id)
555 QCoreApplication::postEvent(d, new WorkerRemoveEvent(id));
558 void QDeclarativeWorkerScriptEngine::executeUrl(int id, const QUrl &url)
560 QCoreApplication::postEvent(d, new WorkerLoadEvent(id, url));
563 void QDeclarativeWorkerScriptEngine::sendMessage(int id, const QVariant &data)
565 QCoreApplication::postEvent(d, new WorkerDataEvent(id, data));
568 void QDeclarativeWorkerScriptEngine::run()
572 d->workerEngine = new QDeclarativeWorkerScriptEnginePrivate::ScriptEngine(d);
580 delete d->workerEngine; d->workerEngine = 0;
585 \qmlclass WorkerScript QDeclarativeWorkerScript
586 \ingroup qml-utility-elements
587 \brief The WorkerScript element enables the use of threads in QML.
589 Use WorkerScript to run operations in a new thread.
590 This is useful for running operations in the background so
591 that the main GUI thread is not blocked.
593 Messages can be passed between the new thread and the parent thread
594 using \l sendMessage() and the \l {WorkerScript::onMessage}{onMessage()} handler.
598 \snippet doc/src/snippets/declarative/workerscript.qml 0
600 The above worker script specifies a JavaScript file, "script.js", that handles
601 the operations to be performed in the new thread. Here is \c script.js:
603 \quotefile doc/src/snippets/declarative/script.js
605 When the user clicks anywhere within the rectangle, \c sendMessage() is
606 called, triggering the \tt WorkerScript.onMessage() handler in
607 \tt script.js. This in turn sends a reply message that is then received
608 by the \tt onMessage() handler of \tt myWorker.
611 \section3 Restrictions
613 Since the \c WorkerScript.onMessage() function is run in a separate thread, the
614 JavaScript file is evaluated in a context separate from the main QML engine. This means
615 that unlike an ordinary JavaScript file that is imported into QML, the \c script.js
616 in the above example cannot access the properties, methods or other attributes
617 of the QML item, nor can it access any context properties set on the QML object
618 through QDeclarativeContext.
620 Additionally, there are restrictions on the types of values that can be passed to and
621 from the worker script. See the sendMessage() documentation for details.
623 \sa {declarative/threading/workerscript}{WorkerScript example},
624 {declarative/threading/threadedlistmodel}{Threaded ListModel example}
626 QDeclarativeWorkerScript::QDeclarativeWorkerScript(QObject *parent)
627 : QObject(parent), m_engine(0), m_scriptId(-1), m_componentComplete(true)
631 QDeclarativeWorkerScript::~QDeclarativeWorkerScript()
633 if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId);
637 \qmlproperty url WorkerScript::source
639 This holds the url of the JavaScript file that implements the
640 \tt WorkerScript.onMessage() handler for threaded operations.
642 QUrl QDeclarativeWorkerScript::source() const
647 void QDeclarativeWorkerScript::setSource(const QUrl &source)
649 if (m_source == source)
655 m_engine->executeUrl(m_scriptId, m_source);
657 emit sourceChanged();
661 \qmlmethod WorkerScript::sendMessage(jsobject message)
663 Sends the given \a message to a worker script handler in another
664 thread. The other worker script handler can receive this message
665 through the onMessage() handler.
667 The \c message object may only contain values of the following
671 \o boolean, number, string
672 \o JavaScript objects and arrays
673 \o ListModel objects (any other type of QObject* is not allowed)
676 All objects and arrays are copied to the \c message. With the exception
677 of ListModel objects, any modifications by the other thread to an object
678 passed in \c message will not be reflected in the original object.
680 void QDeclarativeWorkerScript::sendMessage(const QScriptValue &message)
683 qWarning("QDeclarativeWorkerScript: Attempt to send message before WorkerScript establishment");
687 m_engine->sendMessage(m_scriptId, QDeclarativeWorkerScriptEnginePrivate::scriptValueToVariant(message));
690 void QDeclarativeWorkerScript::classBegin()
692 m_componentComplete = false;
695 QDeclarativeWorkerScriptEngine *QDeclarativeWorkerScript::engine()
697 if (m_engine) return m_engine;
698 if (m_componentComplete) {
699 QDeclarativeEngine *engine = qmlEngine(this);
701 qWarning("QDeclarativeWorkerScript: engine() called without qmlEngine() set");
705 m_engine = QDeclarativeEnginePrivate::get(engine)->getWorkerScriptEngine();
706 m_scriptId = m_engine->registerWorkerScript(this);
708 if (m_source.isValid())
709 m_engine->executeUrl(m_scriptId, m_source);
716 void QDeclarativeWorkerScript::componentComplete()
718 m_componentComplete = true;
719 engine(); // Get it started now.
723 \qmlsignal WorkerScript::onMessage(jsobject msg)
725 This handler is called when a message \a msg is received from a worker
726 script in another thread through a call to sendMessage().
729 bool QDeclarativeWorkerScript::event(QEvent *event)
731 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
732 QDeclarativeEngine *engine = qmlEngine(this);
734 QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine);
735 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
737 QDeclarativeWorkerScriptEnginePrivate::variantToScriptValue(workerEvent->data(), scriptEngine);
741 } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) {
742 WorkerErrorEvent *workerEvent = static_cast<WorkerErrorEvent *>(event);
743 QDeclarativeEnginePrivate::warning(qmlEngine(this), workerEvent->error());
746 return QObject::event(event);
752 #include <qdeclarativeworkerscript.moc>