blob: e878f19fa299e0ae5ee9e0abcf684cb8a5da10d3 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtDeclarative module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "private/qdeclarativeenginedebug_p.h"
#include "private/qdeclarativeboundsignal_p.h"
#include "qdeclarativeengine.h"
#include "private/qdeclarativemetatype_p.h"
#include "qdeclarativeproperty.h"
#include "private/qdeclarativeproperty_p.h"
#include "private/qdeclarativebinding_p.h"
#include "private/qdeclarativecontext_p.h"
#include "private/qdeclarativewatcher_p.h"
#include "private/qdeclarativevaluetype_p.h"
#include "private/qdeclarativevmemetaobject_p.h"
#include "private/qdeclarativeexpression_p.h"
#include "private/qdeclarativepropertychanges_p.h"
#include <QtCore/qdebug.h>
#include <QtCore/qmetaobject.h>
QT_BEGIN_NAMESPACE
Q_GLOBAL_STATIC(QDeclarativeEngineDebugServer, qmlEngineDebugServer);
QDeclarativeEngineDebugServer *QDeclarativeEngineDebugServer::instance()
{
return qmlEngineDebugServer();
}
QDeclarativeEngineDebugServer::QDeclarativeEngineDebugServer(QObject *parent)
: QDeclarativeDebugService(QLatin1String("QDeclarativeEngine"), parent),
m_watch(new QDeclarativeWatcher(this))
{
QObject::connect(m_watch, SIGNAL(propertyChanged(int,int,QMetaProperty,QVariant)),
this, SLOT(propertyChanged(int,int,QMetaProperty,QVariant)));
}
QDataStream &operator<<(QDataStream &ds,
const QDeclarativeEngineDebugServer::QDeclarativeObjectData &data)
{
ds << data.url << data.lineNumber << data.columnNumber << data.idString
<< data.objectName << data.objectType << data.objectId << data.contextId;
return ds;
}
QDataStream &operator>>(QDataStream &ds,
QDeclarativeEngineDebugServer::QDeclarativeObjectData &data)
{
ds >> data.url >> data.lineNumber >> data.columnNumber >> data.idString
>> data.objectName >> data.objectType >> data.objectId >> data.contextId;
return ds;
}
QDataStream &operator<<(QDataStream &ds,
const QDeclarativeEngineDebugServer::QDeclarativeObjectProperty &data)
{
ds << (int)data.type << data.name << data.value << data.valueTypeName
<< data.binding << data.hasNotifySignal;
return ds;
}
QDataStream &operator>>(QDataStream &ds,
QDeclarativeEngineDebugServer::QDeclarativeObjectProperty &data)
{
int type;
ds >> type >> data.name >> data.value >> data.valueTypeName
>> data.binding >> data.hasNotifySignal;
data.type = (QDeclarativeEngineDebugServer::QDeclarativeObjectProperty::Type)type;
return ds;
}
static inline bool isSignalPropertyName(const QString &signalName)
{
// see QmlCompiler::isSignalPropertyName
return signalName.length() >= 3 && signalName.startsWith(QLatin1String("on")) &&
signalName.at(2).isLetter() && signalName.at(2).isUpper();
}
static bool hasValidSignal(QObject *object, const QString &propertyName)
{
if (!isSignalPropertyName(propertyName))
return false;
QString signalName = propertyName.mid(2);
signalName[0] = signalName.at(0).toLower();
int sigIdx = QDeclarativePropertyPrivate::findSignalByName(object->metaObject(), signalName.toLatin1()).methodIndex();
if (sigIdx == -1)
return false;
return true;
}
QDeclarativeEngineDebugServer::QDeclarativeObjectProperty
QDeclarativeEngineDebugServer::propertyData(QObject *obj, int propIdx)
{
QDeclarativeObjectProperty rv;
QMetaProperty prop = obj->metaObject()->property(propIdx);
rv.type = QDeclarativeObjectProperty::Unknown;
rv.valueTypeName = QString::fromUtf8(prop.typeName());
rv.name = QString::fromUtf8(prop.name());
rv.hasNotifySignal = prop.hasNotifySignal();
QDeclarativeAbstractBinding *binding =
QDeclarativePropertyPrivate::binding(QDeclarativeProperty(obj, rv.name));
if (binding)
rv.binding = binding->expression();
QVariant value;
if (prop.userType() != 0) {
value = prop.read(obj);
}
rv.value = valueContents(value);
if (QDeclarativeValueTypeFactory::isValueType(prop.userType())) {
rv.type = QDeclarativeObjectProperty::Basic;
} else if (QDeclarativeMetaType::isQObject(prop.userType())) {
rv.type = QDeclarativeObjectProperty::Object;
} else if (QDeclarativeMetaType::isList(prop.userType())) {
rv.type = QDeclarativeObjectProperty::List;
}
return rv;
}
QVariant QDeclarativeEngineDebugServer::valueContents(const QVariant &value) const
{
int userType = value.userType();
if (value.type() == QVariant::List) {
QVariantList contents;
QVariantList list = value.toList();
int count = list.size();
for (int i = 0; i < count; i++)
contents << valueContents(list.at(i));
return contents;
}
if (QDeclarativeValueTypeFactory::isValueType(userType))
return value;
if (QDeclarativeMetaType::isQObject(userType)) {
QObject *o = QDeclarativeMetaType::toQObject(value);
if (o) {
QString name = o->objectName();
if (name.isEmpty())
name = QLatin1String("<unnamed object>");
return name;
}
}
return QLatin1String("<unknown value>");
}
void QDeclarativeEngineDebugServer::buildObjectDump(QDataStream &message,
QObject *object, bool recur, bool dumpProperties)
{
message << objectData(object);
QObjectList children = object->children();
int childrenCount = children.count();
for (int ii = 0; ii < children.count(); ++ii) {
if (qobject_cast<QDeclarativeContext*>(children[ii]) || QDeclarativeBoundSignal::cast(children[ii]))
--childrenCount;
}
message << childrenCount << recur;
QList<QDeclarativeObjectProperty> fakeProperties;
for (int ii = 0; ii < children.count(); ++ii) {
QObject *child = children.at(ii);
if (qobject_cast<QDeclarativeContext*>(child))
continue;
QDeclarativeBoundSignal *signal = QDeclarativeBoundSignal::cast(child);
if (signal) {
if (!dumpProperties)
continue;
QDeclarativeObjectProperty prop;
prop.type = QDeclarativeObjectProperty::SignalProperty;
prop.hasNotifySignal = false;
QDeclarativeExpression *expr = signal->expression();
if (expr) {
prop.value = expr->expression();
QObject *scope = expr->scopeObject();
if (scope) {
QString sig = QLatin1String(scope->metaObject()->method(signal->index()).signature());
int lparen = sig.indexOf(QLatin1Char('('));
if (lparen >= 0) {
QString methodName = sig.mid(0, lparen);
prop.name = QLatin1String("on") + methodName[0].toUpper()
+ methodName.mid(1);
}
}
}
fakeProperties << prop;
} else {
if (recur)
buildObjectDump(message, child, recur, dumpProperties);
else
message << objectData(child);
}
}
if (!dumpProperties) {
message << 0;
return;
}
QList<int> propertyIndexes;
for (int ii = 0; ii < object->metaObject()->propertyCount(); ++ii) {
if (object->metaObject()->property(ii).isScriptable())
propertyIndexes << ii;
}
message << propertyIndexes.size() + fakeProperties.count();
for (int ii = 0; ii < propertyIndexes.size(); ++ii)
message << propertyData(object, propertyIndexes.at(ii));
for (int ii = 0; ii < fakeProperties.count(); ++ii)
message << fakeProperties[ii];
}
void QDeclarativeEngineDebugServer::prepareDeferredObjects(QObject *obj)
{
qmlExecuteDeferred(obj);
QObjectList children = obj->children();
for (int ii = 0; ii < children.count(); ++ii) {
QObject *child = children.at(ii);
prepareDeferredObjects(child);
}
}
void QDeclarativeEngineDebugServer::buildObjectList(QDataStream &message, QDeclarativeContext *ctxt)
{
QDeclarativeContextData *p = QDeclarativeContextData::get(ctxt);
QString ctxtName = ctxt->objectName();
int ctxtId = QDeclarativeDebugService::idForObject(ctxt);
message << ctxtName << ctxtId;
int count = 0;
QDeclarativeContextData *child = p->childContexts;
while (child) {
++count;
child = child->nextChild;
}
message << count;
child = p->childContexts;
while (child) {
buildObjectList(message, child->asQDeclarativeContext());
child = child->nextChild;
}
// Clean deleted objects
QDeclarativeContextPrivate *ctxtPriv = QDeclarativeContextPrivate::get(ctxt);
for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) {
if (!ctxtPriv->instances.at(ii)) {
ctxtPriv->instances.removeAt(ii);
--ii;
}
}
message << ctxtPriv->instances.count();
for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) {
message << objectData(ctxtPriv->instances.at(ii));
}
}
void QDeclarativeEngineDebugServer::buildStatesList(QDeclarativeContext *ctxt, bool cleanList=false)
{
if (cleanList)
m_allStates.clear();
QDeclarativeContextPrivate *ctxtPriv = QDeclarativeContextPrivate::get(ctxt);
for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) {
buildStatesList(ctxtPriv->instances.at(ii));
}
QDeclarativeContextData *child = QDeclarativeContextData::get(ctxt)->childContexts;
while (child) {
buildStatesList(child->asQDeclarativeContext());
child = child->nextChild;
}
}
void QDeclarativeEngineDebugServer::buildStatesList(QObject *obj)
{
if (QDeclarativeState *state = qobject_cast<QDeclarativeState *>(obj)) {
m_allStates.append(state);
}
QObjectList children = obj->children();
for (int ii = 0; ii < children.count(); ++ii) {
buildStatesList(children.at(ii));
}
}
QDeclarativeEngineDebugServer::QDeclarativeObjectData
QDeclarativeEngineDebugServer::objectData(QObject *object)
{
QDeclarativeData *ddata = QDeclarativeData::get(object);
QDeclarativeObjectData rv;
if (ddata && ddata->outerContext) {
rv.url = ddata->outerContext->url;
rv.lineNumber = ddata->lineNumber;
rv.columnNumber = ddata->columnNumber;
} else {
rv.lineNumber = -1;
rv.columnNumber = -1;
}
QDeclarativeContext *context = qmlContext(object);
if (context) {
QDeclarativeContextData *cdata = QDeclarativeContextData::get(context);
if (cdata)
rv.idString = cdata->findObjectId(object);
}
rv.objectName = object->objectName();
rv.objectId = QDeclarativeDebugService::idForObject(object);
rv.contextId = QDeclarativeDebugService::idForObject(qmlContext(object));
QDeclarativeType *type = QDeclarativeMetaType::qmlType(object->metaObject());
if (type) {
QString typeName = QLatin1String(type->qmlTypeName());
int lastSlash = typeName.lastIndexOf(QLatin1Char('/'));
rv.objectType = lastSlash < 0 ? typeName : typeName.mid(lastSlash+1);
} else {
rv.objectType = QString::fromUtf8(object->metaObject()->className());
int marker = rv.objectType.indexOf(QLatin1String("_QMLTYPE_"));
if (marker != -1)
rv.objectType = rv.objectType.left(marker);
}
return rv;
}
void QDeclarativeEngineDebugServer::messageReceived(const QByteArray &message)
{
QDataStream ds(message);
QByteArray type;
ds >> type;
if (type == "LIST_ENGINES") {
int queryId;
ds >> queryId;
QByteArray reply;
QDataStream rs(&reply, QIODevice::WriteOnly);
rs << QByteArray("LIST_ENGINES_R");
rs << queryId << m_engines.count();
for (int ii = 0; ii < m_engines.count(); ++ii) {
QDeclarativeEngine *engine = m_engines.at(ii);
QString engineName = engine->objectName();
int engineId = QDeclarativeDebugService::idForObject(engine);
rs << engineName << engineId;
}
sendMessage(reply);
} else if (type == "LIST_OBJECTS") {
int queryId;
int engineId = -1;
ds >> queryId >> engineId;
QDeclarativeEngine *engine =
qobject_cast<QDeclarativeEngine *>(QDeclarativeDebugService::objectForId(engineId));
QByteArray reply;
QDataStream rs(&reply, QIODevice::WriteOnly);
rs << QByteArray("LIST_OBJECTS_R") << queryId;
if (engine) {
buildObjectList(rs, engine->rootContext());
buildStatesList(engine->rootContext(), true);
}
sendMessage(reply);
} else if (type == "FETCH_OBJECT") {
int queryId;
int objectId;
bool recurse;
bool dumpProperties = true;
ds >> queryId >> objectId >> recurse >> dumpProperties;
QObject *object = QDeclarativeDebugService::objectForId(objectId);
QByteArray reply;
QDataStream rs(&reply, QIODevice::WriteOnly);
rs << QByteArray("FETCH_OBJECT_R") << queryId;
if (object) {
if (recurse)
prepareDeferredObjects(object);
buildObjectDump(rs, object, recurse, dumpProperties);
}
sendMessage(reply);
} else if (type == "WATCH_OBJECT") {
int queryId;
int objectId;
ds >> queryId >> objectId;
bool ok = m_watch->addWatch(queryId, objectId);
QByteArray reply;
QDataStream rs(&reply, QIODevice::WriteOnly);
rs << QByteArray("WATCH_OBJECT_R") << queryId << ok;
sendMessage(reply);
} else if (type == "WATCH_PROPERTY") {
int queryId;
int objectId;
QByteArray property;
ds >> queryId >> objectId >> property;
bool ok = m_watch->addWatch(queryId, objectId, property);
QByteArray reply;
QDataStream rs(&reply, QIODevice::WriteOnly);
rs << QByteArray("WATCH_PROPERTY_R") << queryId << ok;
sendMessage(reply);
} else if (type == "WATCH_EXPR_OBJECT") {
int queryId;
int debugId;
QString expr;
ds >> queryId >> debugId >> expr;
bool ok = m_watch->addWatch(queryId, debugId, expr);
QByteArray reply;
QDataStream rs(&reply, QIODevice::WriteOnly);
rs << QByteArray("WATCH_EXPR_OBJECT_R") << queryId << ok;
sendMessage(reply);
} else if (type == "NO_WATCH") {
int queryId;
ds >> queryId;
m_watch->removeWatch(queryId);
} else if (type == "EVAL_EXPRESSION") {
int queryId;
int objectId;
QString expr;
ds >> queryId >> objectId >> expr;
QObject *object = QDeclarativeDebugService::objectForId(objectId);
QDeclarativeContext *context = qmlContext(object);
QVariant result;
if (object && context) {
QDeclarativeExpression exprObj(context, object, expr);
bool undefined = false;
QVariant value = exprObj.evaluate(&undefined);
if (undefined)
result = QLatin1String("<undefined>");
else
result = valueContents(value);
} else {
result = QLatin1String("<unknown context>");
}
QByteArray reply;
QDataStream rs(&reply, QIODevice::WriteOnly);
rs << QByteArray("EVAL_EXPRESSION_R") << queryId << result;
sendMessage(reply);
} else if (type == "SET_BINDING") {
int objectId;
QString propertyName;
QVariant expr;
bool isLiteralValue;
ds >> objectId >> propertyName >> expr >> isLiteralValue;
setBinding(objectId, propertyName, expr, isLiteralValue);
} else if (type == "RESET_BINDING") {
int objectId;
QString propertyName;
ds >> objectId >> propertyName;
resetBinding(objectId, propertyName);
} else if (type == "SET_METHOD_BODY") {
int objectId;
QString methodName;
QString methodBody;
ds >> objectId >> methodName >> methodBody;
setMethodBody(objectId, methodName, methodBody);
}
}
void QDeclarativeEngineDebugServer::setBinding(int objectId,
const QString &propertyName,
const QVariant &expression,
bool isLiteralValue)
{
QObject *object = objectForId(objectId);
QDeclarativeContext *context = qmlContext(object);
if (object && context) {
QDeclarativeProperty property(object, propertyName, context);
if (property.isValid()) {
bool inBaseState = true;
foreach(QWeakPointer<QDeclarativeState> statePointer, m_allStates) {
if (QDeclarativeState *state = statePointer.data()) {
// here we assume that the revert list on itself defines the base state
if (state->isStateActive() && state->containsPropertyInRevertList(object, propertyName)) {
inBaseState = false;
QDeclarativeBinding *newBinding = 0;
if (!isLiteralValue) {
newBinding = new QDeclarativeBinding(expression.toString(), object, context);
newBinding->setTarget(property);
newBinding->setNotifyOnValueChanged(true);
}
state->changeBindingInRevertList(object, propertyName, newBinding);
if (isLiteralValue)
state->changeValueInRevertList(object, propertyName, expression);
}
}
}
if (inBaseState) {
if (isLiteralValue) {
property.write(expression);
} else if (hasValidSignal(object, propertyName)) {
QDeclarativeExpression *declarativeExpression = new QDeclarativeExpression(context, object, expression.toString());
QDeclarativeExpression *oldExpression = QDeclarativePropertyPrivate::setSignalExpression(property, declarativeExpression);
declarativeExpression->setSourceLocation(oldExpression->sourceFile(), oldExpression->lineNumber());
} else if (property.isProperty()) {
QDeclarativeBinding *binding = new QDeclarativeBinding(expression.toString(), object, context);
binding->setTarget(property);
binding->setNotifyOnValueChanged(true);
QDeclarativeAbstractBinding *oldBinding = QDeclarativePropertyPrivate::setBinding(property, binding);
if (oldBinding)
oldBinding->destroy();
binding->update();
} else {
qWarning() << "QDeclarativeEngineDebugServer::setBinding: unable to set property" << propertyName << "on object" << object;
}
}
} else {
// not a valid property
if (QDeclarativePropertyChanges *propertyChanges = qobject_cast<QDeclarativePropertyChanges *>(object)) {
if (isLiteralValue) {
propertyChanges->changeValue(propertyName, expression);
} else {
propertyChanges->changeExpression(propertyName, expression.toString());
}
} else {
qWarning() << "QDeclarativeEngineDebugServer::setBinding: unable to set property" << propertyName << "on object" << object;
}
}
}
}
void QDeclarativeEngineDebugServer::resetBinding(int objectId, const QString &propertyName)
{
QObject *object = objectForId(objectId);
QDeclarativeContext *context = qmlContext(object);
if (object && context) {
if (object->property(propertyName.toLatin1()).isValid()) {
QDeclarativeProperty property(object, propertyName);
QDeclarativeAbstractBinding *oldBinding = QDeclarativePropertyPrivate::binding(property);
if (oldBinding) {
QDeclarativeAbstractBinding *oldBinding = QDeclarativePropertyPrivate::setBinding(property, 0);
if (oldBinding)
oldBinding->destroy();
}
if (property.isResettable()) {
// Note: this will reset the property in any case, without regard to states
// Right now almost no QDeclarativeItem has reset methods for its properties (with the
// notable exception of QDeclarativeAnchors), so this is not a big issue
// later on, setBinding does take states into account
property.reset();
} else {
// overwrite with default value
if (QDeclarativeType *objType = QDeclarativeMetaType::qmlType(object->metaObject())) {
if (QObject *emptyObject = objType->create()) {
if (emptyObject->property(propertyName.toLatin1()).isValid()) {
QVariant defaultValue = QDeclarativeProperty(emptyObject, propertyName).read();
if (defaultValue.isValid()) {
setBinding(objectId, propertyName, defaultValue, true);
}
}
delete emptyObject;
}
}
}
} else {
if (QDeclarativePropertyChanges *propertyChanges = qobject_cast<QDeclarativePropertyChanges *>(object)) {
propertyChanges->removeProperty(propertyName);
}
}
}
}
void QDeclarativeEngineDebugServer::setMethodBody(int objectId, const QString &method, const QString &body)
{
QObject *object = objectForId(objectId);
QDeclarativeContext *context = qmlContext(object);
if (!object || !context || !context->engine())
return;
QDeclarativeContextData *contextData = QDeclarativeContextData::get(context);
if (!contextData)
return;
QDeclarativePropertyCache::Data dummy;
QDeclarativePropertyCache::Data *prop =
QDeclarativePropertyCache::property(context->engine(), object, method, dummy);
if (!prop || !(prop->flags & QDeclarativePropertyCache::Data::IsVMEFunction))
return;
QMetaMethod metaMethod = object->metaObject()->method(prop->coreIndex);
QList<QByteArray> paramNames = metaMethod.parameterNames();
QString paramStr;
for (int ii = 0; ii < paramNames.count(); ++ii) {
if (ii != 0) paramStr.append(QLatin1String(","));
paramStr.append(QString::fromUtf8(paramNames.at(ii)));
}
QString jsfunction = QLatin1String("(function ") + method + QLatin1String("(") + paramStr +
QLatin1String(") {");
jsfunction += body;
jsfunction += QLatin1String("\n})");
QDeclarativeVMEMetaObject *vmeMetaObject =
static_cast<QDeclarativeVMEMetaObject*>(QObjectPrivate::get(object)->metaObject);
Q_ASSERT(vmeMetaObject); // the fact we found the property above should guarentee this
int lineNumber = vmeMetaObject->vmeMethodLineNumber(prop->coreIndex);
vmeMetaObject->setVmeMethod(prop->coreIndex, QDeclarativeExpressionPrivate::evalInObjectScope(contextData, object, jsfunction, contextData->url.toString(), lineNumber, 0));
}
void QDeclarativeEngineDebugServer::propertyChanged(int id, int objectId, const QMetaProperty &property, const QVariant &value)
{
QByteArray reply;
QDataStream rs(&reply, QIODevice::WriteOnly);
rs << QByteArray("UPDATE_WATCH") << id << objectId << QByteArray(property.name()) << valueContents(value);
sendMessage(reply);
}
void QDeclarativeEngineDebugServer::addEngine(QDeclarativeEngine *engine)
{
Q_ASSERT(engine);
Q_ASSERT(!m_engines.contains(engine));
m_engines.append(engine);
}
void QDeclarativeEngineDebugServer::remEngine(QDeclarativeEngine *engine)
{
Q_ASSERT(engine);
Q_ASSERT(m_engines.contains(engine));
m_engines.removeAll(engine);
}
void QDeclarativeEngineDebugServer::objectCreated(QDeclarativeEngine *engine, QObject *object)
{
Q_ASSERT(engine);
Q_ASSERT(m_engines.contains(engine));
int engineId = QDeclarativeDebugService::idForObject(engine);
int objectId = QDeclarativeDebugService::idForObject(object);
QByteArray reply;
QDataStream rs(&reply, QIODevice::WriteOnly);
rs << QByteArray("OBJECT_CREATED") << engineId << objectId;
sendMessage(reply);
}
QT_END_NAMESPACE