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(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 NullDevice nullDevice;
98 QDataStream fakeStream(&nullDevice);
99 return QMetaType::save(fakeStream, static_cast<int>(value.type()), value.constData());
100}
101
102QQmlEngineDebugServiceImpl::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
113QQmlEngineDebugServiceImpl::~QQmlEngineDebugServiceImpl()
114{
115 delete m_statesDelegate;
116}
117
118QDataStream &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
127QDataStream &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
136QDataStream &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
145QDataStream &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
155static 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
162static 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
178QQmlEngineDebugServiceImpl::QQmlObjectProperty
179QQmlEngineDebugServiceImpl::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
213QVariant 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
291void 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
366void 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
378void 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
386void 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
433void QQmlEngineDebugServiceImpl::buildStatesList(bool cleanList,
434 const QList<QPointer<QObject> > &instances)
435{
436 if (m_statesDelegate)
437 m_statesDelegate->buildStatesList(cleanList, instances);
438}
439
440QQmlEngineDebugServiceImpl::QQmlObjectData
441QQmlEngineDebugServiceImpl::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
466void 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*/
474QList<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
492void 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
673bool 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
725bool 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
781bool 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
824void 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
831void 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
840void 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
849void 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
866void QQmlEngineDebugServiceImpl::setStatesDelegate(QQmlDebugStatesDelegate *delegate)
867{
868 m_statesDelegate = delegate;
869}
870
871QT_END_NAMESPACE
872
873#include "moc_qqmlenginedebugservice.cpp"
874