aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnton Kreuzkamp <anton.kreuzkamp@kdab.com>2017-04-26 16:31:48 +0200
committerAnton Kreuzkamp <anton.kreuzkamp@kdab.com>2017-08-13 04:07:30 +0000
commit17513266b81e88b84fda250734f07c94a198fabc (patch)
tree2bb498ba41df15971035fb040bf4c07e8369fbc3
parent041c8d28eaa0e516a0c6502fa801781aa8ebe5a6 (diff)
Add API to learn about QQmlBinding's dependencies
Adds a method `dependencies()` to QQmlBinding, that returns a QVector<QQmlProperty> of all properties the binding depends on. The API is meant to be used in debugging tools (e.g. in GammaRay). Also adds a public method subBindings() to QQmlValueTypeProxyBinding in order to be able to access their dependencies. Change-Id: Ib833703ec9e632661626c4532b8d73997f38e62b Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
-rw-r--r--src/qml/qml/qqmlbinding.cpp32
-rw-r--r--src/qml/qml/qqmlbinding_p.h9
-rw-r--r--src/qml/qml/qqmljavascriptexpression_p.h14
-rw-r--r--src/qml/qml/qqmlnotifier_p.h5
-rw-r--r--src/qml/qml/qqmlvaluetypeproxybinding.cpp5
-rw-r--r--src/qml/qml/qqmlvaluetypeproxybinding_p.h1
-rw-r--r--tests/auto/qml/bindingdependencyapi/bindingdependencyapi.pro11
-rw-r--r--tests/auto/qml/bindingdependencyapi/tst_bindingdependencyapi.cpp360
-rw-r--r--tests/auto/qml/qml.pro3
9 files changed, 430 insertions, 10 deletions
diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp
index 84f30af066..566fbb86ac 100644
--- a/src/qml/qml/qqmlbinding.cpp
+++ b/src/qml/qml/qqmlbinding.cpp
@@ -54,6 +54,7 @@
#include <QVariant>
#include <QtCore/qdebug.h>
+#include <QVector>
QT_BEGIN_NAMESPACE
@@ -579,6 +580,37 @@ void QQmlBinding::getPropertyData(QQmlPropertyData **propertyData, QQmlPropertyD
}
}
+QVector<QQmlProperty> QQmlBinding::dependencies() const
+{
+ QVector<QQmlProperty> dependencies;
+ if (!m_target.data())
+ return dependencies;
+
+ for (const auto &guardList : { permanentGuards, activeGuards }) {
+ for (QQmlJavaScriptExpressionGuard *guard = guardList.first(); guard; guard = guardList.next(guard)) {
+ if (guard->signalIndex() == -1) // guard's sender is a QQmlNotifier, not a QObject*.
+ continue;
+
+ QObject *senderObject = guard->senderAsObject();
+ if (!senderObject)
+ continue;
+
+ const QMetaObject *senderMeta = senderObject->metaObject();
+ if (!senderMeta)
+ continue;
+
+ for (int i = 0; i < senderMeta->propertyCount(); i++) {
+ QMetaProperty property = senderMeta->property(i);
+ if (property.notifySignalIndex() == QMetaObjectPrivate::signal(senderMeta, guard->signalIndex()).methodIndex()) {
+ dependencies.push_back(QQmlProperty(senderObject, QString::fromUtf8(senderObject->metaObject()->property(i).name())));
+ }
+ }
+ }
+ }
+
+ return dependencies;
+}
+
class QObjectPointerBinding: public QQmlNonbindingBinding
{
QQmlMetaObject targetMetaObject;
diff --git a/src/qml/qml/qqmlbinding_p.h b/src/qml/qml/qqmlbinding_p.h
index 1c894148e4..38d59a8919 100644
--- a/src/qml/qml/qqmlbinding_p.h
+++ b/src/qml/qml/qqmlbinding_p.h
@@ -102,6 +102,15 @@ public:
QString expressionIdentifier() const override;
void expressionChanged() override;
+ /**
+ * This method returns a snapshot of the currently tracked dependencies of
+ * this binding. The dependencies can change upon reevaluation. This method is
+ * used in GammaRay to visualize binding hierarchies.
+ *
+ * Call this method from the UI thread.
+ */
+ QVector<QQmlProperty> dependencies() const;
+
protected:
virtual void doUpdate(const DeleteWatcher &watcher,
QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) = 0;
diff --git a/src/qml/qml/qqmljavascriptexpression_p.h b/src/qml/qml/qqmljavascriptexpression_p.h
index 8b4b64f4d2..e9a9f4feee 100644
--- a/src/qml/qml/qqmljavascriptexpression_p.h
+++ b/src/qml/qml/qqmljavascriptexpression_p.h
@@ -162,6 +162,13 @@ protected:
void setupFunction(QV4::ExecutionContext *qmlContext, QV4::Function *f);
void setCompilationUnit(QV4::CompiledData::CompilationUnit *compilationUnit);
+ // We store some flag bits in the following flag pointers.
+ // activeGuards:flag1 - notifyOnValueChanged
+ // activeGuards:flag2 - useSharedContext
+ QBiPointer<QObject, DeleteWatcher> m_scopeObject;
+ QForwardFieldList<QQmlJavaScriptExpressionGuard, &QQmlJavaScriptExpressionGuard::next> activeGuards;
+ QForwardFieldList<QQmlJavaScriptExpressionGuard, &QQmlJavaScriptExpressionGuard::next> permanentGuards;
+
private:
friend class QQmlContextData;
friend class QQmlPropertyCapture;
@@ -170,13 +177,6 @@ private:
QQmlDelayedError *m_error;
- // We store some flag bits in the following flag pointers.
- // activeGuards:flag1 - notifyOnValueChanged
- // activeGuards:flag2 - useSharedContext
- QBiPointer<QObject, DeleteWatcher> m_scopeObject;
- QForwardFieldList<QQmlJavaScriptExpressionGuard, &QQmlJavaScriptExpressionGuard::next> activeGuards;
- QForwardFieldList<QQmlJavaScriptExpressionGuard, &QQmlJavaScriptExpressionGuard::next> permanentGuards;
-
QQmlContextData *m_context;
QQmlJavaScriptExpression **m_prevExpression;
QQmlJavaScriptExpression *m_nextExpression;
diff --git a/src/qml/qml/qqmlnotifier_p.h b/src/qml/qml/qqmlnotifier_p.h
index 6e91369793..a99b13f155 100644
--- a/src/qml/qml/qqmlnotifier_p.h
+++ b/src/qml/qml/qqmlnotifier_p.h
@@ -109,6 +109,9 @@ public:
inline int signalIndex() const { return sourceSignal; }
+ inline QObject *senderAsObject() const;
+ inline QQmlNotifier *senderAsNotifier() const;
+
private:
friend class QQmlData;
friend class QQmlNotifier;
@@ -117,8 +120,6 @@ private:
// endpoint is connected to. While the endpoint is notifying, the
// senderPtr points to another qintptr that contains this value.
qintptr senderPtr;
- inline QObject *senderAsObject() const;
- inline QQmlNotifier *senderAsNotifier() const;
Callback callback:4;
int needsConnectNotify:1;
diff --git a/src/qml/qml/qqmlvaluetypeproxybinding.cpp b/src/qml/qml/qqmlvaluetypeproxybinding.cpp
index 4e2e7b06c7..7a3e4b2df4 100644
--- a/src/qml/qml/qqmlvaluetypeproxybinding.cpp
+++ b/src/qml/qml/qqmlvaluetypeproxybinding.cpp
@@ -72,6 +72,11 @@ bool QQmlValueTypeProxyBinding::isValueTypeProxy() const
return true;
}
+QQmlAbstractBinding *QQmlValueTypeProxyBinding::subBindings() const
+{
+ return m_bindings.data();
+}
+
QQmlAbstractBinding *QQmlValueTypeProxyBinding::binding(QQmlPropertyIndex propertyIndex) const
{
QQmlAbstractBinding *binding = m_bindings.data();
diff --git a/src/qml/qml/qqmlvaluetypeproxybinding_p.h b/src/qml/qml/qqmlvaluetypeproxybinding_p.h
index 92b5470f39..ba0d305bd9 100644
--- a/src/qml/qml/qqmlvaluetypeproxybinding_p.h
+++ b/src/qml/qml/qqmlvaluetypeproxybinding_p.h
@@ -60,6 +60,7 @@ class QQmlValueTypeProxyBinding : public QQmlAbstractBinding
public:
QQmlValueTypeProxyBinding(QObject *o, QQmlPropertyIndex coreIndex);
+ QQmlAbstractBinding *subBindings() const;
QQmlAbstractBinding *binding(QQmlPropertyIndex targetPropertyIndex) const;
void removeBindings(quint32 mask);
diff --git a/tests/auto/qml/bindingdependencyapi/bindingdependencyapi.pro b/tests/auto/qml/bindingdependencyapi/bindingdependencyapi.pro
new file mode 100644
index 0000000000..430d87ab5b
--- /dev/null
+++ b/tests/auto/qml/bindingdependencyapi/bindingdependencyapi.pro
@@ -0,0 +1,11 @@
+CONFIG += testcase
+TARGET = tst_bindingdependencyapi
+macos:CONFIG -= app_bundle
+
+SOURCES += tst_bindingdependencyapi.cpp
+
+include (../../shared/util.pri)
+
+TESTDATA = data/*
+
+QT += core-private gui-private qml-private quick-private testlib
diff --git a/tests/auto/qml/bindingdependencyapi/tst_bindingdependencyapi.cpp b/tests/auto/qml/bindingdependencyapi/tst_bindingdependencyapi.cpp
new file mode 100644
index 0000000000..6f24f85f6d
--- /dev/null
+++ b/tests/auto/qml/bindingdependencyapi/tst_bindingdependencyapi.cpp
@@ -0,0 +1,360 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <qtest.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include <private/qqmldata_p.h>
+#include <private/qqmlbinding_p.h>
+#include <QtQuick/private/qquicktext_p.h>
+#include <QtQuick/private/qquickrectangle_p.h>
+#include "../../shared/util.h"
+#include <qqmlcontext.h>
+
+class tst_bindingdependencyapi : public QObject
+{
+ Q_OBJECT
+public:
+ tst_bindingdependencyapi();
+
+private slots:
+ void testSingleDep_data();
+ void testSingleDep();
+ void testManyDeps_data();
+ void testManyDeps();
+ void testConditionalDependencies_data();
+ void testConditionalDependencies();
+ void testBindingLoop();
+
+private:
+ bool findProperties(const QVector<QQmlProperty> &properties, QObject *obj, const QString &propertyName, const QVariant &value);
+};
+
+tst_bindingdependencyapi::tst_bindingdependencyapi()
+{
+}
+
+
+void tst_bindingdependencyapi::testSingleDep_data()
+{
+ QTest::addColumn<QByteArray>("code");
+ QTest::addColumn<QString>("referencedObjectName");
+
+ QTest::addRow("context property")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "objectName: \"rect\"\n"
+ "property string labelText: \"Hello world!\"\n"
+ "Text { text: labelText }\n"
+ "}") << "rect";
+
+ QTest::addRow("scope property")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "property string labelText: \"I am wrong!\"\n"
+ "Text {\n"
+ "objectName: \"text\"\n"
+ "property string labelText: \"Hello world!\"\n"
+ "text: labelText\n"
+ "}\n"
+ "}") << "text";
+
+ QTest::addRow("id object property")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "id: rect\n"
+ "objectName: \"rect\"\n"
+ "property string labelText: \"Hello world!\"\n"
+ "Text { text: rect.labelText }\n"
+ "}") << "rect";
+
+ QTest::addRow("dynamic context property")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "objectName: \"rect\"\n"
+ "property string labelText: \"Hello world!\"\n"
+ "Text { Component.onCompleted: text = Qt.binding(function() { return labelText; }); }\n"
+ "}") << "rect";
+
+ QTest::addRow("dynamic scope property")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "property string labelText: \"I am wrong!\"\n"
+ "Text {\n"
+ "objectName: \"text\"\n"
+ "property string labelText: \"Hello world!\"\n"
+ "Component.onCompleted: text = Qt.binding(function() { return labelText; });\n"
+ "}\n"
+ "}") << "text";
+
+ QTest::addRow("dynamic id object property")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "id: rect\n"
+ "objectName: \"rect\"\n"
+ "property string labelText: \"Hello world!\"\n"
+ "Text { Component.onCompleted: text = Qt.binding(function() { return rect.labelText; }); }\n"
+ "}") << "rect";
+}
+
+void tst_bindingdependencyapi::testSingleDep()
+{
+ QFETCH(QByteArray, code);
+ QFETCH(QString, referencedObjectName);
+
+ QQmlEngine engine;
+ QQmlComponent c(&engine);
+ c.setData(code, QUrl());
+ QObject *rect = c.create();
+ QTest::qWait(10);
+ QVERIFY(rect != 0);
+ QObject *text = rect->findChildren<QQuickText *>().front();
+
+ QObject *referencedObject = rect->objectName() == referencedObjectName ? rect : rect->findChild<QObject *>(referencedObjectName);
+
+ auto data = QQmlData::get(text);
+ QVERIFY(data);
+ auto b = data->bindings;
+ QVERIFY(b);
+ auto binding = dynamic_cast<QQmlBinding*>(b);
+ QVERIFY(binding);
+ auto dependencies = binding->dependencies();
+ QCOMPARE(dependencies.size(), 1);
+ auto dependency = dependencies.front();
+ QVERIFY(dependency.isValid());
+ QCOMPARE(quintptr(dependency.object()), quintptr(referencedObject));
+ QCOMPARE(dependency.property().name(), "labelText");
+ QCOMPARE(dependency.read().toString(), QStringLiteral("Hello world!"));
+ QCOMPARE(dependency, QQmlProperty(referencedObject, "labelText"));
+
+ delete rect;
+}
+
+bool tst_bindingdependencyapi::findProperties(const QVector<QQmlProperty> &properties, QObject *obj, const QString &propertyName, const QVariant &value)
+{
+ auto dep = std::find_if(properties.cbegin(), properties.cend(), [&](const QQmlProperty &dep) {
+ return dep.object() == obj
+ && dep.property().name() == propertyName
+ && dep.read() == value;
+ });
+ if (dep == properties.cend()) {
+ qWarning() << "Searched for property with:" << "{ object:" << obj << ", propertyName:" << propertyName << ", value:" << value << "}" << "but only found:";
+ for (auto dep : properties) {
+ qWarning() << "{ object:" << dep.object() << ", propertyName:" << dep.property().name() << ", value:" << dep.read() << "}";
+ }
+ return false;
+ }
+ return true;
+}
+
+void tst_bindingdependencyapi::testManyDeps_data()
+{
+ QTest::addColumn<QByteArray>("code");
+
+ QTest::addRow("permanent binding")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "id: rect\n"
+ "objectName: 'rect'\n"
+ "property string name: 'world'\n"
+ "Text {\n"
+ "text: config.helloWorldTemplate.arg(greeting).arg(rect.name) \n"
+ "property string greeting: 'Hello'\n"
+ "}\n"
+ "QtObject { id: config; objectName: 'config'; property string helloWorldTemplate: '%1 %2!' }\n"
+ "}");
+
+ QTest::addRow("dynamic binding")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "id: rect\n"
+ "objectName: 'rect'\n"
+ "property string name: 'world'\n"
+ "Text {\n"
+ "Component.onCompleted: text = Qt.binding(function() { return config.helloWorldTemplate.arg(greeting).arg(rect.name); }); \n"
+ "property string greeting: 'Hello'\n"
+ "}\n"
+ "QtObject { id: config; objectName: 'config'; property string helloWorldTemplate: '%1 %2!' }\n"
+ "}");
+}
+
+void tst_bindingdependencyapi::testManyDeps()
+{
+ QFETCH(QByteArray, code);
+ QQmlEngine engine;
+ QQmlComponent c(&engine);
+ c.setData(code, QUrl());
+ QObject *rect = c.create();
+ if (c.isError()) {
+ qWarning() << c.errorString();
+ }
+ QTest::qWait(100);
+ QVERIFY(rect != 0);
+ QObject *text = rect->findChildren<QQuickText *>().front();
+ QObject *configObj = rect->findChild<QObject *>("config");
+
+ auto data = QQmlData::get(text);
+ QVERIFY(data);
+ auto b = data->bindings;
+ QVERIFY(b);
+ auto binding = dynamic_cast<QQmlBinding*>(b);
+ QVERIFY(binding);
+ auto dependencies = binding->dependencies();
+ QCOMPARE(dependencies.size(), 3);
+
+ QVERIFY(findProperties(dependencies, rect, "name", "world"));
+ QVERIFY(findProperties(dependencies, text, "greeting", "Hello"));
+ QVERIFY(findProperties(dependencies, configObj, "helloWorldTemplate", "%1 %2!"));
+
+ delete rect;
+}
+
+void tst_bindingdependencyapi::testConditionalDependencies_data()
+{
+ QTest::addColumn<QByteArray>("code");
+ QTest::addColumn<QString>("referencedObjectName");
+
+ QTest::addRow("id object property")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "id: rect\n"
+ "objectName: \"rect\"\n"
+ "property bool haveDep: false\n"
+ "property string labelText: \"Hello world!\"\n"
+ "Text { text: rect.haveDep ? rect.labelText : '' }\n"
+ "}") << "rect";
+
+ QTest::addRow("dynamic context property")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "objectName: \"rect\"\n"
+ "property bool haveDep: false\n"
+ "property string labelText: \"Hello world!\"\n"
+ "Text { Component.onCompleted: text = Qt.binding(function() { return haveDep ? labelText : ''; }); }\n"
+ "}") << "rect";
+
+ QTest::addRow("dynamic scope property")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "property string labelText: \"I am wrong!\"\n"
+ "Text {\n"
+ "objectName: \"text\"\n"
+ "property bool haveDep: false\n"
+ "property string labelText: \"Hello world!\"\n"
+ "Component.onCompleted: text = Qt.binding(function() { return haveDep ? labelText : ''; });\n"
+ "}\n"
+ "}") << "text";
+
+ QTest::addRow("dynamic id object property")
+ << QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "id: rect\n"
+ "objectName: \"rect\"\n"
+ "property bool haveDep: false\n"
+ "property string labelText: \"Hello world!\"\n"
+ "Text { Component.onCompleted: text = Qt.binding(function() { return rect.haveDep ? rect.labelText : ''; }); }\n"
+ "}") << "rect";
+}
+
+void tst_bindingdependencyapi::testConditionalDependencies()
+{
+ QFETCH(QByteArray, code);
+ QFETCH(QString, referencedObjectName);
+
+ QQmlEngine engine;
+ QQmlComponent c(&engine);
+ c.setData(code, QUrl());
+ QObject *rect = c.create();
+ QTest::qWait(10);
+ QVERIFY(rect != 0);
+ QObject *text = rect->findChildren<QQuickText *>().front();
+
+ QObject *referencedObject = rect->objectName() == referencedObjectName ? rect : rect->findChild<QObject *>(referencedObjectName);
+
+ auto data = QQmlData::get(text);
+ QVERIFY(data);
+ auto b = data->bindings;
+ QVERIFY(b);
+ auto binding = dynamic_cast<QQmlBinding*>(b);
+ QVERIFY(binding);
+ auto dependencies = binding->dependencies();
+ QCOMPARE(dependencies.size(), 1);
+ QVERIFY(findProperties(dependencies, referencedObject, "haveDep", false));
+
+ referencedObject->setProperty("haveDep", true);
+ dependencies = binding->dependencies();
+ QCOMPARE(dependencies.size(), 2);
+ QVERIFY(findProperties(dependencies, referencedObject, "haveDep", true));
+ QVERIFY(findProperties(dependencies, referencedObject, "labelText", "Hello world!"));
+
+ referencedObject->setProperty("haveDep", false);
+ dependencies = binding->dependencies();
+ QCOMPARE(dependencies.size(), 1);
+ QVERIFY(findProperties(dependencies, referencedObject, "haveDep", false));
+
+ delete rect;
+}
+
+void tst_bindingdependencyapi::testBindingLoop()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine);
+ c.setData(QByteArray("import QtQuick 2.0\n"
+ "Rectangle {\n"
+ "property string labelText: label.text\n"
+ "Text {\n"
+ "id: label\n"
+ "text: labelText\n"
+ "}\n"
+ "}"), QUrl());
+ QObject *rect = c.create();
+ if (c.isError()) {
+ qWarning() << c.errorString();
+ }
+ QTest::qWait(100);
+ QVERIFY(rect != 0);
+ QObject *text = rect->findChildren<QQuickText *>().front();
+
+ auto data = QQmlData::get(text);
+ QVERIFY(data);
+ auto b = data->bindings;
+ QVERIFY(b);
+ auto binding = dynamic_cast<QQmlBinding*>(b);
+ QVERIFY(binding);
+ auto dependencies = binding->dependencies();
+ QCOMPARE(dependencies.size(), 1);
+ auto dependency = dependencies.front();
+ QVERIFY(dependency.isValid());
+ QCOMPARE(quintptr(dependency.object()), quintptr(rect));
+ QCOMPARE(dependency.property().name(), "labelText");
+
+ delete rect;
+}
+
+QTEST_MAIN(tst_bindingdependencyapi)
+
+#include "tst_bindingdependencyapi.moc"
diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro
index 12a8bd3829..69af3cd13b 100644
--- a/tests/auto/qml/qml.pro
+++ b/tests/auto/qml/qml.pro
@@ -72,7 +72,8 @@ PRIVATETESTS += \
qqmlimport \
qqmlobjectmodel \
qv4mm \
- ecmascripttests
+ ecmascripttests \
+ bindingdependencyapi
}
qtHaveModule(widgets) {