| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the QtQml module of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU Lesser General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the | 
| 21 | ** packaging of this file. Please review the following information to | 
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements | 
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. | 
| 24 | ** | 
| 25 | ** GNU General Public License Usage | 
| 26 | ** Alternatively, this file may be used under the terms of the GNU | 
| 27 | ** General Public License version 2.0 or (at your option) the GNU General | 
| 28 | ** Public license version 3 or any later version approved by the KDE Free | 
| 29 | ** Qt Foundation. The licenses are as published by the Free Software | 
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 | 
| 31 | ** included in the packaging of this file. Please review the following | 
| 32 | ** information to ensure the GNU General Public License requirements will | 
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and | 
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. | 
| 35 | ** | 
| 36 | ** $QT_END_LICENSE$ | 
| 37 | ** | 
| 38 | ****************************************************************************/ | 
| 39 |  | 
| 40 | #include "qqmlenginedebugservice.h" | 
| 41 | #include "qqmlwatcher.h" | 
| 42 |  | 
| 43 | #include <private/qqmldebugstatesdelegate_p.h> | 
| 44 | #include <private/qqmlboundsignal_p.h> | 
| 45 | #include <qqmlengine.h> | 
| 46 | #include <private/qqmlmetatype_p.h> | 
| 47 | #include <qqmlproperty.h> | 
| 48 | #include <private/qqmlproperty_p.h> | 
| 49 | #include <private/qqmlbinding_p.h> | 
| 50 | #include <private/qqmlcontext_p.h> | 
| 51 | #include <private/qqmlvaluetype_p.h> | 
| 52 | #include <private/qqmlvmemetaobject_p.h> | 
| 53 | #include <private/qqmlexpression_p.h> | 
| 54 |  | 
| 55 | #include <QtCore/qdebug.h> | 
| 56 | #include <QtCore/qmetaobject.h> | 
| 57 | #include <QtCore/qfileinfo.h> | 
| 58 | #include <QtCore/qjsonvalue.h> | 
| 59 | #include <QtCore/qjsonobject.h> | 
| 60 | #include <QtCore/qjsonarray.h> | 
| 61 | #include <QtCore/qjsondocument.h> | 
| 62 |  | 
| 63 | #include <private/qmetaobject_p.h> | 
| 64 | #include <private/qqmldebugconnector_p.h> | 
| 65 | #include <private/qversionedpacket_p.h> | 
| 66 |  | 
| 67 | QT_BEGIN_NAMESPACE | 
| 68 |  | 
| 69 | using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>; | 
| 70 |  | 
| 71 | class NullDevice : public QIODevice | 
| 72 | { | 
| 73 | public: | 
| 74 |     NullDevice() { open(mode: QIODevice::ReadWrite); } | 
| 75 |  | 
| 76 | protected: | 
| 77 |     qint64 readData(char *data, qint64 maxlen) final; | 
| 78 |     qint64 writeData(const char *data, qint64 len) final; | 
| 79 | }; | 
| 80 |  | 
| 81 | qint64 NullDevice::readData(char *data, qint64 maxlen) | 
| 82 | { | 
| 83 |     Q_UNUSED(data); | 
| 84 |     return maxlen; | 
| 85 | } | 
| 86 |  | 
| 87 | qint64 NullDevice::writeData(const char *data, qint64 len) | 
| 88 | { | 
| 89 |     Q_UNUSED(data); | 
| 90 |     return len; | 
| 91 | } | 
| 92 |  | 
| 93 | // check whether the data can be saved | 
| 94 | // (otherwise we assert in QVariant::operator<< when actually saving it) | 
| 95 | static bool isSaveable(const QVariant &value) | 
| 96 | { | 
| 97 |     const int valType = static_cast<int>(value.userType()); | 
| 98 |     if (valType >= QMetaType::User) | 
| 99 |         return false; | 
| 100 |     NullDevice nullDevice; | 
| 101 |     QDataStream fakeStream(&nullDevice); | 
| 102 |     return QMetaType::save(stream&: fakeStream, type: valType, data: value.constData()); | 
| 103 | } | 
| 104 |  | 
| 105 | QQmlEngineDebugServiceImpl::QQmlEngineDebugServiceImpl(QObject *parent) : | 
| 106 |     QQmlEngineDebugService(2, parent), m_watch(new QQmlWatcher(this)), m_statesDelegate(nullptr) | 
| 107 | { | 
| 108 |     connect(sender: m_watch, signal: &QQmlWatcher::propertyChanged, | 
| 109 |             receiver: this, slot: &QQmlEngineDebugServiceImpl::propertyChanged); | 
| 110 |  | 
| 111 |     // Move the message into the correct thread for processing | 
| 112 |     connect(sender: this, signal: &QQmlEngineDebugServiceImpl::scheduleMessage, | 
| 113 |             receiver: this, slot: &QQmlEngineDebugServiceImpl::processMessage, type: Qt::QueuedConnection); | 
| 114 | } | 
| 115 |  | 
| 116 | QQmlEngineDebugServiceImpl::~QQmlEngineDebugServiceImpl() | 
| 117 | { | 
| 118 |     delete m_statesDelegate; | 
| 119 | } | 
| 120 |  | 
| 121 | QDataStream &operator<<(QDataStream &ds, | 
| 122 |                         const QQmlEngineDebugServiceImpl::QQmlObjectData &data) | 
| 123 | { | 
| 124 |     ds << data.url << data.lineNumber << data.columnNumber << data.idString | 
| 125 |        << data.objectName << data.objectType << data.objectId << data.contextId | 
| 126 |        << data.parentId; | 
| 127 |     return ds; | 
| 128 | } | 
| 129 |  | 
| 130 | QDataStream &operator>>(QDataStream &ds, | 
| 131 |                         QQmlEngineDebugServiceImpl::QQmlObjectData &data) | 
| 132 | { | 
| 133 |     ds >> data.url >> data.lineNumber >> data.columnNumber >> data.idString | 
| 134 |        >> data.objectName >> data.objectType >> data.objectId >> data.contextId | 
| 135 |        >> data.parentId; | 
| 136 |     return ds; | 
| 137 | } | 
| 138 |  | 
| 139 | QDataStream &operator<<(QDataStream &ds, | 
| 140 |                         const QQmlEngineDebugServiceImpl::QQmlObjectProperty &data) | 
| 141 | { | 
| 142 |     ds << (int)data.type << data.name; | 
| 143 |     ds << (isSaveable(value: data.value) ? data.value : QVariant()); | 
| 144 |     ds << data.valueTypeName << data.binding << data.hasNotifySignal; | 
| 145 |     return ds; | 
| 146 | } | 
| 147 |  | 
| 148 | QDataStream &operator>>(QDataStream &ds, | 
| 149 |                         QQmlEngineDebugServiceImpl::QQmlObjectProperty &data) | 
| 150 | { | 
| 151 |     int type; | 
| 152 |     ds >> type >> data.name >> data.value >> data.valueTypeName | 
| 153 |        >> data.binding >> data.hasNotifySignal; | 
| 154 |     data.type = (QQmlEngineDebugServiceImpl::QQmlObjectProperty::Type)type; | 
| 155 |     return ds; | 
| 156 | } | 
| 157 |  | 
| 158 | static inline bool isSignalPropertyName(const QString &signalName) | 
| 159 | { | 
| 160 |     // see QmlCompiler::isSignalPropertyName | 
| 161 |     return signalName.length() >= 3 && signalName.startsWith(s: QLatin1String("on" )) && | 
| 162 |             signalName.at(i: 2).isLetter() && signalName.at(i: 2).isUpper(); | 
| 163 | } | 
| 164 |  | 
| 165 | static bool hasValidSignal(QObject *object, const QString &propertyName) | 
| 166 | { | 
| 167 |     if (!isSignalPropertyName(signalName: propertyName)) | 
| 168 |         return false; | 
| 169 |  | 
| 170 |     QString signalName = propertyName.mid(position: 2); | 
| 171 |     signalName[0] = signalName.at(i: 0).toLower(); | 
| 172 |  | 
| 173 |     int sigIdx = QQmlPropertyPrivate::findSignalByName(mo: object->metaObject(), signalName.toLatin1()).methodIndex(); | 
| 174 |  | 
| 175 |     if (sigIdx == -1) | 
| 176 |         return false; | 
| 177 |  | 
| 178 |     return true; | 
| 179 | } | 
| 180 |  | 
| 181 | QQmlEngineDebugServiceImpl::QQmlObjectProperty | 
| 182 | QQmlEngineDebugServiceImpl::propertyData(QObject *obj, int propIdx) | 
| 183 | { | 
| 184 |     QQmlObjectProperty rv; | 
| 185 |  | 
| 186 |     QMetaProperty prop = obj->metaObject()->property(index: propIdx); | 
| 187 |  | 
| 188 |     rv.type = QQmlObjectProperty::Unknown; | 
| 189 |     rv.valueTypeName = QString::fromUtf8(str: prop.typeName()); | 
| 190 |     rv.name = QString::fromUtf8(str: prop.name()); | 
| 191 |     rv.hasNotifySignal = prop.hasNotifySignal(); | 
| 192 |     QQmlAbstractBinding *binding = | 
| 193 |             QQmlPropertyPrivate::binding(that: QQmlProperty(obj, rv.name)); | 
| 194 |     if (binding) | 
| 195 |         rv.binding = binding->expression(); | 
| 196 |  | 
| 197 |     rv.value = valueContents(defaultValue: prop.read(obj)); | 
| 198 |  | 
| 199 |     if (QQmlMetaType::isQObject(prop.userType()))  { | 
| 200 |         rv.type = QQmlObjectProperty::Object; | 
| 201 |     } else if (QQmlMetaType::isList(prop.userType())) { | 
| 202 |         rv.type = QQmlObjectProperty::List; | 
| 203 |     } else if (prop.userType() == QMetaType::QVariant) { | 
| 204 |         rv.type = QQmlObjectProperty::Variant; | 
| 205 |     } else if (rv.value.isValid()) { | 
| 206 |         rv.type = QQmlObjectProperty::Basic; | 
| 207 |     } | 
| 208 |  | 
| 209 |     return rv; | 
| 210 | } | 
| 211 |  | 
| 212 | QVariant QQmlEngineDebugServiceImpl::valueContents(QVariant value) const | 
| 213 | { | 
| 214 |     // We can't send JS objects across the wire, so transform them to variant | 
| 215 |     // maps for serialization. | 
| 216 |     if (value.userType() == qMetaTypeId<QJSValue>()) | 
| 217 |         value = value.value<QJSValue>().toVariant(); | 
| 218 |     const int userType = value.userType(); | 
| 219 |  | 
| 220 |     //QObject * is not streamable. | 
| 221 |     //Convert all such instances to a String value | 
| 222 |  | 
| 223 |     if (value.userType() == QMetaType::QVariantList) { | 
| 224 |         QVariantList contents; | 
| 225 |         QVariantList list = value.toList(); | 
| 226 |         int count = list.size(); | 
| 227 |         contents.reserve(size: count); | 
| 228 |         for (int i = 0; i < count; i++) | 
| 229 |             contents << valueContents(value: list.at(i)); | 
| 230 |         return contents; | 
| 231 |     } | 
| 232 |  | 
| 233 |     if (value.userType() == QMetaType::QVariantMap) { | 
| 234 |         QVariantMap contents; | 
| 235 |         const auto map = value.toMap(); | 
| 236 |         for (auto i = map.cbegin(), end = map.cend(); i != end; ++i) | 
| 237 |              contents.insert(key: i.key(), value: valueContents(value: i.value())); | 
| 238 |         return contents; | 
| 239 |     } | 
| 240 |  | 
| 241 |     switch (userType) { | 
| 242 |     case QMetaType::QRect: | 
| 243 |     case QMetaType::QRectF: | 
| 244 |     case QMetaType::QPoint: | 
| 245 |     case QMetaType::QPointF: | 
| 246 |     case QMetaType::QSize: | 
| 247 |     case QMetaType::QSizeF: | 
| 248 |     case QMetaType::QFont: | 
| 249 |         // Don't call the toString() method on those. The stream operators are better. | 
| 250 |         return value; | 
| 251 |     case QMetaType::QJsonValue: | 
| 252 |         return value.toJsonValue().toVariant(); | 
| 253 |     case QMetaType::QJsonObject: | 
| 254 |         return value.toJsonObject().toVariantMap(); | 
| 255 |     case QMetaType::QJsonArray: | 
| 256 |         return value.toJsonArray().toVariantList(); | 
| 257 |     case QMetaType::QJsonDocument: | 
| 258 |         return value.toJsonDocument().toVariant(); | 
| 259 |     default: | 
| 260 |         if (QQmlValueTypeFactory::isValueType(idx: userType)) { | 
| 261 |             const QMetaObject *mo = QQmlValueTypeFactory::metaObjectForMetaType(type: userType); | 
| 262 |             if (mo) { | 
| 263 |                 int toStringIndex = mo->indexOfMethod(method: "toString()" ); | 
| 264 |                 if (toStringIndex != -1) { | 
| 265 |                     QMetaMethod mm = mo->method(index: toStringIndex); | 
| 266 |                     QString s; | 
| 267 |                     if (mm.invokeOnGadget(gadget: value.data(), Q_RETURN_ARG(QString, s))) | 
| 268 |                         return s; | 
| 269 |                 } | 
| 270 |             } | 
| 271 |         } | 
| 272 |  | 
| 273 |         if (isSaveable(value)) | 
| 274 |             return value; | 
| 275 |     } | 
| 276 |  | 
| 277 |     if (QQmlMetaType::isQObject(userType)) { | 
| 278 |         QObject *o = QQmlMetaType::toQObject(value); | 
| 279 |         if (o) { | 
| 280 |             QString name = o->objectName(); | 
| 281 |             if (name.isEmpty()) | 
| 282 |                 name = QStringLiteral("<unnamed object>" ); | 
| 283 |             return name; | 
| 284 |         } | 
| 285 |     } | 
| 286 |  | 
| 287 |     return QString(QStringLiteral("<unknown value>" )); | 
| 288 | } | 
| 289 |  | 
| 290 | void QQmlEngineDebugServiceImpl::buildObjectDump(QDataStream &message, | 
| 291 |                                                      QObject *object, bool recur, bool dumpProperties) | 
| 292 | { | 
| 293 |     message << objectData(object); | 
| 294 |  | 
| 295 |     QObjectList children = object->children(); | 
| 296 |  | 
| 297 |     int childrenCount = children.count(); | 
| 298 |     for (int ii = 0; ii < children.count(); ++ii) { | 
| 299 |         if (qobject_cast<QQmlContext*>(object: children[ii])) | 
| 300 |             --childrenCount; | 
| 301 |     } | 
| 302 |  | 
| 303 |     message << childrenCount << recur; | 
| 304 |  | 
| 305 |     QList<QQmlObjectProperty> fakeProperties; | 
| 306 |  | 
| 307 |     for (int ii = 0; ii < children.count(); ++ii) { | 
| 308 |         QObject *child = children.at(i: ii); | 
| 309 |         if (qobject_cast<QQmlContext*>(object: child)) | 
| 310 |             continue; | 
| 311 |          if (recur) | 
| 312 |              buildObjectDump(message, object: child, recur, dumpProperties); | 
| 313 |          else | 
| 314 |              message << objectData(child); | 
| 315 |     } | 
| 316 |  | 
| 317 |     if (!dumpProperties) { | 
| 318 |         message << 0; | 
| 319 |         return; | 
| 320 |     } | 
| 321 |  | 
| 322 |     QList<int> propertyIndexes; | 
| 323 |     for (int ii = 0; ii < object->metaObject()->propertyCount(); ++ii) { | 
| 324 |         if (object->metaObject()->property(index: ii).isScriptable()) | 
| 325 |             propertyIndexes << ii; | 
| 326 |     } | 
| 327 |  | 
| 328 |     QQmlData *ddata = QQmlData::get(object); | 
| 329 |     if (ddata && ddata->signalHandlers) { | 
| 330 |         QQmlBoundSignal *signalHandler = ddata->signalHandlers; | 
| 331 |  | 
| 332 |         while (signalHandler) { | 
| 333 |             QQmlObjectProperty prop; | 
| 334 |             prop.type = QQmlObjectProperty::SignalProperty; | 
| 335 |             prop.hasNotifySignal = false; | 
| 336 |             QQmlBoundSignalExpression *expr = signalHandler->expression(); | 
| 337 |             if (expr) { | 
| 338 |                 prop.value = expr->expression(); | 
| 339 |                 QObject *scope = expr->scopeObject(); | 
| 340 |                 if (scope) { | 
| 341 |                     const QByteArray methodName = QMetaObjectPrivate::signal(m: scope->metaObject(), | 
| 342 |                                                                              signal_index: signalHandler->signalIndex()).name(); | 
| 343 |                     const QLatin1String methodNameStr(methodName); | 
| 344 |                     if (methodNameStr.size() != 0) { | 
| 345 |                         prop.name = QLatin1String("on" ) + QChar(methodNameStr.at(i: 0)).toUpper() | 
| 346 |                                 + methodNameStr.mid(pos: 1); | 
| 347 |                     } | 
| 348 |                 } | 
| 349 |             } | 
| 350 |             fakeProperties << prop; | 
| 351 |  | 
| 352 |             signalHandler = nextSignal(prev: signalHandler); | 
| 353 |         } | 
| 354 |     } | 
| 355 |  | 
| 356 |     message << propertyIndexes.size() + fakeProperties.count(); | 
| 357 |  | 
| 358 |     for (int ii = 0; ii < propertyIndexes.size(); ++ii) | 
| 359 |         message << propertyData(obj: object, propIdx: propertyIndexes.at(i: ii)); | 
| 360 |  | 
| 361 |     for (int ii = 0; ii < fakeProperties.count(); ++ii) | 
| 362 |         message << fakeProperties[ii]; | 
| 363 | } | 
| 364 |  | 
| 365 | void QQmlEngineDebugServiceImpl::prepareDeferredObjects(QObject *obj) | 
| 366 | { | 
| 367 |     qmlExecuteDeferred(obj); | 
| 368 |  | 
| 369 |     QObjectList children = obj->children(); | 
| 370 |     for (int ii = 0; ii < children.count(); ++ii) { | 
| 371 |         QObject *child = children.at(i: ii); | 
| 372 |         prepareDeferredObjects(obj: child); | 
| 373 |     } | 
| 374 |  | 
| 375 | } | 
| 376 |  | 
| 377 | void QQmlEngineDebugServiceImpl::storeObjectIds(QObject *co) | 
| 378 | { | 
| 379 |     QQmlDebugService::idForObject(co); | 
| 380 |     QObjectList children = co->children(); | 
| 381 |     for (int ii = 0; ii < children.count(); ++ii) | 
| 382 |         storeObjectIds(co: children.at(i: ii)); | 
| 383 | } | 
| 384 |  | 
| 385 | void QQmlEngineDebugServiceImpl::buildObjectList(QDataStream &message, | 
| 386 |                                              QQmlContext *ctxt, | 
| 387 |                                              const QList<QPointer<QObject> > &instances) | 
| 388 | { | 
| 389 |     if (!ctxt->isValid()) | 
| 390 |         return; | 
| 391 |  | 
| 392 |     QQmlContextData *p = QQmlContextData::get(context: ctxt); | 
| 393 |  | 
| 394 |     QString ctxtName = ctxt->objectName(); | 
| 395 |     int ctxtId = QQmlDebugService::idForObject(ctxt); | 
| 396 |     if (ctxt->contextObject()) | 
| 397 |         storeObjectIds(co: ctxt->contextObject()); | 
| 398 |  | 
| 399 |     message << ctxtName << ctxtId; | 
| 400 |  | 
| 401 |     int count = 0; | 
| 402 |  | 
| 403 |     QQmlContextData *child = p->childContexts; | 
| 404 |     while (child) { | 
| 405 |         ++count; | 
| 406 |         child = child->nextChild; | 
| 407 |     } | 
| 408 |  | 
| 409 |     message << count; | 
| 410 |  | 
| 411 |     child = p->childContexts; | 
| 412 |     while (child) { | 
| 413 |         buildObjectList(message, ctxt: child->asQQmlContext(), instances); | 
| 414 |         child = child->nextChild; | 
| 415 |     } | 
| 416 |  | 
| 417 |     count = 0; | 
| 418 |     for (int ii = 0; ii < instances.count(); ++ii) { | 
| 419 |         QQmlData *data = QQmlData::get(object: instances.at(i: ii)); | 
| 420 |         if (data->context == p) | 
| 421 |             count ++; | 
| 422 |     } | 
| 423 |     message << count; | 
| 424 |  | 
| 425 |     for (int ii = 0; ii < instances.count(); ++ii) { | 
| 426 |         QQmlData *data = QQmlData::get(object: instances.at(i: ii)); | 
| 427 |         if (data->context == p) | 
| 428 |             message << objectData(instances.at(i: ii)); | 
| 429 |     } | 
| 430 | } | 
| 431 |  | 
| 432 | void QQmlEngineDebugServiceImpl::buildStatesList(bool cleanList, | 
| 433 |                                              const QList<QPointer<QObject> > &instances) | 
| 434 | { | 
| 435 |     if (m_statesDelegate) | 
| 436 |         m_statesDelegate->buildStatesList(cleanList, instances); | 
| 437 | } | 
| 438 |  | 
| 439 | QQmlEngineDebugServiceImpl::QQmlObjectData | 
| 440 | QQmlEngineDebugServiceImpl::objectData(QObject *object) | 
| 441 | { | 
| 442 |     QQmlData *ddata = QQmlData::get(object); | 
| 443 |     QQmlObjectData rv; | 
| 444 |     if (ddata && ddata->outerContext) { | 
| 445 |         rv.url = ddata->outerContext->url(); | 
| 446 |         rv.lineNumber = ddata->lineNumber; | 
| 447 |         rv.columnNumber = ddata->columnNumber; | 
| 448 |     } else { | 
| 449 |         rv.lineNumber = -1; | 
| 450 |         rv.columnNumber = -1; | 
| 451 |     } | 
| 452 |  | 
| 453 |     QQmlContext *context = qmlContext(object); | 
| 454 |     if (context && context->isValid()) | 
| 455 |         rv.idString = QQmlContextData::get(context)->findObjectId(obj: object); | 
| 456 |  | 
| 457 |     rv.objectName = object->objectName(); | 
| 458 |     rv.objectId = QQmlDebugService::idForObject(object); | 
| 459 |     rv.contextId = QQmlDebugService::idForObject(qmlContext(object)); | 
| 460 |     rv.parentId = QQmlDebugService::idForObject(object->parent()); | 
| 461 |     rv.objectType = QQmlMetaType::prettyTypeName(object); | 
| 462 |     return rv; | 
| 463 | } | 
| 464 |  | 
| 465 | void QQmlEngineDebugServiceImpl::messageReceived(const QByteArray &message) | 
| 466 | { | 
| 467 |     emit scheduleMessage(message); | 
| 468 | } | 
| 469 |  | 
| 470 | /*! | 
| 471 |     Returns a list of objects matching the given filename, line and column. | 
| 472 | */ | 
| 473 | QList<QObject*> QQmlEngineDebugServiceImpl::objectForLocationInfo(const QString &filename, | 
| 474 |                                                               int lineNumber, int columnNumber) | 
| 475 | { | 
| 476 |     QList<QObject *> objects; | 
| 477 |     const QHash<int, QObject *> &hash = objectsForIds(); | 
| 478 |     for (QHash<int, QObject *>::ConstIterator i = hash.constBegin(); i != hash.constEnd(); ++i) { | 
| 479 |         QQmlData *ddata = QQmlData::get(object: i.value()); | 
| 480 |         if (ddata && ddata->outerContext && ddata->outerContext->isValid()) { | 
| 481 |             if (QFileInfo(ddata->outerContext->urlString()).fileName() == filename && | 
| 482 |                 ddata->lineNumber == lineNumber && | 
| 483 |                 ddata->columnNumber >= columnNumber) { | 
| 484 |                 objects << i.value(); | 
| 485 |             } | 
| 486 |         } | 
| 487 |     } | 
| 488 |     return objects; | 
| 489 | } | 
| 490 |  | 
| 491 | void QQmlEngineDebugServiceImpl::processMessage(const QByteArray &message) | 
| 492 | { | 
| 493 |     QQmlDebugPacket ds(message); | 
| 494 |  | 
| 495 |     QByteArray type; | 
| 496 |     qint32 queryId; | 
| 497 |     ds >> type >> queryId; | 
| 498 |  | 
| 499 |     QQmlDebugPacket rs; | 
| 500 |  | 
| 501 |     if (type == "LIST_ENGINES" ) { | 
| 502 |         rs << QByteArray("LIST_ENGINES_R" ); | 
| 503 |         rs << queryId << m_engines.count(); | 
| 504 |  | 
| 505 |         for (int ii = 0; ii < m_engines.count(); ++ii) { | 
| 506 |             QJSEngine *engine = m_engines.at(i: ii); | 
| 507 |  | 
| 508 |             QString engineName = engine->objectName(); | 
| 509 |             qint32 engineId = QQmlDebugService::idForObject(engine); | 
| 510 |  | 
| 511 |             rs << engineName << engineId; | 
| 512 |         } | 
| 513 |  | 
| 514 |     } else if (type == "LIST_OBJECTS" ) { | 
| 515 |         qint32 engineId = -1; | 
| 516 |         ds >> engineId; | 
| 517 |  | 
| 518 |         QQmlEngine *engine = | 
| 519 |                 qobject_cast<QQmlEngine *>(object: QQmlDebugService::objectForId(id: engineId)); | 
| 520 |  | 
| 521 |         rs << QByteArray("LIST_OBJECTS_R" ) << queryId; | 
| 522 |  | 
| 523 |         if (engine) { | 
| 524 |             QQmlContext *rootContext = engine->rootContext(); | 
| 525 |             // Clean deleted objects | 
| 526 |             QQmlContextPrivate *ctxtPriv = QQmlContextPrivate::get(context: rootContext); | 
| 527 |             for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) { | 
| 528 |                 if (!ctxtPriv->instances.at(i: ii)) { | 
| 529 |                     ctxtPriv->instances.removeAt(i: ii); | 
| 530 |                     --ii; | 
| 531 |                 } | 
| 532 |             } | 
| 533 |             buildObjectList(message&: rs, ctxt: rootContext, instances: ctxtPriv->instances); | 
| 534 |             buildStatesList(cleanList: true, instances: ctxtPriv->instances); | 
| 535 |         } | 
| 536 |  | 
| 537 |     } else if (type == "FETCH_OBJECT" ) { | 
| 538 |         qint32 objectId; | 
| 539 |         bool recurse; | 
| 540 |         bool dumpProperties = true; | 
| 541 |  | 
| 542 |         ds >> objectId >> recurse >> dumpProperties; | 
| 543 |  | 
| 544 |         QObject *object = QQmlDebugService::objectForId(id: objectId); | 
| 545 |  | 
| 546 |         rs << QByteArray("FETCH_OBJECT_R" ) << queryId; | 
| 547 |  | 
| 548 |         if (object) { | 
| 549 |             if (recurse) | 
| 550 |                 prepareDeferredObjects(obj: object); | 
| 551 |             buildObjectDump(message&: rs, object, recur: recurse, dumpProperties); | 
| 552 |         } | 
| 553 |  | 
| 554 |     } else if (type == "FETCH_OBJECTS_FOR_LOCATION" ) { | 
| 555 |         QString file; | 
| 556 |         qint32 lineNumber; | 
| 557 |         qint32 columnNumber; | 
| 558 |         bool recurse; | 
| 559 |         bool dumpProperties = true; | 
| 560 |  | 
| 561 |         ds >> file >> lineNumber >> columnNumber >> recurse >> dumpProperties; | 
| 562 |  | 
| 563 |         const QList<QObject*> objects = objectForLocationInfo(filename: file, lineNumber, columnNumber); | 
| 564 |  | 
| 565 |         rs << QByteArray("FETCH_OBJECTS_FOR_LOCATION_R" ) << queryId | 
| 566 |            << objects.count(); | 
| 567 |  | 
| 568 |         for (QObject *object : objects) { | 
| 569 |             if (recurse) | 
| 570 |                 prepareDeferredObjects(obj: object); | 
| 571 |             buildObjectDump(message&: rs, object, recur: recurse, dumpProperties); | 
| 572 |         } | 
| 573 |  | 
| 574 |     } else if (type == "WATCH_OBJECT" ) { | 
| 575 |         qint32 objectId; | 
| 576 |  | 
| 577 |         ds >> objectId; | 
| 578 |         bool ok = m_watch->addWatch(id: queryId, objectId); | 
| 579 |  | 
| 580 |         rs << QByteArray("WATCH_OBJECT_R" ) << queryId << ok; | 
| 581 |  | 
| 582 |     } else if (type == "WATCH_PROPERTY" ) { | 
| 583 |         qint32 objectId; | 
| 584 |         QByteArray property; | 
| 585 |  | 
| 586 |         ds >> objectId >> property; | 
| 587 |         bool ok = m_watch->addWatch(id: queryId, objectId, property); | 
| 588 |  | 
| 589 |         rs << QByteArray("WATCH_PROPERTY_R" ) << queryId << ok; | 
| 590 |  | 
| 591 |     } else if (type == "WATCH_EXPR_OBJECT" ) { | 
| 592 |         qint32 debugId; | 
| 593 |         QString expr; | 
| 594 |  | 
| 595 |         ds >> debugId >> expr; | 
| 596 |         bool ok = m_watch->addWatch(id: queryId, objectId: debugId, expr); | 
| 597 |  | 
| 598 |         rs << QByteArray("WATCH_EXPR_OBJECT_R" ) << queryId << ok; | 
| 599 |  | 
| 600 |     } else if (type == "NO_WATCH" ) { | 
| 601 |         bool ok = m_watch->removeWatch(id: queryId); | 
| 602 |  | 
| 603 |         rs << QByteArray("NO_WATCH_R" ) << queryId << ok; | 
| 604 |  | 
| 605 |     } else if (type == "EVAL_EXPRESSION" ) { | 
| 606 |         qint32 objectId; | 
| 607 |         QString expr; | 
| 608 |  | 
| 609 |         ds >> objectId >> expr; | 
| 610 |         qint32 engineId = -1; | 
| 611 |         if (!ds.atEnd()) | 
| 612 |             ds >> engineId; | 
| 613 |  | 
| 614 |         QObject *object = QQmlDebugService::objectForId(id: objectId); | 
| 615 |         QQmlContext *context = qmlContext(object); | 
| 616 |         if (!context || !context->isValid()) { | 
| 617 |             QQmlEngine *engine = qobject_cast<QQmlEngine *>( | 
| 618 |                         object: QQmlDebugService::objectForId(id: engineId)); | 
| 619 |             if (engine && m_engines.contains(t: engine)) | 
| 620 |                 context = engine->rootContext(); | 
| 621 |         } | 
| 622 |         QVariant result; | 
| 623 |         if (context && context->isValid()) { | 
| 624 |             QQmlExpression exprObj(context, object, expr); | 
| 625 |             bool undefined = false; | 
| 626 |             QVariant value = exprObj.evaluate(valueIsUndefined: &undefined); | 
| 627 |             if (undefined) | 
| 628 |                 result = QString(QStringLiteral("<undefined>" )); | 
| 629 |             else | 
| 630 |                 result = valueContents(value); | 
| 631 |         } else { | 
| 632 |             result = QString(QStringLiteral("<unknown context>" )); | 
| 633 |         } | 
| 634 |  | 
| 635 |         rs << QByteArray("EVAL_EXPRESSION_R" ) << queryId << result; | 
| 636 |  | 
| 637 |     } else if (type == "SET_BINDING" ) { | 
| 638 |         qint32 objectId; | 
| 639 |         QString propertyName; | 
| 640 |         QVariant expr; | 
| 641 |         bool isLiteralValue; | 
| 642 |         QString filename; | 
| 643 |         qint32 line; | 
| 644 |         ds >> objectId >> propertyName >> expr >> isLiteralValue >> | 
| 645 |               filename >> line; | 
| 646 |         bool ok = setBinding(objectId, propertyName, expression: expr, isLiteralValue, | 
| 647 |                              filename, line); | 
| 648 |  | 
| 649 |         rs << QByteArray("SET_BINDING_R" ) << queryId << ok; | 
| 650 |  | 
| 651 |     } else if (type == "RESET_BINDING" ) { | 
| 652 |         qint32 objectId; | 
| 653 |         QString propertyName; | 
| 654 |         ds >> objectId >> propertyName; | 
| 655 |         bool ok = resetBinding(objectId, propertyName); | 
| 656 |  | 
| 657 |         rs << QByteArray("RESET_BINDING_R" ) << queryId << ok; | 
| 658 |  | 
| 659 |     } else if (type == "SET_METHOD_BODY" ) { | 
| 660 |         qint32 objectId; | 
| 661 |         QString methodName; | 
| 662 |         QString methodBody; | 
| 663 |         ds >> objectId >> methodName >> methodBody; | 
| 664 |         bool ok = setMethodBody(objectId, method: methodName, body: methodBody); | 
| 665 |  | 
| 666 |         rs << QByteArray("SET_METHOD_BODY_R" ) << queryId << ok; | 
| 667 |  | 
| 668 |     } | 
| 669 |     emit messageToClient(name: name(), message: rs.data()); | 
| 670 | } | 
| 671 |  | 
| 672 | bool QQmlEngineDebugServiceImpl::setBinding(int objectId, | 
| 673 |                                                 const QString &propertyName, | 
| 674 |                                                 const QVariant &expression, | 
| 675 |                                                 bool isLiteralValue, | 
| 676 |                                                 QString filename, | 
| 677 |                                                 int line, | 
| 678 |                                                 int column) | 
| 679 | { | 
| 680 |     bool ok = true; | 
| 681 |     QObject *object = objectForId(id: objectId); | 
| 682 |     QQmlContext *context = qmlContext(object); | 
| 683 |  | 
| 684 |     if (object && context && context->isValid()) { | 
| 685 |         QQmlProperty property(object, propertyName, context); | 
| 686 |         if (property.isValid()) { | 
| 687 |  | 
| 688 |             bool inBaseState = true; | 
| 689 |             if (m_statesDelegate) { | 
| 690 |                 m_statesDelegate->updateBinding(context, property, expression, isLiteralValue, | 
| 691 |                                                 fileName: filename, line, column, inBaseState: &inBaseState); | 
| 692 |             } | 
| 693 |  | 
| 694 |             if (inBaseState) { | 
| 695 |                 if (isLiteralValue) { | 
| 696 |                     property.write(expression); | 
| 697 |                 } else if (hasValidSignal(object, propertyName)) { | 
| 698 |                     QQmlBoundSignalExpression *qmlExpression = new QQmlBoundSignalExpression(object, QQmlPropertyPrivate::get(p: property)->signalIndex(), | 
| 699 |                                                                                              QQmlContextData::get(context), object, expression.toString(), | 
| 700 |                                                                                              filename, line, column); | 
| 701 |                     QQmlPropertyPrivate::takeSignalExpression(that: property, qmlExpression); | 
| 702 |                 } else if (property.isProperty()) { | 
| 703 |                     QQmlBinding *binding = QQmlBinding::create(&QQmlPropertyPrivate::get(p: property)->core, expression.toString(), object, QQmlContextData::get(context), url: filename, lineNumber: line); | 
| 704 |                     binding->setTarget(property); | 
| 705 |                     QQmlPropertyPrivate::setBinding(binding); | 
| 706 |                     binding->update(); | 
| 707 |                 } else { | 
| 708 |                     ok = false; | 
| 709 |                     qWarning() << "QQmlEngineDebugService::setBinding: unable to set property"  << propertyName << "on object"  << object; | 
| 710 |                 } | 
| 711 |             } | 
| 712 |  | 
| 713 |         } else { | 
| 714 |             // not a valid property | 
| 715 |             if (m_statesDelegate) | 
| 716 |                 ok = m_statesDelegate->setBindingForInvalidProperty(object, propertyName, expression, isLiteralValue); | 
| 717 |             if (!ok) | 
| 718 |                 qWarning() << "QQmlEngineDebugService::setBinding: unable to set property"  << propertyName << "on object"  << object; | 
| 719 |         } | 
| 720 |     } | 
| 721 |     return ok; | 
| 722 | } | 
| 723 |  | 
| 724 | bool QQmlEngineDebugServiceImpl::resetBinding(int objectId, const QString &propertyName) | 
| 725 | { | 
| 726 |     QObject *object = objectForId(id: objectId); | 
| 727 |     QQmlContext *context = qmlContext(object); | 
| 728 |  | 
| 729 |     if (object && context && context->isValid()) { | 
| 730 |         QStringRef parentPropertyRef(&propertyName); | 
| 731 |         const int idx = parentPropertyRef.indexOf(ch: QLatin1Char('.')); | 
| 732 |         if (idx != -1) | 
| 733 |             parentPropertyRef = parentPropertyRef.left(n: idx); | 
| 734 |  | 
| 735 |         const QByteArray parentProperty = parentPropertyRef.toLatin1(); | 
| 736 |         if (object->property(name: parentProperty).isValid()) { | 
| 737 |             QQmlProperty property(object, propertyName); | 
| 738 |             QQmlPropertyPrivate::removeBinding(that: property); | 
| 739 |             if (property.isResettable()) { | 
| 740 |                 // Note: this will reset the property in any case, without regard to states | 
| 741 |                 // Right now almost no QQuickItem has reset methods for its properties (with the | 
| 742 |                 // notable exception of QQuickAnchors), so this is not a big issue | 
| 743 |                 // later on, setBinding does take states into account | 
| 744 |                 property.reset(); | 
| 745 |             } else { | 
| 746 |                 // overwrite with default value | 
| 747 |                 QQmlType objType = QQmlMetaType::qmlType(object->metaObject()); | 
| 748 |                 if (objType.isValid()) { | 
| 749 |                     if (QObject *emptyObject = objType.create()) { | 
| 750 |                         if (emptyObject->property(name: parentProperty).isValid()) { | 
| 751 |                             QVariant defaultValue = QQmlProperty(emptyObject, propertyName).read(); | 
| 752 |                             if (defaultValue.isValid()) { | 
| 753 |                                 setBinding(objectId, propertyName, expression: defaultValue, isLiteralValue: true); | 
| 754 |                             } | 
| 755 |                         } | 
| 756 |                         delete emptyObject; | 
| 757 |                     } | 
| 758 |                 } | 
| 759 |             } | 
| 760 |             return true; | 
| 761 |         } | 
| 762 |  | 
| 763 |         if (hasValidSignal(object, propertyName)) { | 
| 764 |             QQmlProperty property(object, propertyName, context); | 
| 765 |             QQmlPropertyPrivate::setSignalExpression(that: property, nullptr); | 
| 766 |             return true; | 
| 767 |         } | 
| 768 |  | 
| 769 |         if (m_statesDelegate) { | 
| 770 |             m_statesDelegate->resetBindingForInvalidProperty(object, propertyName); | 
| 771 |             return true; | 
| 772 |         } | 
| 773 |  | 
| 774 |         return false; | 
| 775 |     } | 
| 776 |     // object or context null. | 
| 777 |     return false; | 
| 778 | } | 
| 779 |  | 
| 780 | bool QQmlEngineDebugServiceImpl::setMethodBody(int objectId, const QString &method, const QString &body) | 
| 781 | { | 
| 782 |     QObject *object = objectForId(id: objectId); | 
| 783 |     QQmlContext *context = qmlContext(object); | 
| 784 |     if (!object || !context || !context->isValid()) | 
| 785 |         return false; | 
| 786 |     QQmlContextData *contextData = QQmlContextData::get(context); | 
| 787 |  | 
| 788 |     QQmlPropertyData dummy; | 
| 789 |     QQmlPropertyData *prop = | 
| 790 |             QQmlPropertyCache::property(engine: context->engine(), obj: object, name: method, context: contextData, local&: dummy); | 
| 791 |  | 
| 792 |     if (!prop || !prop->isVMEFunction()) | 
| 793 |         return false; | 
| 794 |  | 
| 795 |     QMetaMethod metaMethod = object->metaObject()->method(index: prop->coreIndex()); | 
| 796 |     QList<QByteArray> paramNames = metaMethod.parameterNames(); | 
| 797 |  | 
| 798 |     QString paramStr; | 
| 799 |     for (int ii = 0; ii < paramNames.count(); ++ii) { | 
| 800 |         if (ii != 0) paramStr.append(c: QLatin1Char(',')); | 
| 801 |         paramStr.append(s: QString::fromUtf8(str: paramNames.at(i: ii))); | 
| 802 |     } | 
| 803 |  | 
| 804 |     const QString jsfunction = QLatin1String("(function " ) + method + QLatin1Char('(') + paramStr + | 
| 805 |             QLatin1String(") {" ) + body + QLatin1String("\n})" ); | 
| 806 |  | 
| 807 |     QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(obj: object); | 
| 808 |     Q_ASSERT(vmeMetaObject); // the fact we found the property above should guarentee this | 
| 809 |  | 
| 810 |     QV4::ExecutionEngine *v4 = qmlEngine(object)->handle(); | 
| 811 |     QV4::Scope scope(v4); | 
| 812 |  | 
| 813 |     int lineNumber = 0; | 
| 814 |     QV4::ScopedFunctionObject oldMethod(scope, vmeMetaObject->vmeMethod(index: prop->coreIndex())); | 
| 815 |     if (oldMethod && oldMethod->d()->function) { | 
| 816 |         lineNumber = oldMethod->d()->function->compiledFunction->location.line; | 
| 817 |     } | 
| 818 |     QV4::ScopedValue v(scope, QQmlJavaScriptExpression::evalFunction(ctxt: contextData, scope: object, code: jsfunction, filename: contextData->urlString(), line: lineNumber)); | 
| 819 |     vmeMetaObject->setVmeMethod(index: prop->coreIndex(), function: v); | 
| 820 |     return true; | 
| 821 | } | 
| 822 |  | 
| 823 | void QQmlEngineDebugServiceImpl::propertyChanged( | 
| 824 |         qint32 id, qint32 objectId, const QMetaProperty &property, const QVariant &value) | 
| 825 | { | 
| 826 |     QQmlDebugPacket rs; | 
| 827 |     rs << QByteArray("UPDATE_WATCH" ) << id << objectId << QByteArray(property.name()) << valueContents(value); | 
| 828 |     emit messageToClient(name: name(), message: rs.data()); | 
| 829 | } | 
| 830 |  | 
| 831 | void QQmlEngineDebugServiceImpl::engineAboutToBeAdded(QJSEngine *engine) | 
| 832 | { | 
| 833 |     Q_ASSERT(engine); | 
| 834 |     Q_ASSERT(!m_engines.contains(engine)); | 
| 835 |  | 
| 836 |     m_engines.append(t: engine); | 
| 837 |     emit attachedToEngine(engine); | 
| 838 | } | 
| 839 |  | 
| 840 | void QQmlEngineDebugServiceImpl::engineAboutToBeRemoved(QJSEngine *engine) | 
| 841 | { | 
| 842 |     Q_ASSERT(engine); | 
| 843 |     Q_ASSERT(m_engines.contains(engine)); | 
| 844 |  | 
| 845 |     m_engines.removeAll(t: engine); | 
| 846 |     emit detachedFromEngine(engine); | 
| 847 | } | 
| 848 |  | 
| 849 | void QQmlEngineDebugServiceImpl::objectCreated(QJSEngine *engine, QObject *object) | 
| 850 | { | 
| 851 |     Q_ASSERT(engine); | 
| 852 |     if (!m_engines.contains(t: engine)) | 
| 853 |         return; | 
| 854 |  | 
| 855 |     qint32 engineId = QQmlDebugService::idForObject(engine); | 
| 856 |     qint32 objectId = QQmlDebugService::idForObject(object); | 
| 857 |     qint32 parentId = QQmlDebugService::idForObject(object->parent()); | 
| 858 |  | 
| 859 |     QQmlDebugPacket rs; | 
| 860 |  | 
| 861 |     //unique queryId -1 | 
| 862 |     rs << QByteArray("OBJECT_CREATED" ) << qint32(-1) << engineId << objectId << parentId; | 
| 863 |     emit messageToClient(name: name(), message: rs.data()); | 
| 864 | } | 
| 865 |  | 
| 866 | void QQmlEngineDebugServiceImpl::setStatesDelegate(QQmlDebugStatesDelegate *delegate) | 
| 867 | { | 
| 868 |     m_statesDelegate = delegate; | 
| 869 | } | 
| 870 |  | 
| 871 | QT_END_NAMESPACE | 
| 872 |  | 
| 873 | #include "moc_qqmlenginedebugservice.cpp" | 
| 874 |  |