aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/jsruntime/qv4engine.cpp
diff options
context:
space:
mode:
authorLuca Di Sera <luca.disera@qt.io>2024-11-13 14:32:21 +0100
committerLuca Di Sera <luca.disera@qt.io>2024-12-01 22:30:22 +0100
commitcca0b460f882aa3fce2e8aa7594bee974d9ed74d (patch)
tree90c7d04cd447cb586292d537f6eab7431e2c28ca /src/qml/jsruntime/qv4engine.cpp
parenta6c8de2780b6dface7504b5423e064e026a6e564 (diff)
Implement read/write-backs for QVariantMap and QVariantHash
When QML receives something from the C++ side, it, sometimes, tries to set up a read/"write-back" mechanism to allow changes on the QML side to be reflected on the C++ side and changes to the original element being synchronized with the QML side. This can happen, for example, when accessing a property of a C++ object that was registered to QML directly from QML. Similarly, it can do so when nesting some of its internal representation of an object with C++ provenance. For example, a `QVariantList` that is passed to the QML side and converted to a `Sequence` type might require some of its stored element to perform a write-back when accessed and modified to ensure that mutations are permanent. For `QVariantMap` and `QVariantHash` this was currently not implemented, with `QVariantMap` being generally converted to a Javascript object and support for `QVariantHash` not necessarily entirely implemented. This can produce surprising results. For example, a `QVariantMap` that is stored in a `QVariantList`, where the list is passed over to the QML side from the C++ side, might incur into its mutations being lost when accessed as a member of the converted `QVariantList`. To ensure that this does not happen, `QVariantMap` and `QVariantHash` will now be converted to a representation that is a `ReferenceObject`, that is, it uses the general read/write-back mechanism in QML. Introduce a new QV4 Object, `VariantAssociationObject`, that can store either a `QVariantMap` or a `QVariantHash` and has read/write-back behavior. The prototype for the object is now registered by the engine and can be accessed through the `variantAssociationPrototype` method. A `QVariantMap`/`QVariantHash` that is being converted to a JS representation will now be converted to the newly introduced object instead of being mapped to a generic JS object. `variantMapToJS` and `variantToJs` in "qv4egnine.cpp", that were used during the conversion of `QVariantMap` to a Javascript object were removed as they are now unused. Some additional cases were added to support conversion from/to `QVariantHash` and conversion from the newly added object. The newly added object supports a small subset of an object functionality and is not intended, at least currently, to support the whole breadth of interactions that a Javascript object would. In particular it aims to support setting properties, retrieving properties, deleting properties, basic iteration and `Object.hasOwnProperty`. It further implements basic read/write-back behavior for those interactions and allows for recursive read/write-backs through the general `virtualMetacall` interface. Additionally, the code `QQmlVMEMetaObject::writeVarProperty` was modified to ensure that the new reference object is detached when assigned to a `var` property, so as to be consistent with the general behavior of the other reference objects. As a drive-by, a comment in the above code that stated that some of the code should be adjusted if a new case was added to it was modified to state that the code should be adjusted with no additional clause, as a new element was added but the adjustment will not be performed as part of this patch. Some general test cases were introduced in `tst_qqmllanguage` for the new object. In particular to test the most basic interactions of the above subset of an object interaction, some of the read/write-back behavior and the behavior of detached when being assigned to a var property. Fixes: QTBUG-129972 Change-Id: Ib655ba6001aef07a74ccf235d2e3223b74d7be59 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/qml/jsruntime/qv4engine.cpp')
-rw-r--r--src/qml/jsruntime/qv4engine.cpp48
1 files changed, 19 insertions, 29 deletions
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp
index 0b6464f094..1be562b86b 100644
--- a/src/qml/jsruntime/qv4engine.cpp
+++ b/src/qml/jsruntime/qv4engine.cpp
@@ -59,6 +59,7 @@
#include "qv4atomics_p.h"
#include "qv4urlobject_p.h"
#include "qv4variantobject_p.h"
+#include "qv4variantassociationobject_p.h"
#include "qv4sequenceobject_p.h"
#include "qv4qobjectwrapper_p.h"
#include "qv4qmetaobjectwrapper_p.h"
@@ -323,6 +324,8 @@ void ExecutionEngine::initializeStaticMembers()
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QVariantMap>())
QMetaType::registerConverter<QJSValue, QVariantMap>(convertJSValueToVariantType<QVariantMap>);
+ if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QVariantHash>())
+ QMetaType::registerConverter<QJSValue, QVariantHash>(convertJSValueToVariantType<QVariantHash>);
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QVariantList>())
QMetaType::registerConverter<QJSValue, QVariantList>(convertJSValueToVariantType<QVariantList>);
if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QStringList>())
@@ -625,6 +628,9 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
jsObjects[VariantProto] = memoryManager->allocate<VariantPrototype>();
Q_ASSERT(variantPrototype()->getPrototypeOf() == objectPrototype()->d());
+ jsObjects[VariantAssociationProto] = memoryManager->allocate<VariantAssociationPrototype>();
+ Q_ASSERT(variantAssociationPrototype()->getPrototypeOf() == objectPrototype()->d());
+
ic = newInternalClass(SequencePrototype::staticVTable(), SequencePrototype::defaultPrototype(this));
jsObjects[SequenceProto] = ScopedValue(scope, memoryManager->allocObject<SequencePrototype>(ic->d()));
@@ -1521,11 +1527,6 @@ static QObject *qtObjectFromJS(const QV4::Value &value);
static QVariant objectToVariant(const QV4::Object *o, V4ObjectSet *visitedObjects = nullptr,
JSToQVariantConversionBehavior behavior = JSToQVariantConversionBehavior::Safish);
static bool convertToNativeQObject(const QV4::Value &value, QMetaType targetType, void **result);
-static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVariantMap &vmap);
-static QV4::ReturnedValue variantToJS(QV4::ExecutionEngine *v4, const QVariant &value)
-{
- return v4->metaTypeToJS(value.metaType(), value.constData());
-}
static QVariant toVariant(const QV4::Value &value, QMetaType metaType, JSToQVariantConversionBehavior conversionBehavior,
V4ObjectSet *visitedObjects)
@@ -1578,6 +1579,10 @@ static QVariant toVariant(const QV4::Value &value, QMetaType metaType, JSToQVari
// Otherwise produce the "natural" type of the sequence.
return QV4::SequencePrototype::toVariant(s);
+ } else if (auto association = object->as<QV4::VariantAssociationObject>()) {
+ if (conversionBehavior == JSToQVariantConversionBehavior::Never)
+ return QVariant::fromValue(QJSValuePrivate::fromReturnedValue(association->asReturnedValue()));
+ return association->d()->toVariant();
}
}
@@ -1847,7 +1852,15 @@ QV4::ReturnedValue ExecutionEngine::fromData(
case QMetaType::QVariantList:
return createSequence(QMetaSequence::fromContainer<QVariantList>());
case QMetaType::QVariantMap:
- return variantMapToJS(this, *reinterpret_cast<const QVariantMap *>(ptr));
+ return VariantAssociationPrototype::fromQVariantMap(
+ this,
+ *reinterpret_cast<const QVariantMap *>(ptr),
+ container, property, Heap::ReferenceObject::Flags(flags));
+ case QMetaType::QVariantHash:
+ return VariantAssociationPrototype::fromQVariantHash(
+ this,
+ *reinterpret_cast<const QVariantHash *>(ptr),
+ container, property, Heap::ReferenceObject::Flags(flags));
case QMetaType::QJsonValue:
return QV4::JsonObject::fromJsonValue(this, *reinterpret_cast<const QJsonValue *>(ptr));
case QMetaType::QJsonObject:
@@ -1973,29 +1986,6 @@ QVariantMap ExecutionEngine::variantMapFromJS(const Object *o)
return objectToVariantMap(o, &visitedObjects, JSToQVariantConversionBehavior::Safish);
}
-// Converts a QVariantMap to JS.
-// The result is a new Object object with property names being
-// the keys of the QVariantMap, and values being the values of
-// the QVariantMap converted to JS, recursively.
-static QV4::ReturnedValue variantMapToJS(QV4::ExecutionEngine *v4, const QVariantMap &vmap)
-{
- QV4::Scope scope(v4);
- QV4::ScopedObject o(scope, v4->newObject());
- QV4::ScopedString s(scope);
- QV4::ScopedPropertyKey key(scope);
- QV4::ScopedValue v(scope);
- for (QVariantMap::const_iterator it = vmap.constBegin(), cend = vmap.constEnd(); it != cend; ++it) {
- s = v4->newIdentifier(it.key());
- key = s->propertyKey();
- v = variantToJS(v4, it.value());
- if (key->isArrayIndex())
- o->arraySet(key->asArrayIndex(), v);
- else
- o->insertMember(s, v);
- }
- return o.asReturnedValue();
-}
-
// Converts the meta-type defined by the given type and data to JS.
// Returns the value if conversion succeeded, an empty handle otherwise.
QV4::ReturnedValue ExecutionEngine::metaTypeToJS(QMetaType type, const void *data)