1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qqmlenginedebugservice_p.h"
44 #include "qqmldebugstatesdelegate_p.h"
45 #include <private/qqmlboundsignal_p.h>
46 #include <qqmlengine.h>
47 #include <private/qqmlmetatype_p.h>
48 #include <qqmlproperty.h>
49 #include <private/qqmlproperty_p.h>
50 #include <private/qqmlbinding_p.h>
51 #include <private/qqmlcontext_p.h>
52 #include <private/qqmlwatcher_p.h>
53 #include <private/qqmlvaluetype_p.h>
54 #include <private/qqmlvmemetaobject_p.h>
55 #include <private/qqmlexpression_p.h>
57 #include <QtCore/qdebug.h>
58 #include <QtCore/qmetaobject.h>
62 Q_GLOBAL_STATIC(QQmlEngineDebugService, qmlEngineDebugService)
64 QQmlEngineDebugService *QQmlEngineDebugService::instance()
66 return qmlEngineDebugService();
69 QQmlEngineDebugService::QQmlEngineDebugService(QObject *parent)
70 : QQmlDebugService(QLatin1String("QDeclarativeEngine"), 1, parent),
71 m_watch(new QQmlWatcher(this)),
74 QObject::connect(m_watch, SIGNAL(propertyChanged(int,int,QMetaProperty,QVariant)),
75 this, SLOT(propertyChanged(int,int,QMetaProperty,QVariant)));
80 QQmlEngineDebugService::~QQmlEngineDebugService()
82 delete m_statesDelegate;
85 QDataStream &operator<<(QDataStream &ds,
86 const QQmlEngineDebugService::QQmlObjectData &data)
88 ds << data.url << data.lineNumber << data.columnNumber << data.idString
89 << data.objectName << data.objectType << data.objectId << data.contextId;
93 QDataStream &operator>>(QDataStream &ds,
94 QQmlEngineDebugService::QQmlObjectData &data)
96 ds >> data.url >> data.lineNumber >> data.columnNumber >> data.idString
97 >> data.objectName >> data.objectType >> data.objectId >> data.contextId;
101 QDataStream &operator<<(QDataStream &ds,
102 const QQmlEngineDebugService::QQmlObjectProperty &data)
104 ds << (int)data.type << data.name << data.value << data.valueTypeName
105 << data.binding << data.hasNotifySignal;
109 QDataStream &operator>>(QDataStream &ds,
110 QQmlEngineDebugService::QQmlObjectProperty &data)
113 ds >> type >> data.name >> data.value >> data.valueTypeName
114 >> data.binding >> data.hasNotifySignal;
115 data.type = (QQmlEngineDebugService::QQmlObjectProperty::Type)type;
119 static inline bool isSignalPropertyName(const QString &signalName)
121 // see QmlCompiler::isSignalPropertyName
122 return signalName.length() >= 3 && signalName.startsWith(QLatin1String("on")) &&
123 signalName.at(2).isLetter() && signalName.at(2).isUpper();
126 static bool hasValidSignal(QObject *object, const QString &propertyName)
128 if (!isSignalPropertyName(propertyName))
131 QString signalName = propertyName.mid(2);
132 signalName[0] = signalName.at(0).toLower();
134 int sigIdx = QQmlPropertyPrivate::findSignalByName(object->metaObject(), signalName.toLatin1()).methodIndex();
142 QQmlEngineDebugService::QQmlObjectProperty
143 QQmlEngineDebugService::propertyData(QObject *obj, int propIdx)
145 QQmlObjectProperty rv;
147 QMetaProperty prop = obj->metaObject()->property(propIdx);
149 rv.type = QQmlObjectProperty::Unknown;
150 rv.valueTypeName = QString::fromUtf8(prop.typeName());
151 rv.name = QString::fromUtf8(prop.name());
152 rv.hasNotifySignal = prop.hasNotifySignal();
153 QQmlAbstractBinding *binding =
154 QQmlPropertyPrivate::binding(QQmlProperty(obj, rv.name));
156 rv.binding = binding->expression();
158 if (QQmlValueTypeFactory::isValueType(prop.userType())) {
159 rv.type = QQmlObjectProperty::Basic;
160 } else if (QQmlMetaType::isQObject(prop.userType())) {
161 rv.type = QQmlObjectProperty::Object;
162 } else if (QQmlMetaType::isList(prop.userType())) {
163 rv.type = QQmlObjectProperty::List;
167 if (rv.type != QQmlObjectProperty::Unknown && prop.userType() != 0) {
168 value = prop.read(obj);
170 rv.value = valueContents(value);
175 QVariant QQmlEngineDebugService::valueContents(const QVariant &value) const
177 int userType = value.userType();
179 //QObject * is not streamable.
180 //Convert all such instances to a String value
182 if (value.type() == QVariant::List) {
183 QVariantList contents;
184 QVariantList list = value.toList();
185 int count = list.size();
186 for (int i = 0; i < count; i++)
187 contents << valueContents(list.at(i));
191 if (value.type() == QVariant::Map) {
192 QVariantMap contents;
193 QMapIterator<QString, QVariant> i(value.toMap());
194 while (i.hasNext()) {
196 contents.insert(i.key(), valueContents(i.value()));
201 if (QQmlValueTypeFactory::isValueType(userType))
204 if (QQmlMetaType::isQObject(userType)) {
205 QObject *o = QQmlMetaType::toQObject(value);
207 QString name = o->objectName();
209 name = QLatin1String("<unnamed object>");
214 return QLatin1String("<unknown value>");
217 void QQmlEngineDebugService::buildObjectDump(QDataStream &message,
218 QObject *object, bool recur, bool dumpProperties)
220 message << objectData(object);
222 QObjectList children = object->children();
224 int childrenCount = children.count();
225 for (int ii = 0; ii < children.count(); ++ii) {
226 if (qobject_cast<QQmlContext*>(children[ii]) || QQmlBoundSignal::cast(children[ii]))
230 message << childrenCount << recur;
232 QList<QQmlObjectProperty> fakeProperties;
234 for (int ii = 0; ii < children.count(); ++ii) {
235 QObject *child = children.at(ii);
236 if (qobject_cast<QQmlContext*>(child))
238 QQmlBoundSignal *signal = QQmlBoundSignal::cast(child);
242 QQmlObjectProperty prop;
243 prop.type = QQmlObjectProperty::SignalProperty;
244 prop.hasNotifySignal = false;
245 QQmlExpression *expr = signal->expression();
247 prop.value = expr->expression();
248 QObject *scope = expr->scopeObject();
250 QString sig = QLatin1String(scope->metaObject()->method(signal->index()).signature());
251 int lparen = sig.indexOf(QLatin1Char('('));
253 QString methodName = sig.mid(0, lparen);
254 prop.name = QLatin1String("on") + methodName[0].toUpper()
259 fakeProperties << prop;
262 buildObjectDump(message, child, recur, dumpProperties);
264 message << objectData(child);
268 if (!dumpProperties) {
273 QList<int> propertyIndexes;
274 for (int ii = 0; ii < object->metaObject()->propertyCount(); ++ii) {
275 if (object->metaObject()->property(ii).isScriptable())
276 propertyIndexes << ii;
279 message << propertyIndexes.size() + fakeProperties.count();
281 for (int ii = 0; ii < propertyIndexes.size(); ++ii)
282 message << propertyData(object, propertyIndexes.at(ii));
284 for (int ii = 0; ii < fakeProperties.count(); ++ii)
285 message << fakeProperties[ii];
288 void QQmlEngineDebugService::prepareDeferredObjects(QObject *obj)
290 qmlExecuteDeferred(obj);
292 QObjectList children = obj->children();
293 for (int ii = 0; ii < children.count(); ++ii) {
294 QObject *child = children.at(ii);
295 prepareDeferredObjects(child);
300 void QQmlEngineDebugService::buildObjectList(QDataStream &message, QQmlContext *ctxt)
302 QQmlContextData *p = QQmlContextData::get(ctxt);
304 QString ctxtName = ctxt->objectName();
305 int ctxtId = QQmlDebugService::idForObject(ctxt);
307 message << ctxtName << ctxtId;
311 QQmlContextData *child = p->childContexts;
314 child = child->nextChild;
319 child = p->childContexts;
321 buildObjectList(message, child->asQQmlContext());
322 child = child->nextChild;
325 // Clean deleted objects
326 QQmlContextPrivate *ctxtPriv = QQmlContextPrivate::get(ctxt);
327 for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) {
328 if (!ctxtPriv->instances.at(ii)) {
329 ctxtPriv->instances.removeAt(ii);
334 message << ctxtPriv->instances.count();
335 for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) {
336 message << objectData(ctxtPriv->instances.at(ii));
340 void QQmlEngineDebugService::buildStatesList(QQmlContext *ctxt, bool cleanList)
342 if (m_statesDelegate)
343 m_statesDelegate->buildStatesList(ctxt, cleanList);
346 QQmlEngineDebugService::QQmlObjectData
347 QQmlEngineDebugService::objectData(QObject *object)
349 QQmlData *ddata = QQmlData::get(object);
351 if (ddata && ddata->outerContext) {
352 rv.url = ddata->outerContext->url;
353 rv.lineNumber = ddata->lineNumber;
354 rv.columnNumber = ddata->columnNumber;
357 rv.columnNumber = -1;
360 QQmlContext *context = qmlContext(object);
362 QQmlContextData *cdata = QQmlContextData::get(context);
364 rv.idString = cdata->findObjectId(object);
367 rv.objectName = object->objectName();
368 rv.objectId = QQmlDebugService::idForObject(object);
369 rv.contextId = QQmlDebugService::idForObject(qmlContext(object));
371 QQmlType *type = QQmlMetaType::qmlType(object->metaObject());
373 QString typeName = type->qmlTypeName();
374 int lastSlash = typeName.lastIndexOf(QLatin1Char('/'));
375 rv.objectType = lastSlash < 0 ? typeName : typeName.mid(lastSlash+1);
377 rv.objectType = QString::fromUtf8(object->metaObject()->className());
378 int marker = rv.objectType.indexOf(QLatin1String("_QMLTYPE_"));
380 rv.objectType = rv.objectType.left(marker);
386 void QQmlEngineDebugService::messageReceived(const QByteArray &message)
388 QMetaObject::invokeMethod(this, "processMessage", Qt::QueuedConnection, Q_ARG(QByteArray, message));
391 void QQmlEngineDebugService::processMessage(const QByteArray &message)
393 QDataStream ds(message);
398 if (type == "LIST_ENGINES") {
403 QDataStream rs(&reply, QIODevice::WriteOnly);
404 rs << QByteArray("LIST_ENGINES_R");
405 rs << queryId << m_engines.count();
407 for (int ii = 0; ii < m_engines.count(); ++ii) {
408 QQmlEngine *engine = m_engines.at(ii);
410 QString engineName = engine->objectName();
411 int engineId = QQmlDebugService::idForObject(engine);
413 rs << engineName << engineId;
417 } else if (type == "LIST_OBJECTS") {
420 ds >> queryId >> engineId;
423 qobject_cast<QQmlEngine *>(QQmlDebugService::objectForId(engineId));
426 QDataStream rs(&reply, QIODevice::WriteOnly);
427 rs << QByteArray("LIST_OBJECTS_R") << queryId;
430 buildObjectList(rs, engine->rootContext());
431 buildStatesList(engine->rootContext(), true);
435 } else if (type == "FETCH_OBJECT") {
439 bool dumpProperties = true;
441 ds >> queryId >> objectId >> recurse >> dumpProperties;
443 QObject *object = QQmlDebugService::objectForId(objectId);
446 QDataStream rs(&reply, QIODevice::WriteOnly);
447 rs << QByteArray("FETCH_OBJECT_R") << queryId;
451 prepareDeferredObjects(object);
452 buildObjectDump(rs, object, recurse, dumpProperties);
456 } else if (type == "WATCH_OBJECT") {
460 ds >> queryId >> objectId;
461 bool ok = m_watch->addWatch(queryId, objectId);
464 QDataStream rs(&reply, QIODevice::WriteOnly);
465 rs << QByteArray("WATCH_OBJECT_R") << queryId << ok;
468 } else if (type == "WATCH_PROPERTY") {
473 ds >> queryId >> objectId >> property;
474 bool ok = m_watch->addWatch(queryId, objectId, property);
477 QDataStream rs(&reply, QIODevice::WriteOnly);
478 rs << QByteArray("WATCH_PROPERTY_R") << queryId << ok;
481 } else if (type == "WATCH_EXPR_OBJECT") {
486 ds >> queryId >> debugId >> expr;
487 bool ok = m_watch->addWatch(queryId, debugId, expr);
490 QDataStream rs(&reply, QIODevice::WriteOnly);
491 rs << QByteArray("WATCH_EXPR_OBJECT_R") << queryId << ok;
493 } else if (type == "NO_WATCH") {
497 m_watch->removeWatch(queryId);
498 } else if (type == "EVAL_EXPRESSION") {
503 ds >> queryId >> objectId >> expr;
505 QObject *object = QQmlDebugService::objectForId(objectId);
506 QQmlContext *context = qmlContext(object);
508 if (object && context) {
509 QQmlExpression exprObj(context, object, expr);
510 bool undefined = false;
511 QVariant value = exprObj.evaluate(&undefined);
513 result = QLatin1String("<undefined>");
515 result = valueContents(value);
517 result = QLatin1String("<unknown context>");
521 QDataStream rs(&reply, QIODevice::WriteOnly);
522 rs << QByteArray("EVAL_EXPRESSION_R") << queryId << result;
525 } else if (type == "SET_BINDING") {
527 QString propertyName;
532 ds >> objectId >> propertyName >> expr >> isLiteralValue;
533 if (!ds.atEnd()) { // backward compatibility from 2.1, 2.2
534 ds >> filename >> line;
536 setBinding(objectId, propertyName, expr, isLiteralValue, filename, line);
537 } else if (type == "RESET_BINDING") {
539 QString propertyName;
540 ds >> objectId >> propertyName;
541 resetBinding(objectId, propertyName);
542 } else if (type == "SET_METHOD_BODY") {
546 ds >> objectId >> methodName >> methodBody;
547 setMethodBody(objectId, methodName, methodBody);
551 void QQmlEngineDebugService::setBinding(int objectId,
552 const QString &propertyName,
553 const QVariant &expression,
559 QObject *object = objectForId(objectId);
560 QQmlContext *context = qmlContext(object);
562 if (object && context) {
563 QQmlProperty property(object, propertyName, context);
564 if (property.isValid()) {
566 bool inBaseState = true;
567 if (m_statesDelegate) {
568 m_statesDelegate->updateBinding(context, property, expression, isLiteralValue,
569 filename, line, column, &inBaseState);
573 if (isLiteralValue) {
574 property.write(expression);
575 } else if (hasValidSignal(object, propertyName)) {
576 QQmlExpression *qmlExpression = new QQmlExpression(context, object, expression.toString());
577 QQmlPropertyPrivate::setSignalExpression(property, qmlExpression);
578 qmlExpression->setSourceLocation(filename, line, column);
579 } else if (property.isProperty()) {
580 QQmlBinding *binding = new QQmlBinding(expression.toString(), object, context);
581 binding->setTarget(property);
582 binding->setSourceLocation(filename, line, column);
583 binding->setNotifyOnValueChanged(true);
584 QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::setBinding(property, binding);
586 oldBinding->destroy();
589 qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object;
594 // not a valid property
596 if (m_statesDelegate)
597 ok = m_statesDelegate->setBindingForInvalidProperty(object, propertyName, expression, isLiteralValue);
599 qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object;
604 void QQmlEngineDebugService::resetBinding(int objectId, const QString &propertyName)
606 QObject *object = objectForId(objectId);
607 QQmlContext *context = qmlContext(object);
609 if (object && context) {
610 if (object->property(propertyName.toLatin1()).isValid()) {
611 QQmlProperty property(object, propertyName);
612 QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::binding(property);
614 QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::setBinding(property, 0);
616 oldBinding->destroy();
618 if (property.isResettable()) {
619 // Note: this will reset the property in any case, without regard to states
620 // Right now almost no QQuickItem has reset methods for its properties (with the
621 // notable exception of QQuickAnchors), so this is not a big issue
622 // later on, setBinding does take states into account
625 // overwrite with default value
626 if (QQmlType *objType = QQmlMetaType::qmlType(object->metaObject())) {
627 if (QObject *emptyObject = objType->create()) {
628 if (emptyObject->property(propertyName.toLatin1()).isValid()) {
629 QVariant defaultValue = QQmlProperty(emptyObject, propertyName).read();
630 if (defaultValue.isValid()) {
631 setBinding(objectId, propertyName, defaultValue, true);
638 } else if (hasValidSignal(object, propertyName)) {
639 QQmlProperty property(object, propertyName, context);
640 QQmlPropertyPrivate::setSignalExpression(property, 0);
642 if (m_statesDelegate)
643 m_statesDelegate->resetBindingForInvalidProperty(object, propertyName);
648 void QQmlEngineDebugService::setMethodBody(int objectId, const QString &method, const QString &body)
650 QObject *object = objectForId(objectId);
651 QQmlContext *context = qmlContext(object);
652 if (!object || !context || !context->engine())
654 QQmlContextData *contextData = QQmlContextData::get(context);
658 QQmlPropertyData dummy;
659 QQmlPropertyData *prop =
660 QQmlPropertyCache::property(context->engine(), object, method, dummy);
662 if (!prop || !prop->isVMEFunction())
665 QMetaMethod metaMethod = object->metaObject()->method(prop->coreIndex);
666 QList<QByteArray> paramNames = metaMethod.parameterNames();
669 for (int ii = 0; ii < paramNames.count(); ++ii) {
670 if (ii != 0) paramStr.append(QLatin1String(","));
671 paramStr.append(QString::fromUtf8(paramNames.at(ii)));
674 QString jsfunction = QLatin1String("(function ") + method + QLatin1String("(") + paramStr +
675 QLatin1String(") {");
677 jsfunction += QLatin1String("\n})");
679 QQmlVMEMetaObject *vmeMetaObject =
680 static_cast<QQmlVMEMetaObject*>(QObjectPrivate::get(object)->metaObject);
681 Q_ASSERT(vmeMetaObject); // the fact we found the property above should guarentee this
683 int lineNumber = vmeMetaObject->vmeMethodLineNumber(prop->coreIndex);
684 vmeMetaObject->setVmeMethod(prop->coreIndex, QQmlExpressionPrivate::evalFunction(contextData, object, jsfunction, contextData->url.toString(), lineNumber));
687 void QQmlEngineDebugService::propertyChanged(int id, int objectId, const QMetaProperty &property, const QVariant &value)
690 QDataStream rs(&reply, QIODevice::WriteOnly);
692 rs << QByteArray("UPDATE_WATCH") << id << objectId << QByteArray(property.name()) << valueContents(value);
697 void QQmlEngineDebugService::addEngine(QQmlEngine *engine)
700 Q_ASSERT(!m_engines.contains(engine));
702 m_engines.append(engine);
705 void QQmlEngineDebugService::remEngine(QQmlEngine *engine)
708 Q_ASSERT(m_engines.contains(engine));
710 m_engines.removeAll(engine);
713 void QQmlEngineDebugService::objectCreated(QQmlEngine *engine, QObject *object)
716 Q_ASSERT(m_engines.contains(engine));
718 int engineId = QQmlDebugService::idForObject(engine);
719 int objectId = QQmlDebugService::idForObject(object);
722 QDataStream rs(&reply, QIODevice::WriteOnly);
724 rs << QByteArray("OBJECT_CREATED") << engineId << objectId;
728 void QQmlEngineDebugService::setStatesDelegate(QQmlDebugStatesDelegate *delegate)
730 m_statesDelegate = delegate;