aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/CMakeLists.txt1
-rw-r--r--src/qml/qml/ftw/qflagpointer_p.h20
-rw-r--r--src/qml/qml/qqmlanybinding_p.h419
-rw-r--r--src/qml/qml/qqmlpropertybinding.cpp14
-rw-r--r--src/qml/qml/qqmlpropertybinding_p.h7
-rw-r--r--tests/auto/qml/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qqmlanybinding/CMakeLists.txt45
-rw-r--r--tests/auto/qml/qqmlanybinding/data/newstylebinding.qml14
-rw-r--r--tests/auto/qml/qqmlanybinding/data/oldstylebinding.qml13
-rw-r--r--tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp117
-rw-r--r--tests/auto/qml/qqmlanybinding/withbindable.h48
11 files changed, 698 insertions, 1 deletions
diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt
index a96ddc2ab7..0d87869d00 100644
--- a/src/qml/CMakeLists.txt
+++ b/src/qml/CMakeLists.txt
@@ -269,6 +269,7 @@ qt_internal_add_module(Qml
qml/qqmlprivate.h
qml/qqmlproperty.cpp qml/qqmlproperty.h qml/qqmlproperty_p.h
qml/qqmlpropertybinding.cpp qml/qqmlpropertybinding_p.h
+ qml/qqmlanybinding_p.h
qml/qqmlpropertycache.cpp qml/qqmlpropertycache_p.h
qml/qqmlpropertycachecreator.cpp qml/qqmlpropertycachecreator_p.h
qml/qqmlpropertycachemethodarguments_p.h
diff --git a/src/qml/qml/ftw/qflagpointer_p.h b/src/qml/qml/ftw/qflagpointer_p.h
index 5cdf973352..c8824f3866 100644
--- a/src/qml/qml/ftw/qflagpointer_p.h
+++ b/src/qml/qml/ftw/qflagpointer_p.h
@@ -87,6 +87,26 @@ public:
inline QBiPointer<T, T2> &operator=(T *);
inline QBiPointer<T, T2> &operator=(T2 *);
+ friend inline bool operator==(QBiPointer<T, T2> ptr1, QBiPointer<T, T2> ptr2)
+ {
+ if (ptr1.isNull() && ptr2.isNull())
+ return true;
+ if (ptr1.isT1() && ptr2.isT1())
+ return ptr1.asT1() == ptr2.asT1();
+ if (ptr1.isT2() && ptr2.isT2())
+ return ptr1.asT2() == ptr2.asT2();
+ return false;
+ }
+ friend inline bool operator!=(QBiPointer<T, T2> ptr1, QBiPointer<T, T2> ptr2)
+ {
+ return !(ptr1 == ptr2);
+ }
+
+ friend inline void swap(QBiPointer<T, T2> ptr1, QBiPointer<T, T2> ptr2)
+ {
+ qSwap(ptr1.ptr_value, ptr2.ptr_value);
+ }
+
inline T *asT1() const;
inline T2 *asT2() const;
diff --git a/src/qml/qml/qqmlanybinding_p.h b/src/qml/qml/qqmlanybinding_p.h
new file mode 100644
index 0000000000..38c1023c79
--- /dev/null
+++ b/src/qml/qml/qqmlanybinding_p.h
@@ -0,0 +1,419 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLANYBINDINGPTR_P_H
+#define QQMLANYBINDINGPTR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qqmlproperty.h>
+#include <private/qqmlpropertybinding_p.h>
+#include <private/qqmlbinding_p.h>
+
+// Fully inline so that subsequent prop.isBindable check might get ellided.
+
+/*!
+ \internal
+ \brief QQmlAnyBinding is an abstraction over the various bindings in QML
+
+ QQmlAnyBinding can store both classical bindings (derived from QQmlAbstractBinding)
+ as well as new-style bindings (derived from QPropertyBindingPrivate). For both, it keeps
+ a strong reference to them, and knows how to delete them in case the reference count
+ becomes zero. In that sense it can be thought of as a union of QUntypedPropertyBinding
+ and QQmlAbstractBinding::Ptr.
+
+ It also offers methods to create bindings (from QV4::Function, from translation bindings
+ and from code strings). Moreover, it allows the retrieval, the removal and the
+ installation of bindings on a QQmlProperty.
+
+ Note that the class intentionally does not allow construction from QUntypedProperty and
+ QQmlAbstractBinding::Ptr. This is meant to catch code which doesn't handle bindable properties
+ yet when porting existing code.
+ */
+class QQmlAnyBinding {
+public:
+
+ QQmlAnyBinding() = default;
+ QQmlAnyBinding(std::nullptr_t) : d(static_cast<QQmlAbstractBinding *>(nullptr)) {}
+
+
+ /*!
+ \internal
+ Returns the binding of the property \a prop as a QQmlAnyBinding.
+ The binding continues to be active and set on the property.
+ If there was no binding set, the returned QQmlAnyBinding is null.
+ */
+ static QQmlAnyBinding ofProperty(const QQmlProperty &prop) {
+ QQmlAnyBinding binding;
+ if (prop.isBindable()) {
+ QUntypedBindable bindable = prop.property().bindable(prop.object());
+ binding = bindable.binding();
+ } else {
+ binding = QQmlPropertyPrivate::binding(prop);
+ }
+ return binding;
+ }
+
+ /*!
+ Removes the binding from the property \a prop, and returns it as a
+ QQmlAnyBinding if there was any. Otherwise returns a null
+ QQmlAnyBinding.
+ */
+ static QQmlAnyBinding takeFrom(const QQmlProperty &prop)
+ {
+ QQmlAnyBinding binding;
+ if (prop.isBindable()) {
+ QUntypedBindable bindable = prop.property().bindable(prop.object());
+ binding = bindable.takeBinding();
+ } else {
+ auto qmlBinding = QQmlPropertyPrivate::binding(prop);
+ if (qmlBinding) {
+ qmlBinding->setEnabled(false, QQmlPropertyData::DontRemoveBinding | QQmlPropertyData::BypassInterceptor);
+ qmlBinding->removeFromObject();
+ binding = qmlBinding;
+ }
+ }
+ return binding;
+ }
+
+ /*!
+ \internal
+ Creates a binding for property \a prop from \a function.
+ \a obj is the scope object which shall be used for the function and \a scope its QML scope.
+ The binding is not installed on the property (but if a QQmlBinding is created, it has its
+ target set to \a prop).
+ */
+ static QQmlAnyBinding createFromFunction(const QQmlProperty &prop, QV4::Function *function,
+ QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
+ QV4::ExecutionContext *scope)
+ {
+ QQmlAnyBinding binding;
+ auto propPriv = QQmlPropertyPrivate::get(prop);
+ if (prop.isBindable()) {
+ auto index = QQmlPropertyIndex(propPriv->core.coreIndex(), -1);
+ binding = QQmlPropertyBinding::create(&propPriv->core,
+ function, obj, ctxt,
+ scope, prop.object(), index);
+ } else {
+ auto qmlBinding = QQmlBinding::create(&propPriv->core, function, obj, ctxt, scope);
+ qmlBinding->setTarget(prop);
+ binding = qmlBinding;
+ }
+ return binding;
+ }
+
+ /*!
+ \internal
+ Removes the binding from \a prop if there is any.
+ */
+ static void removeBindingFrom(QQmlProperty &prop)
+ {
+ if (prop.isBindable())
+ prop.property().bindable(prop.object()).takeBinding();
+ else
+ QQmlPropertyPrivate::removeBinding(prop);
+ }
+
+ /*!
+ \internal
+ Creates a binding for property \a prop from \a function.
+ \a obj is the scope object which shall be used for the function and \a scope its QML scope.
+ The binding is not installed on the property (but if a QQmlBinding is created, it has its
+ target set to \a prop).
+ */
+ static QQmlAnyBinding createFromCodeString(const QQmlProperty &prop, const QString& code, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber) {
+ QQmlAnyBinding binding;
+ auto propPriv = QQmlPropertyPrivate::get(prop);
+ if (prop.isBindable()) {
+ auto index = QQmlPropertyIndex(propPriv->core.coreIndex(), -1);
+ binding = QQmlPropertyBinding::createFromCodeString(&propPriv->core,
+ code, obj, ctxt,
+ url, lineNumber,
+ prop.object(), index);
+ } else {
+ auto qmlBinding = QQmlBinding::create(&propPriv->core, code, obj, ctxt, url, lineNumber);
+ qmlBinding->setTarget(prop);
+ binding = qmlBinding;
+ }
+ return binding;
+ }
+
+ /*!
+ \internal
+ Creates a translattion binding for \a prop from \a compilationUnit and \a transationBinding.
+ \a obj is the context object, \a context the qml context.
+ */
+ static QQmlAnyBinding createTranslationBinding(const QQmlProperty &prop, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *translationBinding, QObject *scopeObject=nullptr, QQmlRefPointer<QQmlContextData> context={})
+ {
+ QQmlAnyBinding binding;
+ auto propPriv = QQmlPropertyPrivate::get(prop);
+ if (prop.isBindable()) {
+ binding = QQmlTranslationPropertyBinding::create(&propPriv->core, compilationUnit, translationBinding);
+ } else {
+ auto qmlBinding = QQmlBinding::createTranslationBinding(compilationUnit, translationBinding, scopeObject, context);
+ binding = qmlBinding;
+ qmlBinding->setTarget(prop);
+ }
+ return binding;
+ }
+
+ /*!
+ \internal
+ Installs the binding referenced by this QQmlAnyBinding on the target.
+ If \a mode is set to RespectInterceptors, interceptors are honored, otherwise
+ writes and binding installation bypass them (the default).
+ Preconditions:
+ - The binding is non-null.
+ - If the binding is QQmlAbstractBinding derived, the target is non-bindable.
+ - If the binding is a QUntypedPropertyBinding, then the target is bindable.
+ */
+ enum InterceptorMode : bool {
+ IgnoreInterceptors,
+ RespectInterceptors
+ };
+
+ void installOn(const QQmlProperty &target, InterceptorMode mode = IgnoreInterceptors)
+ {
+ Q_ASSERT(!d.isNull());
+ if (isAbstractPropertyBinding()) {
+ auto abstractBinding = asAbstractBinding();
+ Q_ASSERT(abstractBinding->targetObject() == target.object() || QQmlPropertyPrivate::get(target)->core.isAlias());
+ Q_ASSERT(!target.isBindable());
+ if (mode == IgnoreInterceptors)
+ QQmlPropertyPrivate::setBinding(abstractBinding, QQmlPropertyPrivate::None, QQmlPropertyData::DontRemoveBinding | QQmlPropertyData::BypassInterceptor);
+ else
+ QQmlPropertyPrivate::setBinding(abstractBinding);
+ } else {
+ Q_ASSERT(target.isBindable());
+ // TODO: QMetaProperty::bindable needs a mode to work on the dynamic metaobject
+ QUntypedBindable bindable = target.property().bindable(target.object());
+ bindable.setBinding(asUntypedPropertyBinding());
+ }
+ }
+
+ /*!
+ Stores a null binding. For purpose of classification, the null bindings is
+ treated as a QQmlAbstractPropertyBindings.
+ */
+ QQmlAnyBinding& operator=(std::nullptr_t )
+ {
+ clear();
+ return *this;
+ }
+
+ operator bool() const{
+ return !d.isNull();
+ }
+
+ /*!
+ \internal
+ Returns true if a binding derived from QQmlAbstractPropertyBinding is stored.
+ The binding migh still be null.
+ */
+ bool isAbstractPropertyBinding() const
+ { return d.isT1(); }
+
+ /*!
+ \internal
+ Returns true if a binding derived from QPropertyBindingPrivate is stored.
+ The binding might still be null.
+ */
+ bool isUntypedPropertyBinding() const
+ { return d.isT2(); }
+
+ /*!
+ \internal
+ Returns the stored QPropertyBindingPrivate as a QUntypedPropertyBinding.
+ If no such binding is currently stored, a null QUntypedPropertyBinding is returned.
+ */
+ QUntypedPropertyBinding asUntypedPropertyBinding() const
+ {
+ if (d.isT1() || d.isNull())
+ return {};
+ auto priv = d.asT2();
+ return QUntypedPropertyBinding {priv};
+ }
+
+ /*!
+ \internal
+ Returns the stored QQmlAbstractBinding.
+ If no such binding is currently stored, a null pointer is returned.
+ */
+ QQmlAbstractBinding *asAbstractBinding() const
+ {
+ if (d.isT2() || d.isNull())
+ return nullptr;
+ return d.asT1();
+ }
+
+ /*!
+ \internal
+ Stores \a binding and keeps a reference to it.
+ */
+ QQmlAnyBinding& operator=(QQmlAbstractBinding *binding)
+ {
+ clear();
+ if (binding) {
+ d = binding;
+ binding->ref.ref();
+ }
+ return *this;
+ }
+
+ /*!
+ \internal
+ Stores the binding stored in \a binding and keeps a reference to it.
+ */
+ QQmlAnyBinding& operator=(const QQmlAbstractBinding::Ptr &binding)
+ {
+ clear();
+ if (binding) {
+ d = binding.data();
+ binding->ref.ref();
+ }
+ return *this;
+ }
+
+ /*!
+ \internal
+ Stores \a binding's binding, taking ownership from \a binding.
+ */
+ QQmlAnyBinding& operator=(QQmlAbstractBinding::Ptr &&binding)
+ {
+ clear();
+ if (binding) {
+ d = binding.take();
+ }
+ return *this;
+ }
+
+ /*!
+ \internal
+ Stores the binding stored in \a untypedBinding and keeps a reference to it.
+ */
+ QQmlAnyBinding& operator=(const QUntypedPropertyBinding &untypedBinding)
+ {
+ clear();
+ auto binding = QPropertyBindingPrivate::get(untypedBinding);
+ if (binding) {
+ d = binding;
+ binding->addRef();
+ }
+ return *this;
+ }
+
+ /*!
+ \internal
+ \overload
+ Stores the binding stored in \a untypedBinding, taking ownership from it.
+ */
+ QQmlAnyBinding& operator=(const QUntypedPropertyBinding &&untypedBinding)
+ {
+ clear();
+ auto binding = QPropertyBindingPrivate::get(untypedBinding);
+ QPropertyBindingPrivatePtr ptr(binding);
+ if (binding) {
+ d = static_cast<QPropertyBindingPrivate *>(ptr.take());
+ }
+ return *this;
+ }
+
+ QQmlAnyBinding(const QQmlAnyBinding &other)
+ {
+ *this = other;
+ }
+
+ friend void swap(const QQmlAnyBinding &a, const QQmlAnyBinding &b)
+ {
+ qSwap(a.d, b.d);
+ }
+
+ QQmlAnyBinding& operator=(const QQmlAnyBinding &other)
+ {
+ clear();
+ if (auto abstractBinding = other.asAbstractBinding())
+ *this = abstractBinding;
+ else if (auto untypedBinding = other.asUntypedPropertyBinding(); !untypedBinding.isNull())
+ *this = untypedBinding;
+ return *this;
+ }
+
+ friend inline bool operator==(const QQmlAnyBinding &p1, const QQmlAnyBinding &p2)
+ {
+ return p1.d == p2.d;
+ }
+
+ friend inline bool operator!=(const QQmlAnyBinding &p1, const QQmlAnyBinding &p2)
+ {
+ return p1.d != p2.d;
+ }
+
+ ~QQmlAnyBinding() {
+ clear();
+ }
+private:
+ void clear() {
+ if (d.isNull())
+ return;
+ if (d.isT1()) {
+ QQmlAbstractBinding *qqmlptr = d.asT1();
+ if (!qqmlptr->ref.deref())
+ delete qqmlptr;
+ } else if (d.isT2()) {
+ QPropertyBindingPrivate *priv = d.asT2();
+ priv->ref--;
+ if (!priv->ref)
+ QPropertyBindingPrivate::destroyAndFreeMemory(priv);
+ }
+ d = static_cast<QQmlAbstractBinding *>(nullptr);
+ }
+ QBiPointer<QQmlAbstractBinding, QPropertyBindingPrivate> d = static_cast<QQmlAbstractBinding *>(nullptr);
+};
+
+
+#endif // QQMLANYBINDINGPTR_P_H
diff --git a/src/qml/qml/qqmlpropertybinding.cpp b/src/qml/qml/qqmlpropertybinding.cpp
index 6757618b86..e600748be7 100644
--- a/src/qml/qml/qqmlpropertybinding.cpp
+++ b/src/qml/qml/qqmlpropertybinding.cpp
@@ -97,6 +97,20 @@ QUntypedPropertyBinding QQmlPropertyBinding::create(const QQmlPropertyData *pd,
return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
}
+QUntypedPropertyBinding QQmlPropertyBinding::createFromCodeString(const QQmlPropertyData *pd, const QString& str, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber, QObject *target, QQmlPropertyIndex targetIndex)
+{
+ auto buffer = new std::byte[sizeof(QQmlPropertyBinding)+sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
+ auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, true);
+ auto js = new(buffer + sizeof(QQmlPropertyBinding) + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
+ Q_ASSERT(binding->jsExpression() == js);
+ Q_ASSERT(js->asBinding() == binding);
+ Q_UNUSED(js);
+ binding->jsExpression()->setNotifyOnValueChanged(true);
+ binding->jsExpression()->setContext(ctxt);
+ binding->jsExpression()->createQmlBinding(ctxt, obj, str, url, lineNumber);
+ return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
+}
+
QUntypedPropertyBinding QQmlPropertyBinding::createFromBoundFunction(const QQmlPropertyData *pd, QV4::BoundFunction *function, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex)
{
auto buffer = new std::byte[sizeof(QQmlPropertyBinding)+sizeof(QQmlPropertyBindingJSForBoundFunction)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
diff --git a/src/qml/qml/qqmlpropertybinding_p.h b/src/qml/qml/qqmlpropertybinding_p.h
index 4dcd5c1879..7c7fac866a 100644
--- a/src/qml/qml/qqmlpropertybinding_p.h
+++ b/src/qml/qml/qqmlpropertybinding_p.h
@@ -101,6 +101,11 @@ public:
QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
QV4::ExecutionContext *scope, QObject *target,
QQmlPropertyIndex targetIndex);
+ static QUntypedPropertyBinding createFromCodeString(const QQmlPropertyData *property,
+ const QString &str, QObject *obj,
+ const QQmlRefPointer<QQmlContextData> &ctxt,
+ const QString &url, quint16 lineNumber,
+ QObject *target, QQmlPropertyIndex targetIndex);
static QUntypedPropertyBinding createFromBoundFunction(const QQmlPropertyData *pd, QV4::BoundFunction *function,
QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
@@ -157,7 +162,7 @@ inline constexpr BindingFunctionVTable bindingFunctionVTable<QQmlPropertyBinding
class QQmlTranslationPropertyBinding
{
public:
- static QUntypedPropertyBinding create(const QQmlPropertyData *pd,
+ static QUntypedPropertyBinding Q_QML_PRIVATE_EXPORT create(const QQmlPropertyData *pd,
const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
const QV4::CompiledData::Binding *binding);
};
diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt
index ae57b63a1d..58520d92a0 100644
--- a/tests/auto/qml/CMakeLists.txt
+++ b/tests/auto/qml/CMakeLists.txt
@@ -55,6 +55,7 @@ if(QT_FEATURE_private_tests)
add_subdirectory(qmlcachegen)
add_subdirectory(animation)
add_subdirectory(qqmlecmascript)
+ add_subdirectory(qqmlanybinding)
add_subdirectory(qqmlcontext)
add_subdirectory(qqmlexpression)
add_subdirectory(qqmlglobal)
diff --git a/tests/auto/qml/qqmlanybinding/CMakeLists.txt b/tests/auto/qml/qqmlanybinding/CMakeLists.txt
new file mode 100644
index 0000000000..83c9bd7994
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/CMakeLists.txt
@@ -0,0 +1,45 @@
+#####################################################################
+## tst_qqmlanybinding Test:
+#####################################################################
+
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ data/*)
+list(APPEND test_data ${test_data_glob})
+
+qt_internal_add_test(tst_qqmlanybinding
+ SOURCES
+ ../../shared/util.cpp ../../shared/util.h
+ withbindable.h
+ tst_qqmlanybinding.cpp
+ INCLUDE_DIRECTORIES
+ ../../shared
+ PUBLIC_LIBRARIES
+ Qt::CorePrivate
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Network
+ Qt::QmlPrivate
+ TESTDATA ${test_data}
+)
+
+set_target_properties(tst_qqmlanybinding PROPERTIES
+ QT_QML_MODULE_VERSION 1.0
+ QT_QML_MODULE_URI bindable
+)
+
+qt6_qml_type_registration(tst_qqmlanybinding)
+
+## Scopes:
+#####################################################################
+
+qt_internal_extend_target(tst_qqmlanybinding CONDITION ANDROID OR IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\":/data\\\"
+)
+
+qt_internal_extend_target(tst_qqmlanybinding CONDITION NOT ANDROID AND NOT IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\"
+)
diff --git a/tests/auto/qml/qqmlanybinding/data/newstylebinding.qml b/tests/auto/qml/qqmlanybinding/data/newstylebinding.qml
new file mode 100644
index 0000000000..0db793952e
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/data/newstylebinding.qml
@@ -0,0 +1,14 @@
+import QtQml
+import bindable 1.0
+
+WithBinding {
+ id: root
+ property int trigger: 1
+ prop: trigger
+ property alias a2: obj1.a1
+
+ property QtObject obj: QtObject {
+ id: obj1
+ property alias a1: root.prop
+ }
+}
diff --git a/tests/auto/qml/qqmlanybinding/data/oldstylebinding.qml b/tests/auto/qml/qqmlanybinding/data/oldstylebinding.qml
new file mode 100644
index 0000000000..bcbadbc919
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/data/oldstylebinding.qml
@@ -0,0 +1,13 @@
+import QtQml
+
+QtObject {
+ id: root
+ property int trigger: 1
+ property int prop: trigger
+ property alias a2: obj1.a1
+
+ property QtObject obj: QtObject {
+ id: obj1
+ property alias a1: root.prop
+ }
+}
diff --git a/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp b/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp
new file mode 100644
index 0000000000..d99b988c52
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/tst_qqmlanybinding.cpp
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 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 "../../shared/util.h"
+#include <QtQml/QQmlEngine>
+#include <QtQml/QQmlComponent>
+#include <QtCore/QScopedPointer>
+#include <QtQml/private/qqmlanybinding_p.h>
+#include "withbindable.h"
+
+class tst_qqmlanybinding : public QQmlDataTest
+{
+ Q_OBJECT
+
+private slots:
+ void basicActions_data();
+ void basicActions();
+};
+
+void tst_qqmlanybinding::basicActions_data()
+{
+ QTest::addColumn<QString>("fileName");
+ QTest::addRow("old style") << QStringLiteral("oldstylebinding.qml");
+ QTest::addRow("new style") << QStringLiteral("newstylebinding.qml");
+}
+
+static int getRefCount(const QQmlAnyBinding &binding)
+{
+ if (binding.isAbstractPropertyBinding()) {
+ return binding.asAbstractBinding()->ref;
+ } else {
+ // this temporarily adds a refcount because we construc a new untypedpropertybinding
+ // thus -1
+ return QPropertyBindingPrivate::get(binding.asUntypedPropertyBinding())->ref - 1;
+ }
+}
+
+void tst_qqmlanybinding::basicActions()
+{
+ QQmlEngine engine;
+ QFETCH(QString, fileName);
+ QQmlComponent comp(&engine, testFileUrl(fileName));
+ QScopedPointer<QObject> root(comp.create());
+ QVERIFY2(root, qPrintable(comp.errorString()));
+
+ QQmlProperty prop(root.get(), "prop");
+ QQmlProperty trigger(root.get(), "trigger");
+
+ // sanity check
+ QCOMPARE(prop.read().toInt(), 1);
+
+ // We can take the binding from a property
+ auto binding = QQmlAnyBinding::ofProperty(prop);
+ if (fileName == u"oldstylebinding.qml") {
+ // and it is a QQmlAbstractBinding,
+ QVERIFY(binding.isAbstractPropertyBinding());
+ } else {
+ // and it is a new style binding,
+ QVERIFY(binding.isUntypedPropertyBinding());
+ }
+ // which is not null.
+ QVERIFY(binding);
+ // Getting the binding did not remove it.
+ trigger.write(42);
+ QCOMPARE(prop.read().toInt(), 42);
+ // remove does that,
+ QQmlAnyBinding::removeBindingFrom(prop);
+ QVERIFY(!QQmlAnyBinding::ofProperty(prop));
+ // but does not change the value.
+ QCOMPARE(prop.read().toInt(), 42);
+ // As there is no binding anymore, there won't be any change if trigger changes.
+ trigger.write(2);
+ QCOMPARE(prop.read().toInt(), 42);
+ // The binding we took is still valid (and we are currently the sole owner).
+ QVERIFY(getRefCount(binding) == 1);
+ // We can reinstall the binding
+ binding.installOn(prop);
+ // This changes the value to reflect that trigger has changed
+ QCOMPARE(prop.read().toInt(), 2);
+ // The binding behaves normally.
+ trigger.write(3);
+ QCOMPARE(prop.read().toInt(), 3);
+ // "binding" still has a reference to the binding
+ QVERIFY(getRefCount(binding) == 2);
+
+ // aliases are resolved correctly
+ QQmlProperty a2(root.get(), "a2");
+ QCOMPARE(QQmlAnyBinding::ofProperty(a2), binding);
+}
+
+QTEST_MAIN(tst_qqmlanybinding)
+
+#include "tst_qqmlanybinding.moc"
diff --git a/tests/auto/qml/qqmlanybinding/withbindable.h b/tests/auto/qml/qqmlanybinding/withbindable.h
new file mode 100644
index 0000000000..4488c0c350
--- /dev/null
+++ b/tests/auto/qml/qqmlanybinding/withbindable.h
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 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$
+**
+****************************************************************************/
+#ifndef WITH_BINDABLE_H
+#define WITH_BINDABLE_H
+
+#include <QObject>
+#include <qqml.h>
+
+class WithBinding : public QObject {
+ Q_OBJECT
+ QML_ELEMENT
+
+ Q_PROPERTY(int prop READ prop WRITE setProp BINDABLE bindableProp)
+public:
+ int prop() {return m_prop;}
+ void setProp(int i) {m_prop = i;}
+ QBindable<int> bindableProp() {return &m_prop;}
+private:
+ QProperty<int> m_prop;
+};
+
+#endif
+