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 | |