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
67QT_BEGIN_NAMESPACE
68
69using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
70
71class NullDevice : public QIODevice
72{
73public:
74 NullDevice() { open(mode: QIODevice::ReadWrite); }
75
76protected:
77 qint64 readData(char *data, qint64 maxlen) final;
78 qint64 writeData(const char *data, qint64 len) final;
79};
80
81qint64 NullDevice::readData(char *data, qint64 maxlen)
82{
83 Q_UNUSED(data);
84 return maxlen;
85}
86
87qint64 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)
95static 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
105QQmlEngineDebugServiceImpl::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
116QQmlEngineDebugServiceImpl::~QQmlEngineDebugServiceImpl()
117{
118 delete m_statesDelegate;
119}
120
121QDataStream &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
130QDataStream &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
139QDataStream &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
148QDataStream &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
158static 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
165static 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
181QQmlEngineDebugServiceImpl::QQmlObjectProperty
182QQmlEngineDebugServiceImpl::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
212QVariant 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
290void 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
365void 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
377void 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
385void 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
432void QQmlEngineDebugServiceImpl::buildStatesList(bool cleanList,
433 const QList<QPointer<QObject> > &instances)
434{
435 if (m_statesDelegate)
436 m_statesDelegate->buildStatesList(cleanList, instances);
437}
438
439QQmlEngineDebugServiceImpl::QQmlObjectData
440QQmlEngineDebugServiceImpl::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
465void 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*/
473QList<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
491void 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
672bool 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
724bool 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
780bool 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
823void 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
831void 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
840void 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
849void 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
866void QQmlEngineDebugServiceImpl::setStatesDelegate(QQmlDebugStatesDelegate *delegate)
867{
868 m_statesDelegate = delegate;
869}
870
871QT_END_NAMESPACE
872
873#include "moc_qqmlenginedebugservice.cpp"
874

source code of qtdeclarative/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp