diff options
| author | Luca Di Sera <luca.disera@qt.io> | 2024-11-13 14:32:21 +0100 |
|---|---|---|
| committer | Luca Di Sera <luca.disera@qt.io> | 2024-12-01 22:30:22 +0100 |
| commit | cca0b460f882aa3fce2e8aa7594bee974d9ed74d (patch) | |
| tree | 90c7d04cd447cb586292d537f6eab7431e2c28ca /src/qml/jsruntime/qv4engine.cpp | |
| parent | a6c8de2780b6dface7504b5423e064e026a6e564 (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.cpp | 48 |
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) |
