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(QStringLiteral("QmlDebugger"), 2, 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
94 QDataStream &operator>>(QDataStream &ds,
95 QQmlEngineDebugService::QQmlObjectData &data)
97 ds >> data.url >> data.lineNumber >> data.columnNumber >> data.idString
98 >> data.objectName >> data.objectType >> data.objectId >> data.contextId
103 QDataStream &operator<<(QDataStream &ds,
104 const QQmlEngineDebugService::QQmlObjectProperty &data)
106 ds << (int)data.type << data.name << data.value << data.valueTypeName
107 << data.binding << data.hasNotifySignal;
111 QDataStream &operator>>(QDataStream &ds,
112 QQmlEngineDebugService::QQmlObjectProperty &data)
115 ds >> type >> data.name >> data.value >> data.valueTypeName
116 >> data.binding >> data.hasNotifySignal;
117 data.type = (QQmlEngineDebugService::QQmlObjectProperty::Type)type;
121 static inline bool isSignalPropertyName(const QString &signalName)
123 // see QmlCompiler::isSignalPropertyName
124 return signalName.length() >= 3 && signalName.startsWith(QLatin1String("on")) &&
125 signalName.at(2).isLetter() && signalName.at(2).isUpper();
128 static bool hasValidSignal(QObject *object, const QString &propertyName)
130 if (!isSignalPropertyName(propertyName))
133 QString signalName = propertyName.mid(2);
134 signalName[0] = signalName.at(0).toLower();
136 int sigIdx = QQmlPropertyPrivate::findSignalByName(object->metaObject(), signalName.toLatin1()).methodIndex();
144 QQmlEngineDebugService::QQmlObjectProperty
145 QQmlEngineDebugService::propertyData(QObject *obj, int propIdx)
147 QQmlObjectProperty rv;
149 QMetaProperty prop = obj->metaObject()->property(propIdx);
151 rv.type = QQmlObjectProperty::Unknown;
152 rv.valueTypeName = QString::fromUtf8(prop.typeName());
153 rv.name = QString::fromUtf8(prop.name());
154 rv.hasNotifySignal = prop.hasNotifySignal();
155 QQmlAbstractBinding *binding =
156 QQmlPropertyPrivate::binding(QQmlProperty(obj, rv.name));
158 rv.binding = binding->expression();
160 if (QQmlValueTypeFactory::isValueType(prop.userType())) {
161 rv.type = QQmlObjectProperty::Basic;
162 } else if (QQmlMetaType::isQObject(prop.userType())) {
163 rv.type = QQmlObjectProperty::Object;
164 } else if (QQmlMetaType::isList(prop.userType())) {
165 rv.type = QQmlObjectProperty::List;
166 } else if (prop.userType() == QMetaType::QVariant) {
167 rv.type = QQmlObjectProperty::Variant;
171 if (rv.type != QQmlObjectProperty::Unknown && prop.userType() != 0) {
172 value = prop.read(obj);
174 rv.value = valueContents(value);
179 QVariant QQmlEngineDebugService::valueContents(const QVariant &value) const
181 int userType = value.userType();
183 //QObject * is not streamable.
184 //Convert all such instances to a String value
186 if (value.type() == QVariant::List) {
187 QVariantList contents;
188 QVariantList list = value.toList();
189 int count = list.size();
190 for (int i = 0; i < count; i++)
191 contents << valueContents(list.at(i));
195 if (value.type() == QVariant::Map) {
196 QVariantMap contents;
197 QMapIterator<QString, QVariant> i(value.toMap());
198 while (i.hasNext()) {
200 contents.insert(i.key(), valueContents(i.value()));
205 if (QQmlValueTypeFactory::isValueType(userType))
208 if (QQmlMetaType::isQObject(userType)) {
209 QObject *o = QQmlMetaType::toQObject(value);
211 QString name = o->objectName();
213 name = QStringLiteral("<unnamed object>");
218 return QString(QStringLiteral("<unknown value>"));
221 void QQmlEngineDebugService::buildObjectDump(QDataStream &message,
222 QObject *object, bool recur, bool dumpProperties)
224 message << objectData(object);
226 QObjectList children = object->children();
228 int childrenCount = children.count();
229 for (int ii = 0; ii < children.count(); ++ii) {
230 if (qobject_cast<QQmlContext*>(children[ii]))
234 message << childrenCount << recur;
236 QList<QQmlObjectProperty> fakeProperties;
238 for (int ii = 0; ii < children.count(); ++ii) {
239 QObject *child = children.at(ii);
240 if (qobject_cast<QQmlContext*>(child))
243 buildObjectDump(message, child, recur, dumpProperties);
245 message << objectData(child);
248 if (!dumpProperties) {
253 QList<int> propertyIndexes;
254 for (int ii = 0; ii < object->metaObject()->propertyCount(); ++ii) {
255 if (object->metaObject()->property(ii).isScriptable())
256 propertyIndexes << ii;
259 QQmlData *ddata = QQmlData::get(object);
260 if (ddata && ddata->signalHandlers) {
261 QQmlAbstractBoundSignal *signalHandler = ddata->signalHandlers;
263 while (signalHandler) {
264 if (!dumpProperties) {
265 signalHandler = signalHandler->m_nextSignal;
268 QQmlObjectProperty prop;
269 prop.type = QQmlObjectProperty::SignalProperty;
270 prop.hasNotifySignal = false;
271 QQmlBoundSignalExpression *expr = signalHandler->expression();
273 prop.value = expr->expression();
274 QObject *scope = expr->scopeObject();
276 QString methodName = QString::fromLatin1(scope->metaObject()->method(signalHandler->index()).name());
277 if (!methodName.isEmpty()) {
278 prop.name = QLatin1String("on") + methodName[0].toUpper()
283 fakeProperties << prop;
285 signalHandler = signalHandler->m_nextSignal;
289 message << propertyIndexes.size() + fakeProperties.count();
291 for (int ii = 0; ii < propertyIndexes.size(); ++ii)
292 message << propertyData(object, propertyIndexes.at(ii));
294 for (int ii = 0; ii < fakeProperties.count(); ++ii)
295 message << fakeProperties[ii];
298 void QQmlEngineDebugService::prepareDeferredObjects(QObject *obj)
300 qmlExecuteDeferred(obj);
302 QObjectList children = obj->children();
303 for (int ii = 0; ii < children.count(); ++ii) {
304 QObject *child = children.at(ii);
305 prepareDeferredObjects(child);
310 void QQmlEngineDebugService::storeObjectIds(QObject *co)
312 QQmlDebugService::idForObject(co);
313 QObjectList children = co->children();
314 for (int ii = 0; ii < children.count(); ++ii)
315 storeObjectIds(children.at(ii));
318 void QQmlEngineDebugService::buildObjectList(QDataStream &message,
320 const QList<QPointer<QObject> > &instances)
322 QQmlContextData *p = QQmlContextData::get(ctxt);
324 QString ctxtName = ctxt->objectName();
325 int ctxtId = QQmlDebugService::idForObject(ctxt);
326 if (ctxt->contextObject())
327 storeObjectIds(ctxt->contextObject());
329 message << ctxtName << ctxtId;
333 QQmlContextData *child = p->childContexts;
336 child = child->nextChild;
341 child = p->childContexts;
343 buildObjectList(message, child->asQQmlContext(), instances);
344 child = child->nextChild;
348 for (int ii = 0; ii < instances.count(); ++ii) {
349 QQmlData *data = QQmlData::get(instances.at(ii));
350 if (data->context == p)
355 for (int ii = 0; ii < instances.count(); ++ii) {
356 QQmlData *data = QQmlData::get(instances.at(ii));
357 if (data->context == p)
358 message << objectData(instances.at(ii));
362 void QQmlEngineDebugService::buildStatesList(bool cleanList,
363 const QList<QPointer<QObject> > &instances)
365 if (m_statesDelegate)
366 m_statesDelegate->buildStatesList(cleanList, instances);
369 QQmlEngineDebugService::QQmlObjectData
370 QQmlEngineDebugService::objectData(QObject *object)
372 QQmlData *ddata = QQmlData::get(object);
374 if (ddata && ddata->outerContext) {
375 rv.url = ddata->outerContext->url;
376 rv.lineNumber = ddata->lineNumber;
377 rv.columnNumber = ddata->columnNumber;
380 rv.columnNumber = -1;
383 QQmlContext *context = qmlContext(object);
385 QQmlContextData *cdata = QQmlContextData::get(context);
387 rv.idString = cdata->findObjectId(object);
390 rv.objectName = object->objectName();
391 rv.objectId = QQmlDebugService::idForObject(object);
392 rv.contextId = QQmlDebugService::idForObject(qmlContext(object));
393 rv.parentId = QQmlDebugService::idForObject(object->parent());
394 QQmlType *type = QQmlMetaType::qmlType(object->metaObject());
396 QString typeName = type->qmlTypeName();
397 int lastSlash = typeName.lastIndexOf(QLatin1Char('/'));
398 rv.objectType = lastSlash < 0 ? typeName : typeName.mid(lastSlash+1);
400 rv.objectType = QString::fromUtf8(object->metaObject()->className());
401 int marker = rv.objectType.indexOf(QLatin1String("_QMLTYPE_"));
403 rv.objectType = rv.objectType.left(marker);
409 void QQmlEngineDebugService::messageReceived(const QByteArray &message)
411 QMetaObject::invokeMethod(this, "processMessage", Qt::QueuedConnection, Q_ARG(QByteArray, message));
414 void QQmlEngineDebugService::processMessage(const QByteArray &message)
416 QQmlDebugStream ds(message);
420 ds >> type >> queryId;
423 QQmlDebugStream rs(&reply, QIODevice::WriteOnly);
425 if (type == "LIST_ENGINES") {
426 rs << QByteArray("LIST_ENGINES_R");
427 rs << queryId << m_engines.count();
429 for (int ii = 0; ii < m_engines.count(); ++ii) {
430 QQmlEngine *engine = m_engines.at(ii);
432 QString engineName = engine->objectName();
433 int engineId = QQmlDebugService::idForObject(engine);
435 rs << engineName << engineId;
438 } else if (type == "LIST_OBJECTS") {
443 qobject_cast<QQmlEngine *>(QQmlDebugService::objectForId(engineId));
445 rs << QByteArray("LIST_OBJECTS_R") << queryId;
448 QQmlContext *rootContext = engine->rootContext();
449 // Clean deleted objects
450 QQmlContextPrivate *ctxtPriv = QQmlContextPrivate::get(rootContext);
451 for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) {
452 if (!ctxtPriv->instances.at(ii)) {
453 ctxtPriv->instances.removeAt(ii);
457 buildObjectList(rs, rootContext, ctxtPriv->instances);
458 buildStatesList(true, ctxtPriv->instances);
461 } else if (type == "FETCH_OBJECT") {
464 bool dumpProperties = true;
466 ds >> objectId >> recurse >> dumpProperties;
468 QObject *object = QQmlDebugService::objectForId(objectId);
470 rs << QByteArray("FETCH_OBJECT_R") << queryId;
474 prepareDeferredObjects(object);
475 buildObjectDump(rs, object, recurse, dumpProperties);
478 } else if (type == "FETCH_OBJECTS_FOR_LOCATION") {
483 bool dumpProperties = true;
485 ds >> file >> lineNumber >> columnNumber >> recurse >> dumpProperties;
487 QList<QObject*> objects = QQmlDebugService::objectForLocationInfo(
488 file, lineNumber, columnNumber);
490 rs << QByteArray("FETCH_OBJECTS_FOR_LOCATION_R") << queryId
493 foreach (QObject *object, objects) {
495 prepareDeferredObjects(object);
496 buildObjectDump(rs, object, recurse, dumpProperties);
499 } else if (type == "WATCH_OBJECT") {
503 bool ok = m_watch->addWatch(queryId, objectId);
505 rs << QByteArray("WATCH_OBJECT_R") << queryId << ok;
507 } else if (type == "WATCH_PROPERTY") {
511 ds >> objectId >> property;
512 bool ok = m_watch->addWatch(queryId, objectId, property);
514 rs << QByteArray("WATCH_PROPERTY_R") << queryId << ok;
516 } else if (type == "WATCH_EXPR_OBJECT") {
520 ds >> debugId >> expr;
521 bool ok = m_watch->addWatch(queryId, debugId, expr);
523 rs << QByteArray("WATCH_EXPR_OBJECT_R") << queryId << ok;
525 } else if (type == "NO_WATCH") {
526 bool ok = m_watch->removeWatch(queryId);
528 rs << QByteArray("NO_WATCH_R") << queryId << ok;
530 } else if (type == "EVAL_EXPRESSION") {
534 ds >> objectId >> expr;
539 QObject *object = QQmlDebugService::objectForId(objectId);
540 QQmlContext *context = qmlContext(object);
542 QQmlEngine *engine = qobject_cast<QQmlEngine *>(
543 QQmlDebugService::objectForId(engineId));
544 if (engine && m_engines.contains(engine))
545 context = engine->rootContext();
549 QQmlExpression exprObj(context, object, expr);
550 bool undefined = false;
551 QVariant value = exprObj.evaluate(&undefined);
553 result = QString(QStringLiteral("<undefined>"));
555 result = valueContents(value);
557 result = QString(QStringLiteral("<unknown context>"));
560 rs << QByteArray("EVAL_EXPRESSION_R") << queryId << result;
562 } else if (type == "SET_BINDING") {
564 QString propertyName;
569 ds >> objectId >> propertyName >> expr >> isLiteralValue >>
571 bool ok = setBinding(objectId, propertyName, expr, isLiteralValue,
574 rs << QByteArray("SET_BINDING_R") << queryId << ok;
576 } else if (type == "RESET_BINDING") {
578 QString propertyName;
579 ds >> objectId >> propertyName;
580 bool ok = resetBinding(objectId, propertyName);
582 rs << QByteArray("RESET_BINDING_R") << queryId << ok;
584 } else if (type == "SET_METHOD_BODY") {
588 ds >> objectId >> methodName >> methodBody;
589 bool ok = setMethodBody(objectId, methodName, methodBody);
591 rs << QByteArray("SET_METHOD_BODY_R") << queryId << ok;
597 bool QQmlEngineDebugService::setBinding(int objectId,
598 const QString &propertyName,
599 const QVariant &expression,
606 QObject *object = objectForId(objectId);
607 QQmlContext *context = qmlContext(object);
609 if (object && context) {
610 QQmlProperty property(object, propertyName, context);
611 if (property.isValid()) {
613 bool inBaseState = true;
614 if (m_statesDelegate) {
615 m_statesDelegate->updateBinding(context, property, expression, isLiteralValue,
616 filename, line, column, &inBaseState);
620 if (isLiteralValue) {
621 property.write(expression);
622 } else if (hasValidSignal(object, propertyName)) {
623 QQmlBoundSignalExpression *qmlExpression = new QQmlBoundSignalExpression(QQmlContextData::get(context), object, expression.toString(),
624 false, filename, line, column);
625 QQmlPropertyPrivate::takeSignalExpression(property, qmlExpression);
626 } else if (property.isProperty()) {
627 QQmlBinding *binding = new QQmlBinding(expression.toString(), false, object, QQmlContextData::get(context), filename, line, column);;
628 binding->setTarget(property);
629 QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::setBinding(property, binding);
631 oldBinding->destroy();
635 qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object;
640 // not a valid property
641 if (m_statesDelegate)
642 ok = m_statesDelegate->setBindingForInvalidProperty(object, propertyName, expression, isLiteralValue);
644 qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object;
650 bool QQmlEngineDebugService::resetBinding(int objectId, const QString &propertyName)
652 QObject *object = objectForId(objectId);
653 QQmlContext *context = qmlContext(object);
655 if (object && context) {
656 QString parentProperty = propertyName;
657 if (propertyName.indexOf(QLatin1Char('.')) != -1)
658 parentProperty = propertyName.left(propertyName.indexOf(QLatin1Char('.')));
660 if (object->property(parentProperty.toLatin1()).isValid()) {
661 QQmlProperty property(object, propertyName);
662 QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::binding(property);
664 QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::setBinding(property, 0);
666 oldBinding->destroy();
668 if (property.isResettable()) {
669 // Note: this will reset the property in any case, without regard to states
670 // Right now almost no QQuickItem has reset methods for its properties (with the
671 // notable exception of QQuickAnchors), so this is not a big issue
672 // later on, setBinding does take states into account
675 // overwrite with default value
676 if (QQmlType *objType = QQmlMetaType::qmlType(object->metaObject())) {
677 if (QObject *emptyObject = objType->create()) {
678 if (emptyObject->property(parentProperty.toLatin1()).isValid()) {
679 QVariant defaultValue = QQmlProperty(emptyObject, propertyName).read();
680 if (defaultValue.isValid()) {
681 setBinding(objectId, propertyName, defaultValue, true);
691 if (hasValidSignal(object, propertyName)) {
692 QQmlProperty property(object, propertyName, context);
693 QQmlPropertyPrivate::setSignalExpression(property, 0);
697 if (m_statesDelegate) {
698 m_statesDelegate->resetBindingForInvalidProperty(object, propertyName);
704 // object or context null.
708 bool QQmlEngineDebugService::setMethodBody(int objectId, const QString &method, const QString &body)
710 QObject *object = objectForId(objectId);
711 QQmlContext *context = qmlContext(object);
712 if (!object || !context || !context->engine())
714 QQmlContextData *contextData = QQmlContextData::get(context);
718 QQmlPropertyData dummy;
719 QQmlPropertyData *prop =
720 QQmlPropertyCache::property(context->engine(), object, method, dummy);
722 if (!prop || !prop->isVMEFunction())
725 QMetaMethod metaMethod = object->metaObject()->method(prop->coreIndex);
726 QList<QByteArray> paramNames = metaMethod.parameterNames();
729 for (int ii = 0; ii < paramNames.count(); ++ii) {
730 if (ii != 0) paramStr.append(QLatin1Char(','));
731 paramStr.append(QString::fromUtf8(paramNames.at(ii)));
734 QString jsfunction = QLatin1String("(function ") + method + QLatin1Char('(') + paramStr +
735 QLatin1String(") {");
737 jsfunction += QLatin1String("\n})");
739 QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(object);
740 Q_ASSERT(vmeMetaObject); // the fact we found the property above should guarentee this
742 int lineNumber = vmeMetaObject->vmeMethodLineNumber(prop->coreIndex);
743 vmeMetaObject->setVmeMethod(prop->coreIndex, QQmlExpressionPrivate::evalFunction(contextData, object, jsfunction, contextData->url.toString(), lineNumber));
747 void QQmlEngineDebugService::propertyChanged(int id, int objectId, const QMetaProperty &property, const QVariant &value)
750 QQmlDebugStream rs(&reply, QIODevice::WriteOnly);
752 rs << QByteArray("UPDATE_WATCH") << id << objectId << QByteArray(property.name()) << valueContents(value);
757 void QQmlEngineDebugService::addEngine(QQmlEngine *engine)
760 Q_ASSERT(!m_engines.contains(engine));
762 m_engines.append(engine);
765 void QQmlEngineDebugService::remEngine(QQmlEngine *engine)
768 Q_ASSERT(m_engines.contains(engine));
770 m_engines.removeAll(engine);
773 void QQmlEngineDebugService::objectCreated(QQmlEngine *engine, QObject *object)
776 Q_ASSERT(m_engines.contains(engine));
778 int engineId = QQmlDebugService::idForObject(engine);
779 int objectId = QQmlDebugService::idForObject(object);
780 int parentId = QQmlDebugService::idForObject(object->parent());
783 QQmlDebugStream rs(&reply, QIODevice::WriteOnly);
786 rs << QByteArray("OBJECT_CREATED") << -1 << engineId << objectId << parentId;
790 void QQmlEngineDebugService::setStatesDelegate(QQmlDebugStatesDelegate *delegate)
792 m_statesDelegate = delegate;