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(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 | NullDevice nullDevice; |
98 | QDataStream fakeStream(&nullDevice); |
99 | return QMetaType::save(fakeStream, static_cast<int>(value.type()), value.constData()); |
100 | } |
101 | |
102 | QQmlEngineDebugServiceImpl::QQmlEngineDebugServiceImpl(QObject *parent) : |
103 | QQmlEngineDebugService(2, parent), m_watch(new QQmlWatcher(this)), m_statesDelegate(nullptr) |
104 | { |
105 | connect(m_watch, &QQmlWatcher::propertyChanged, |
106 | this, &QQmlEngineDebugServiceImpl::propertyChanged); |
107 | |
108 | // Move the message into the correct thread for processing |
109 | connect(this, &QQmlEngineDebugServiceImpl::scheduleMessage, |
110 | this, &QQmlEngineDebugServiceImpl::processMessage, Qt::QueuedConnection); |
111 | } |
112 | |
113 | QQmlEngineDebugServiceImpl::~QQmlEngineDebugServiceImpl() |
114 | { |
115 | delete m_statesDelegate; |
116 | } |
117 | |
118 | QDataStream &operator<<(QDataStream &ds, |
119 | const QQmlEngineDebugServiceImpl::QQmlObjectData &data) |
120 | { |
121 | ds << data.url << data.lineNumber << data.columnNumber << data.idString |
122 | << data.objectName << data.objectType << data.objectId << data.contextId |
123 | << data.parentId; |
124 | return ds; |
125 | } |
126 | |
127 | QDataStream &operator>>(QDataStream &ds, |
128 | QQmlEngineDebugServiceImpl::QQmlObjectData &data) |
129 | { |
130 | ds >> data.url >> data.lineNumber >> data.columnNumber >> data.idString |
131 | >> data.objectName >> data.objectType >> data.objectId >> data.contextId |
132 | >> data.parentId; |
133 | return ds; |
134 | } |
135 | |
136 | QDataStream &operator<<(QDataStream &ds, |
137 | const QQmlEngineDebugServiceImpl::QQmlObjectProperty &data) |
138 | { |
139 | ds << (int)data.type << data.name; |
140 | ds << (isSaveable(data.value) ? data.value : QVariant()); |
141 | ds << data.valueTypeName << data.binding << data.hasNotifySignal; |
142 | return ds; |
143 | } |
144 | |
145 | QDataStream &operator>>(QDataStream &ds, |
146 | QQmlEngineDebugServiceImpl::QQmlObjectProperty &data) |
147 | { |
148 | int type; |
149 | ds >> type >> data.name >> data.value >> data.valueTypeName |
150 | >> data.binding >> data.hasNotifySignal; |
151 | data.type = (QQmlEngineDebugServiceImpl::QQmlObjectProperty::Type)type; |
152 | return ds; |
153 | } |
154 | |
155 | static inline bool isSignalPropertyName(const QString &signalName) |
156 | { |
157 | // see QmlCompiler::isSignalPropertyName |
158 | return signalName.length() >= 3 && signalName.startsWith(QLatin1String("on" )) && |
159 | signalName.at(2).isLetter() && signalName.at(2).isUpper(); |
160 | } |
161 | |
162 | static bool hasValidSignal(QObject *object, const QString &propertyName) |
163 | { |
164 | if (!isSignalPropertyName(propertyName)) |
165 | return false; |
166 | |
167 | QString signalName = propertyName.mid(2); |
168 | signalName[0] = signalName.at(0).toLower(); |
169 | |
170 | int sigIdx = QQmlPropertyPrivate::findSignalByName(object->metaObject(), signalName.toLatin1()).methodIndex(); |
171 | |
172 | if (sigIdx == -1) |
173 | return false; |
174 | |
175 | return true; |
176 | } |
177 | |
178 | QQmlEngineDebugServiceImpl::QQmlObjectProperty |
179 | QQmlEngineDebugServiceImpl::propertyData(QObject *obj, int propIdx) |
180 | { |
181 | QQmlObjectProperty rv; |
182 | |
183 | QMetaProperty prop = obj->metaObject()->property(propIdx); |
184 | |
185 | rv.type = QQmlObjectProperty::Unknown; |
186 | rv.valueTypeName = QString::fromUtf8(prop.typeName()); |
187 | rv.name = QString::fromUtf8(prop.name()); |
188 | rv.hasNotifySignal = prop.hasNotifySignal(); |
189 | QQmlAbstractBinding *binding = |
190 | QQmlPropertyPrivate::binding(QQmlProperty(obj, rv.name)); |
191 | if (binding) |
192 | rv.binding = binding->expression(); |
193 | |
194 | if (QQmlValueTypeFactory::isValueType(prop.userType())) { |
195 | rv.type = QQmlObjectProperty::Basic; |
196 | } else if (QQmlMetaType::isQObject(prop.userType())) { |
197 | rv.type = QQmlObjectProperty::Object; |
198 | } else if (QQmlMetaType::isList(prop.userType())) { |
199 | rv.type = QQmlObjectProperty::List; |
200 | } else if (prop.userType() == QMetaType::QVariant) { |
201 | rv.type = QQmlObjectProperty::Variant; |
202 | } |
203 | |
204 | QVariant value; |
205 | if (rv.type != QQmlObjectProperty::Unknown && prop.userType() != 0) { |
206 | value = prop.read(obj); |
207 | } |
208 | rv.value = valueContents(value); |
209 | |
210 | return rv; |
211 | } |
212 | |
213 | QVariant QQmlEngineDebugServiceImpl::valueContents(QVariant value) const |
214 | { |
215 | // We can't send JS objects across the wire, so transform them to variant |
216 | // maps for serialization. |
217 | if (value.userType() == qMetaTypeId<QJSValue>()) |
218 | value = value.value<QJSValue>().toVariant(); |
219 | const int userType = value.userType(); |
220 | |
221 | //QObject * is not streamable. |
222 | //Convert all such instances to a String value |
223 | |
224 | if (value.type() == QVariant::List) { |
225 | QVariantList contents; |
226 | QVariantList list = value.toList(); |
227 | int count = list.size(); |
228 | contents.reserve(count); |
229 | for (int i = 0; i < count; i++) |
230 | contents << valueContents(list.at(i)); |
231 | return contents; |
232 | } |
233 | |
234 | if (value.type() == QVariant::Map) { |
235 | QVariantMap contents; |
236 | const auto map = value.toMap(); |
237 | for (auto i = map.cbegin(), end = map.cend(); i != end; ++i) |
238 | contents.insert(i.key(), valueContents(i.value())); |
239 | return contents; |
240 | } |
241 | |
242 | switch (userType) { |
243 | case QMetaType::QRect: |
244 | case QMetaType::QRectF: |
245 | case QMetaType::QPoint: |
246 | case QMetaType::QPointF: |
247 | case QMetaType::QSize: |
248 | case QMetaType::QSizeF: |
249 | case QMetaType::QFont: |
250 | // Don't call the toString() method on those. The stream operators are better. |
251 | return value; |
252 | case QMetaType::QJsonValue: |
253 | return value.toJsonValue().toVariant(); |
254 | case QMetaType::QJsonObject: |
255 | return value.toJsonObject().toVariantMap(); |
256 | case QMetaType::QJsonArray: |
257 | return value.toJsonArray().toVariantList(); |
258 | case QMetaType::QJsonDocument: |
259 | return value.toJsonDocument().toVariant(); |
260 | default: |
261 | if (QQmlValueTypeFactory::isValueType(userType)) { |
262 | const QMetaObject *mo = QQmlValueTypeFactory::metaObjectForMetaType(userType); |
263 | if (mo) { |
264 | int toStringIndex = mo->indexOfMethod("toString()" ); |
265 | if (toStringIndex != -1) { |
266 | QMetaMethod mm = mo->method(toStringIndex); |
267 | QString s; |
268 | if (mm.invokeOnGadget(value.data(), Q_RETURN_ARG(QString, s))) |
269 | return s; |
270 | } |
271 | } |
272 | |
273 | if (isSaveable(value)) |
274 | return value; |
275 | } |
276 | } |
277 | |
278 | if (QQmlMetaType::isQObject(userType)) { |
279 | QObject *o = QQmlMetaType::toQObject(value); |
280 | if (o) { |
281 | QString name = o->objectName(); |
282 | if (name.isEmpty()) |
283 | name = QStringLiteral("<unnamed object>" ); |
284 | return name; |
285 | } |
286 | } |
287 | |
288 | return QString(QStringLiteral("<unknown value>" )); |
289 | } |
290 | |
291 | void QQmlEngineDebugServiceImpl::buildObjectDump(QDataStream &message, |
292 | QObject *object, bool recur, bool dumpProperties) |
293 | { |
294 | message << objectData(object); |
295 | |
296 | QObjectList children = object->children(); |
297 | |
298 | int childrenCount = children.count(); |
299 | for (int ii = 0; ii < children.count(); ++ii) { |
300 | if (qobject_cast<QQmlContext*>(children[ii])) |
301 | --childrenCount; |
302 | } |
303 | |
304 | message << childrenCount << recur; |
305 | |
306 | QList<QQmlObjectProperty> fakeProperties; |
307 | |
308 | for (int ii = 0; ii < children.count(); ++ii) { |
309 | QObject *child = children.at(ii); |
310 | if (qobject_cast<QQmlContext*>(child)) |
311 | continue; |
312 | if (recur) |
313 | buildObjectDump(message, child, recur, dumpProperties); |
314 | else |
315 | message << objectData(child); |
316 | } |
317 | |
318 | if (!dumpProperties) { |
319 | message << 0; |
320 | return; |
321 | } |
322 | |
323 | QList<int> propertyIndexes; |
324 | for (int ii = 0; ii < object->metaObject()->propertyCount(); ++ii) { |
325 | if (object->metaObject()->property(ii).isScriptable()) |
326 | propertyIndexes << ii; |
327 | } |
328 | |
329 | QQmlData *ddata = QQmlData::get(object); |
330 | if (ddata && ddata->signalHandlers) { |
331 | QQmlBoundSignal *signalHandler = ddata->signalHandlers; |
332 | |
333 | while (signalHandler) { |
334 | QQmlObjectProperty prop; |
335 | prop.type = QQmlObjectProperty::SignalProperty; |
336 | prop.hasNotifySignal = false; |
337 | QQmlBoundSignalExpression *expr = signalHandler->expression(); |
338 | if (expr) { |
339 | prop.value = expr->expression(); |
340 | QObject *scope = expr->scopeObject(); |
341 | if (scope) { |
342 | const QByteArray methodName = QMetaObjectPrivate::signal(scope->metaObject(), |
343 | signalHandler->signalIndex()).name(); |
344 | const QLatin1String methodNameStr(methodName); |
345 | if (methodNameStr.size() != 0) { |
346 | prop.name = QLatin1String("on" ) + QChar(methodNameStr.at(0)).toUpper() |
347 | + methodNameStr.mid(1); |
348 | } |
349 | } |
350 | } |
351 | fakeProperties << prop; |
352 | |
353 | signalHandler = nextSignal(signalHandler); |
354 | } |
355 | } |
356 | |
357 | message << propertyIndexes.size() + fakeProperties.count(); |
358 | |
359 | for (int ii = 0; ii < propertyIndexes.size(); ++ii) |
360 | message << propertyData(object, propertyIndexes.at(ii)); |
361 | |
362 | for (int ii = 0; ii < fakeProperties.count(); ++ii) |
363 | message << fakeProperties[ii]; |
364 | } |
365 | |
366 | void QQmlEngineDebugServiceImpl::prepareDeferredObjects(QObject *obj) |
367 | { |
368 | qmlExecuteDeferred(obj); |
369 | |
370 | QObjectList children = obj->children(); |
371 | for (int ii = 0; ii < children.count(); ++ii) { |
372 | QObject *child = children.at(ii); |
373 | prepareDeferredObjects(child); |
374 | } |
375 | |
376 | } |
377 | |
378 | void QQmlEngineDebugServiceImpl::storeObjectIds(QObject *co) |
379 | { |
380 | QQmlDebugService::idForObject(co); |
381 | QObjectList children = co->children(); |
382 | for (int ii = 0; ii < children.count(); ++ii) |
383 | storeObjectIds(children.at(ii)); |
384 | } |
385 | |
386 | void QQmlEngineDebugServiceImpl::buildObjectList(QDataStream &message, |
387 | QQmlContext *ctxt, |
388 | const QList<QPointer<QObject> > &instances) |
389 | { |
390 | if (!ctxt->isValid()) |
391 | return; |
392 | |
393 | QQmlContextData *p = QQmlContextData::get(ctxt); |
394 | |
395 | QString ctxtName = ctxt->objectName(); |
396 | int ctxtId = QQmlDebugService::idForObject(ctxt); |
397 | if (ctxt->contextObject()) |
398 | storeObjectIds(ctxt->contextObject()); |
399 | |
400 | message << ctxtName << ctxtId; |
401 | |
402 | int count = 0; |
403 | |
404 | QQmlContextData *child = p->childContexts; |
405 | while (child) { |
406 | ++count; |
407 | child = child->nextChild; |
408 | } |
409 | |
410 | message << count; |
411 | |
412 | child = p->childContexts; |
413 | while (child) { |
414 | buildObjectList(message, child->asQQmlContext(), instances); |
415 | child = child->nextChild; |
416 | } |
417 | |
418 | count = 0; |
419 | for (int ii = 0; ii < instances.count(); ++ii) { |
420 | QQmlData *data = QQmlData::get(instances.at(ii)); |
421 | if (data->context == p) |
422 | count ++; |
423 | } |
424 | message << count; |
425 | |
426 | for (int ii = 0; ii < instances.count(); ++ii) { |
427 | QQmlData *data = QQmlData::get(instances.at(ii)); |
428 | if (data->context == p) |
429 | message << objectData(instances.at(ii)); |
430 | } |
431 | } |
432 | |
433 | void QQmlEngineDebugServiceImpl::buildStatesList(bool cleanList, |
434 | const QList<QPointer<QObject> > &instances) |
435 | { |
436 | if (m_statesDelegate) |
437 | m_statesDelegate->buildStatesList(cleanList, instances); |
438 | } |
439 | |
440 | QQmlEngineDebugServiceImpl::QQmlObjectData |
441 | QQmlEngineDebugServiceImpl::objectData(QObject *object) |
442 | { |
443 | QQmlData *ddata = QQmlData::get(object); |
444 | QQmlObjectData rv; |
445 | if (ddata && ddata->outerContext) { |
446 | rv.url = ddata->outerContext->url(); |
447 | rv.lineNumber = ddata->lineNumber; |
448 | rv.columnNumber = ddata->columnNumber; |
449 | } else { |
450 | rv.lineNumber = -1; |
451 | rv.columnNumber = -1; |
452 | } |
453 | |
454 | QQmlContext *context = qmlContext(object); |
455 | if (context && context->isValid()) |
456 | rv.idString = QQmlContextData::get(context)->findObjectId(object); |
457 | |
458 | rv.objectName = object->objectName(); |
459 | rv.objectId = QQmlDebugService::idForObject(object); |
460 | rv.contextId = QQmlDebugService::idForObject(qmlContext(object)); |
461 | rv.parentId = QQmlDebugService::idForObject(object->parent()); |
462 | rv.objectType = QQmlMetaType::prettyTypeName(object); |
463 | return rv; |
464 | } |
465 | |
466 | void QQmlEngineDebugServiceImpl::messageReceived(const QByteArray &message) |
467 | { |
468 | emit scheduleMessage(message); |
469 | } |
470 | |
471 | /*! |
472 | Returns a list of objects matching the given filename, line and column. |
473 | */ |
474 | QList<QObject*> QQmlEngineDebugServiceImpl::objectForLocationInfo(const QString &filename, |
475 | int lineNumber, int columnNumber) |
476 | { |
477 | QList<QObject *> objects; |
478 | const QHash<int, QObject *> &hash = objectsForIds(); |
479 | for (QHash<int, QObject *>::ConstIterator i = hash.constBegin(); i != hash.constEnd(); ++i) { |
480 | QQmlData *ddata = QQmlData::get(i.value()); |
481 | if (ddata && ddata->outerContext && ddata->outerContext->isValid()) { |
482 | if (QFileInfo(ddata->outerContext->urlString()).fileName() == filename && |
483 | ddata->lineNumber == lineNumber && |
484 | ddata->columnNumber >= columnNumber) { |
485 | objects << i.value(); |
486 | } |
487 | } |
488 | } |
489 | return objects; |
490 | } |
491 | |
492 | void QQmlEngineDebugServiceImpl::processMessage(const QByteArray &message) |
493 | { |
494 | QQmlDebugPacket ds(message); |
495 | |
496 | QByteArray type; |
497 | int queryId; |
498 | ds >> type >> queryId; |
499 | |
500 | QQmlDebugPacket rs; |
501 | |
502 | if (type == "LIST_ENGINES" ) { |
503 | rs << QByteArray("LIST_ENGINES_R" ); |
504 | rs << queryId << m_engines.count(); |
505 | |
506 | for (int ii = 0; ii < m_engines.count(); ++ii) { |
507 | QJSEngine *engine = m_engines.at(ii); |
508 | |
509 | QString engineName = engine->objectName(); |
510 | int engineId = QQmlDebugService::idForObject(engine); |
511 | |
512 | rs << engineName << engineId; |
513 | } |
514 | |
515 | } else if (type == "LIST_OBJECTS" ) { |
516 | int engineId = -1; |
517 | ds >> engineId; |
518 | |
519 | QQmlEngine *engine = |
520 | qobject_cast<QQmlEngine *>(QQmlDebugService::objectForId(engineId)); |
521 | |
522 | rs << QByteArray("LIST_OBJECTS_R" ) << queryId; |
523 | |
524 | if (engine) { |
525 | QQmlContext *rootContext = engine->rootContext(); |
526 | // Clean deleted objects |
527 | QQmlContextPrivate *ctxtPriv = QQmlContextPrivate::get(rootContext); |
528 | for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) { |
529 | if (!ctxtPriv->instances.at(ii)) { |
530 | ctxtPriv->instances.removeAt(ii); |
531 | --ii; |
532 | } |
533 | } |
534 | buildObjectList(rs, rootContext, ctxtPriv->instances); |
535 | buildStatesList(true, ctxtPriv->instances); |
536 | } |
537 | |
538 | } else if (type == "FETCH_OBJECT" ) { |
539 | int objectId; |
540 | bool recurse; |
541 | bool dumpProperties = true; |
542 | |
543 | ds >> objectId >> recurse >> dumpProperties; |
544 | |
545 | QObject *object = QQmlDebugService::objectForId(objectId); |
546 | |
547 | rs << QByteArray("FETCH_OBJECT_R" ) << queryId; |
548 | |
549 | if (object) { |
550 | if (recurse) |
551 | prepareDeferredObjects(object); |
552 | buildObjectDump(rs, object, recurse, dumpProperties); |
553 | } |
554 | |
555 | } else if (type == "FETCH_OBJECTS_FOR_LOCATION" ) { |
556 | QString file; |
557 | int lineNumber; |
558 | int columnNumber; |
559 | bool recurse; |
560 | bool dumpProperties = true; |
561 | |
562 | ds >> file >> lineNumber >> columnNumber >> recurse >> dumpProperties; |
563 | |
564 | const QList<QObject*> objects = objectForLocationInfo(file, lineNumber, columnNumber); |
565 | |
566 | rs << QByteArray("FETCH_OBJECTS_FOR_LOCATION_R" ) << queryId |
567 | << objects.count(); |
568 | |
569 | for (QObject *object : objects) { |
570 | if (recurse) |
571 | prepareDeferredObjects(object); |
572 | buildObjectDump(rs, object, recurse, dumpProperties); |
573 | } |
574 | |
575 | } else if (type == "WATCH_OBJECT" ) { |
576 | int objectId; |
577 | |
578 | ds >> objectId; |
579 | bool ok = m_watch->addWatch(queryId, objectId); |
580 | |
581 | rs << QByteArray("WATCH_OBJECT_R" ) << queryId << ok; |
582 | |
583 | } else if (type == "WATCH_PROPERTY" ) { |
584 | int objectId; |
585 | QByteArray property; |
586 | |
587 | ds >> objectId >> property; |
588 | bool ok = m_watch->addWatch(queryId, objectId, property); |
589 | |
590 | rs << QByteArray("WATCH_PROPERTY_R" ) << queryId << ok; |
591 | |
592 | } else if (type == "WATCH_EXPR_OBJECT" ) { |
593 | int debugId; |
594 | QString expr; |
595 | |
596 | ds >> debugId >> expr; |
597 | bool ok = m_watch->addWatch(queryId, debugId, expr); |
598 | |
599 | rs << QByteArray("WATCH_EXPR_OBJECT_R" ) << queryId << ok; |
600 | |
601 | } else if (type == "NO_WATCH" ) { |
602 | bool ok = m_watch->removeWatch(queryId); |
603 | |
604 | rs << QByteArray("NO_WATCH_R" ) << queryId << ok; |
605 | |
606 | } else if (type == "EVAL_EXPRESSION" ) { |
607 | int objectId; |
608 | QString expr; |
609 | |
610 | ds >> objectId >> expr; |
611 | int engineId = -1; |
612 | if (!ds.atEnd()) |
613 | ds >> engineId; |
614 | |
615 | QObject *object = QQmlDebugService::objectForId(objectId); |
616 | QQmlContext *context = qmlContext(object); |
617 | if (!context || !context->isValid()) { |
618 | QQmlEngine *engine = qobject_cast<QQmlEngine *>( |
619 | QQmlDebugService::objectForId(engineId)); |
620 | if (engine && m_engines.contains(engine)) |
621 | context = engine->rootContext(); |
622 | } |
623 | QVariant result; |
624 | if (context && context->isValid()) { |
625 | QQmlExpression exprObj(context, object, expr); |
626 | bool undefined = false; |
627 | QVariant value = exprObj.evaluate(&undefined); |
628 | if (undefined) |
629 | result = QString(QStringLiteral("<undefined>" )); |
630 | else |
631 | result = valueContents(value); |
632 | } else { |
633 | result = QString(QStringLiteral("<unknown context>" )); |
634 | } |
635 | |
636 | rs << QByteArray("EVAL_EXPRESSION_R" ) << queryId << result; |
637 | |
638 | } else if (type == "SET_BINDING" ) { |
639 | int objectId; |
640 | QString propertyName; |
641 | QVariant expr; |
642 | bool isLiteralValue; |
643 | QString filename; |
644 | int line; |
645 | ds >> objectId >> propertyName >> expr >> isLiteralValue >> |
646 | filename >> line; |
647 | bool ok = setBinding(objectId, propertyName, expr, isLiteralValue, |
648 | filename, line); |
649 | |
650 | rs << QByteArray("SET_BINDING_R" ) << queryId << ok; |
651 | |
652 | } else if (type == "RESET_BINDING" ) { |
653 | int objectId; |
654 | QString propertyName; |
655 | ds >> objectId >> propertyName; |
656 | bool ok = resetBinding(objectId, propertyName); |
657 | |
658 | rs << QByteArray("RESET_BINDING_R" ) << queryId << ok; |
659 | |
660 | } else if (type == "SET_METHOD_BODY" ) { |
661 | int objectId; |
662 | QString methodName; |
663 | QString methodBody; |
664 | ds >> objectId >> methodName >> methodBody; |
665 | bool ok = setMethodBody(objectId, methodName, methodBody); |
666 | |
667 | rs << QByteArray("SET_METHOD_BODY_R" ) << queryId << ok; |
668 | |
669 | } |
670 | emit messageToClient(name(), rs.data()); |
671 | } |
672 | |
673 | bool QQmlEngineDebugServiceImpl::setBinding(int objectId, |
674 | const QString &propertyName, |
675 | const QVariant &expression, |
676 | bool isLiteralValue, |
677 | QString filename, |
678 | int line, |
679 | int column) |
680 | { |
681 | bool ok = true; |
682 | QObject *object = objectForId(objectId); |
683 | QQmlContext *context = qmlContext(object); |
684 | |
685 | if (object && context && context->isValid()) { |
686 | QQmlProperty property(object, propertyName, context); |
687 | if (property.isValid()) { |
688 | |
689 | bool inBaseState = true; |
690 | if (m_statesDelegate) { |
691 | m_statesDelegate->updateBinding(context, property, expression, isLiteralValue, |
692 | filename, line, column, &inBaseState); |
693 | } |
694 | |
695 | if (inBaseState) { |
696 | if (isLiteralValue) { |
697 | property.write(expression); |
698 | } else if (hasValidSignal(object, propertyName)) { |
699 | QQmlBoundSignalExpression *qmlExpression = new QQmlBoundSignalExpression(object, QQmlPropertyPrivate::get(property)->signalIndex(), |
700 | QQmlContextData::get(context), object, expression.toString(), |
701 | filename, line, column); |
702 | QQmlPropertyPrivate::takeSignalExpression(property, qmlExpression); |
703 | } else if (property.isProperty()) { |
704 | QQmlBinding *binding = QQmlBinding::create(&QQmlPropertyPrivate::get(property)->core, expression.toString(), object, QQmlContextData::get(context), filename, line); |
705 | binding->setTarget(property); |
706 | QQmlPropertyPrivate::setBinding(binding); |
707 | binding->update(); |
708 | } else { |
709 | ok = false; |
710 | qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object; |
711 | } |
712 | } |
713 | |
714 | } else { |
715 | // not a valid property |
716 | if (m_statesDelegate) |
717 | ok = m_statesDelegate->setBindingForInvalidProperty(object, propertyName, expression, isLiteralValue); |
718 | if (!ok) |
719 | qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object; |
720 | } |
721 | } |
722 | return ok; |
723 | } |
724 | |
725 | bool QQmlEngineDebugServiceImpl::resetBinding(int objectId, const QString &propertyName) |
726 | { |
727 | QObject *object = objectForId(objectId); |
728 | QQmlContext *context = qmlContext(object); |
729 | |
730 | if (object && context && context->isValid()) { |
731 | QStringRef parentPropertyRef(&propertyName); |
732 | const int idx = parentPropertyRef.indexOf(QLatin1Char('.')); |
733 | if (idx != -1) |
734 | parentPropertyRef = parentPropertyRef.left(idx); |
735 | |
736 | const QByteArray parentProperty = parentPropertyRef.toLatin1(); |
737 | if (object->property(parentProperty).isValid()) { |
738 | QQmlProperty property(object, propertyName); |
739 | QQmlPropertyPrivate::removeBinding(property); |
740 | if (property.isResettable()) { |
741 | // Note: this will reset the property in any case, without regard to states |
742 | // Right now almost no QQuickItem has reset methods for its properties (with the |
743 | // notable exception of QQuickAnchors), so this is not a big issue |
744 | // later on, setBinding does take states into account |
745 | property.reset(); |
746 | } else { |
747 | // overwrite with default value |
748 | QQmlType objType = QQmlMetaType::qmlType(object->metaObject()); |
749 | if (objType.isValid()) { |
750 | if (QObject *emptyObject = objType.create()) { |
751 | if (emptyObject->property(parentProperty).isValid()) { |
752 | QVariant defaultValue = QQmlProperty(emptyObject, propertyName).read(); |
753 | if (defaultValue.isValid()) { |
754 | setBinding(objectId, propertyName, defaultValue, true); |
755 | } |
756 | } |
757 | delete emptyObject; |
758 | } |
759 | } |
760 | } |
761 | return true; |
762 | } |
763 | |
764 | if (hasValidSignal(object, propertyName)) { |
765 | QQmlProperty property(object, propertyName, context); |
766 | QQmlPropertyPrivate::setSignalExpression(property, nullptr); |
767 | return true; |
768 | } |
769 | |
770 | if (m_statesDelegate) { |
771 | m_statesDelegate->resetBindingForInvalidProperty(object, propertyName); |
772 | return true; |
773 | } |
774 | |
775 | return false; |
776 | } |
777 | // object or context null. |
778 | return false; |
779 | } |
780 | |
781 | bool QQmlEngineDebugServiceImpl::setMethodBody(int objectId, const QString &method, const QString &body) |
782 | { |
783 | QObject *object = objectForId(objectId); |
784 | QQmlContext *context = qmlContext(object); |
785 | if (!object || !context || !context->isValid()) |
786 | return false; |
787 | QQmlContextData *contextData = QQmlContextData::get(context); |
788 | |
789 | QQmlPropertyData dummy; |
790 | QQmlPropertyData *prop = |
791 | QQmlPropertyCache::property(context->engine(), object, method, contextData, dummy); |
792 | |
793 | if (!prop || !prop->isVMEFunction()) |
794 | return false; |
795 | |
796 | QMetaMethod metaMethod = object->metaObject()->method(prop->coreIndex()); |
797 | QList<QByteArray> paramNames = metaMethod.parameterNames(); |
798 | |
799 | QString paramStr; |
800 | for (int ii = 0; ii < paramNames.count(); ++ii) { |
801 | if (ii != 0) paramStr.append(QLatin1Char(',')); |
802 | paramStr.append(QString::fromUtf8(paramNames.at(ii))); |
803 | } |
804 | |
805 | const QString jsfunction = QLatin1String("(function " ) + method + QLatin1Char('(') + paramStr + |
806 | QLatin1String(") {" ) + body + QLatin1String("\n})" ); |
807 | |
808 | QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(object); |
809 | Q_ASSERT(vmeMetaObject); // the fact we found the property above should guarentee this |
810 | |
811 | QV4::ExecutionEngine *v4 = qmlEngine(object)->handle(); |
812 | QV4::Scope scope(v4); |
813 | |
814 | int lineNumber = 0; |
815 | QV4::ScopedFunctionObject oldMethod(scope, vmeMetaObject->vmeMethod(prop->coreIndex())); |
816 | if (oldMethod && oldMethod->d()->function) { |
817 | lineNumber = oldMethod->d()->function->compiledFunction->location.line; |
818 | } |
819 | QV4::ScopedValue v(scope, QQmlJavaScriptExpression::evalFunction(contextData, object, jsfunction, contextData->urlString(), lineNumber)); |
820 | vmeMetaObject->setVmeMethod(prop->coreIndex(), v); |
821 | return true; |
822 | } |
823 | |
824 | void QQmlEngineDebugServiceImpl::propertyChanged(int id, int 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(), 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(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(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(engine)) |
853 | return; |
854 | |
855 | int engineId = QQmlDebugService::idForObject(engine); |
856 | int objectId = QQmlDebugService::idForObject(object); |
857 | int parentId = QQmlDebugService::idForObject(object->parent()); |
858 | |
859 | QQmlDebugPacket rs; |
860 | |
861 | //unique queryId -1 |
862 | rs << QByteArray("OBJECT_CREATED" ) << -1 << engineId << objectId << parentId; |
863 | emit messageToClient(name(), 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 | |