aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@digia.com>2014-09-10 17:13:10 +0200
committerLars Knoll <lars.knoll@digia.com>2014-09-17 08:13:11 +0200
commit3dbe05f6bf3fd51ce8097c35f6c7f12b39acb0f6 (patch)
tree444ed433aa02085357b589b19b28f4bc1c243320 /src
parentcfe1a8152c948a4586ffa1fe79b47f9a0e88beb5 (diff)
Fix mapping of JS objects/arrays to C++
[ChangeLog][QtQml][Important Behavior Changes] When a JavaScript object/array is passed to C++ through a QVariant, the engine no longer immediately converts the object recursively into a QVariantMap or QVariantList but instead stores a QJSValue in the QVariant. This prevents a loss of data when the JS object contains non-primitive types such as function objects for example. Code that expects the variant type to be exactly QVariant::Map or QVariant::List may need to be adapted. Registered conversion functions however ensure that code that merely calls toMap() or toList() continues to work. Task-number: QTBUG-40431 Change-Id: I1dbc1d5f8e78ad28bb62db3681b9a0b34557e7f5 Reviewed-by: Lars Knoll <lars.knoll@digia.com>
Diffstat (limited to 'src')
-rw-r--r--src/qml/debugger/qqmlenginedebugservice.cpp8
-rw-r--r--src/qml/debugger/qqmlenginedebugservice_p.h2
-rw-r--r--src/qml/jsapi/qjsengine.cpp27
-rw-r--r--src/qml/jsapi/qjsvalue.cpp69
-rw-r--r--src/qml/jsapi/qjsvalue_p.h7
-rw-r--r--src/qml/jsruntime/qv4qobjectwrapper.cpp6
-rw-r--r--src/qml/jsruntime/qv4variantobject.cpp2
-rw-r--r--src/qml/qml/v8/qv8engine.cpp299
-rw-r--r--src/qml/qml/v8/qv8engine_p.h20
-rw-r--r--src/qml/types/qqmllistmodel.cpp11
-rw-r--r--src/qml/util/qqmllistaccessor.cpp7
11 files changed, 227 insertions, 231 deletions
diff --git a/src/qml/debugger/qqmlenginedebugservice.cpp b/src/qml/debugger/qqmlenginedebugservice.cpp
index 399cc3e07d..cb533a0459 100644
--- a/src/qml/debugger/qqmlenginedebugservice.cpp
+++ b/src/qml/debugger/qqmlenginedebugservice.cpp
@@ -169,9 +169,13 @@ QQmlEngineDebugService::propertyData(QObject *obj, int propIdx)
return rv;
}
-QVariant QQmlEngineDebugService::valueContents(const QVariant &value) const
+QVariant QQmlEngineDebugService::valueContents(QVariant value) const
{
- int userType = value.userType();
+ // We can't send JS objects across the wire, so transform them to variant
+ // maps for serialization.
+ if (value.userType() == qMetaTypeId<QJSValue>())
+ value = value.value<QJSValue>().toVariant();
+ const int userType = value.userType();
//QObject * is not streamable.
//Convert all such instances to a String value
diff --git a/src/qml/debugger/qqmlenginedebugservice_p.h b/src/qml/debugger/qqmlenginedebugservice_p.h
index 1bab51d17b..940ca7d99c 100644
--- a/src/qml/debugger/qqmlenginedebugservice_p.h
+++ b/src/qml/debugger/qqmlenginedebugservice_p.h
@@ -111,7 +111,7 @@ private:
void buildStatesList(bool cleanList, const QList<QPointer<QObject> > &instances);
QQmlObjectData objectData(QObject *);
QQmlObjectProperty propertyData(QObject *, int);
- QVariant valueContents(const QVariant &defaultValue) const;
+ QVariant valueContents(QVariant defaultValue) const;
bool setBinding(int objectId, const QString &propertyName, const QVariant &expression, bool isLiteralValue, QString filename = QString(), int line = -1, int column = 0);
bool resetBinding(int objectId, const QString &propertyName);
bool setMethodBody(int objectId, const QString &method, const QString &body);
diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp
index 58251fac96..0d2b394cd6 100644
--- a/src/qml/jsapi/qjsengine.cpp
+++ b/src/qml/jsapi/qjsengine.cpp
@@ -420,17 +420,19 @@ bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr)
QV4::ScopedValue v(scope, vp->getValue(engine->m_v4Engine));
return engine->metaTypeFromJS(v, type, ptr);
} else if (vp->value.isEmpty()) {
- // have a string based value without engine. Do conversion manually
- if (type == QMetaType::Bool) {
- *reinterpret_cast<bool*>(ptr) = vp->string.length() != 0;
- return true;
- }
- if (type == QMetaType::QString) {
- *reinterpret_cast<QString*>(ptr) = vp->string;
- return true;
- }
- double d = QV4::RuntimeHelpers::stringToNumber(vp->string);
- switch (type) {
+ if (vp->unboundData.userType() == QMetaType::QString) {
+ QString string = vp->unboundData.toString();
+ // have a string based value without engine. Do conversion manually
+ if (type == QMetaType::Bool) {
+ *reinterpret_cast<bool*>(ptr) = string.length() != 0;
+ return true;
+ }
+ if (type == QMetaType::QString) {
+ *reinterpret_cast<QString*>(ptr) = string;
+ return true;
+ }
+ double d = QV4::RuntimeHelpers::stringToNumber(string);
+ switch (type) {
case QMetaType::Int:
*reinterpret_cast<int*>(ptr) = QV4::Primitive::toInt32(d);
return true;
@@ -466,6 +468,9 @@ bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr)
return true;
default:
return false;
+ }
+ } else {
+ return QMetaType::convert(&vp->unboundData.data_ptr(), vp->unboundData.userType(), ptr, type);
}
} else {
switch (type) {
diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp
index 891f17762c..47a764e641 100644
--- a/src/qml/jsapi/qjsvalue.cpp
+++ b/src/qml/jsapi/qjsvalue.cpp
@@ -58,14 +58,14 @@ QV4::ReturnedValue QJSValuePrivate::getValue(QV4::ExecutionEngine *e)
}
if (value.isEmpty()) {
- value = QV4::Encode(engine->newString(string));
+ value = QV4::Encode(engine->v8Engine->fromVariant(unboundData));
PersistentValuePrivate **listRoot = &engine->memoryManager->m_persistentValues;
prev = listRoot;
next = *listRoot;
*prev = this;
if (next)
next->prev = &this->next;
- string = QString();
+ unboundData.clear();
}
return value.asReturnedValue();
}
@@ -353,8 +353,21 @@ bool QJSValue::isVariant() const
*/
QString QJSValue::toString() const
{
- if (d->value.isEmpty())
- return d->string;
+ if (d->value.isEmpty()) {
+ if (d->unboundData.type() == QVariant::Map)
+ return QStringLiteral("[object Object]");
+ else if (d->unboundData.type() == QVariant::List) {
+ const QVariantList list = d->unboundData.toList();
+ QString result;
+ for (int i = 0; i < list.count(); ++i) {
+ if (i > 0)
+ result.append(QLatin1Char(','));
+ result.append(list.at(i).toString());
+ }
+ return result;
+ }
+ return d->unboundData.toString();
+ }
return d->value.toQStringNoThrow();
}
@@ -372,8 +385,14 @@ QString QJSValue::toString() const
*/
double QJSValue::toNumber() const
{
- if (d->value.isEmpty())
- return RuntimeHelpers::stringToNumber(d->string);
+ if (d->value.isEmpty()) {
+ if (d->unboundData.type() == QVariant::String)
+ return RuntimeHelpers::stringToNumber(d->unboundData.toString());
+ else if (d->unboundData.canConvert<double>())
+ return d->unboundData.value<double>();
+ else
+ return std::numeric_limits<double>::quiet_NaN();
+ }
QV4::ExecutionContext *ctx = d->engine ? d->engine->currentContext() : 0;
double dbl = d->value.toNumber();
@@ -398,8 +417,12 @@ double QJSValue::toNumber() const
*/
bool QJSValue::toBool() const
{
- if (d->value.isEmpty())
- return d->string.length() > 0;
+ if (d->value.isEmpty()) {
+ if (d->unboundData.userType() == QMetaType::QString)
+ return d->unboundData.toString().length() > 0;
+ else
+ return d->unboundData.toBool();
+ }
QV4::ExecutionContext *ctx = d->engine ? d->engine->currentContext() : 0;
bool b = d->value.toBoolean();
@@ -424,8 +447,12 @@ bool QJSValue::toBool() const
*/
qint32 QJSValue::toInt() const
{
- if (d->value.isEmpty())
- return QV4::Primitive::toInt32(RuntimeHelpers::stringToNumber(d->string));
+ if (d->value.isEmpty()) {
+ if (d->unboundData.userType() == QMetaType::QString)
+ return QV4::Primitive::toInt32(RuntimeHelpers::stringToNumber(d->unboundData.toString()));
+ else
+ return d->unboundData.toInt();
+ }
QV4::ExecutionContext *ctx = d->engine ? d->engine->currentContext() : 0;
qint32 i = d->value.toInt32();
@@ -450,8 +477,12 @@ qint32 QJSValue::toInt() const
*/
quint32 QJSValue::toUInt() const
{
- if (d->value.isEmpty())
- return QV4::Primitive::toUInt32(RuntimeHelpers::stringToNumber(d->string));
+ if (d->value.isEmpty()) {
+ if (d->unboundData.userType() == QMetaType::QString)
+ return QV4::Primitive::toUInt32(RuntimeHelpers::stringToNumber(d->unboundData.toString()));
+ else
+ return d->unboundData.toUInt();
+ }
QV4::ExecutionContext *ctx = d->engine ? d->engine->currentContext() : 0;
quint32 u = d->value.toUInt32();
@@ -487,7 +518,7 @@ quint32 QJSValue::toUInt() const
QVariant QJSValue::toVariant() const
{
if (d->value.isEmpty())
- return QVariant(d->string);
+ return d->unboundData;
return QV4::VariantObject::toVariant(d->value);
}
@@ -775,8 +806,10 @@ bool QJSValue::equals(const QJSValue& other) const
{
if (d->value.isEmpty()) {
if (other.d->value.isEmpty())
- return d->string == other.d->string;
- return js_equal(d->string, QV4::ValueRef(other.d->value));
+ return d->unboundData == other.d->unboundData;
+ if (d->unboundData.type() == QVariant::Map || d->unboundData.type() == QVariant::List)
+ return false;
+ return js_equal(d->unboundData.toString(), QV4::ValueRef(other.d->value));
}
if (other.d->value.isEmpty())
return other.equals(*this);
@@ -810,9 +843,11 @@ bool QJSValue::strictlyEquals(const QJSValue& other) const
{
if (d->value.isEmpty()) {
if (other.d->value.isEmpty())
- return d->string == other.d->string;
+ return d->unboundData == other.d->unboundData;
+ if (d->unboundData.type() == QVariant::Map || d->unboundData.type() == QVariant::List)
+ return false;
if (other.d->value.isString())
- return d->string == other.d->value.stringValue()->toQString();
+ return d->unboundData.toString() == other.d->value.stringValue()->toQString();
return false;
}
if (other.d->value.isEmpty())
diff --git a/src/qml/jsapi/qjsvalue_p.h b/src/qml/jsapi/qjsvalue_p.h
index e66c1bcde4..43a3a74e38 100644
--- a/src/qml/jsapi/qjsvalue_p.h
+++ b/src/qml/jsapi/qjsvalue_p.h
@@ -51,6 +51,7 @@
#include <private/qv4string_p.h>
#include <private/qv4engine_p.h>
#include <private/qv4object_p.h>
+#include <private/qflagpointer_p.h>
QT_BEGIN_NAMESPACE
@@ -72,8 +73,8 @@ public:
Q_ASSERT(!value.isEmpty());
}
QJSValuePrivate(const QString &s)
- : PersistentValuePrivate(QV4::Primitive::emptyValue().asReturnedValue())
- , string(s)
+ : PersistentValuePrivate(QV4::Primitive::emptyValue().asReturnedValue()),
+ unboundData(s)
{
}
@@ -81,7 +82,7 @@ public:
static QJSValuePrivate *get(const QJSValue &v) { return v.d; }
- QString string;
+ QVariant unboundData;
};
QT_END_NAMESPACE
diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp
index ff51ee6c6f..32379f7f1e 100644
--- a/src/qml/jsruntime/qv4qobjectwrapper.cpp
+++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp
@@ -1665,17 +1665,13 @@ void CallArgument::fromValue(int callType, QV8Engine *engine, const QV4::ValueRe
type = -1;
QQmlEnginePrivate *ep = engine->engine() ? QQmlEnginePrivate::get(engine->engine()) : 0;
- QVariant v = engine->toVariant(value, -1); // why -1 instead of callType?
+ QVariant v = engine->toVariant(value, callType);
if (v.userType() == callType) {
*qvariantPtr = v;
} else if (v.canConvert(callType)) {
*qvariantPtr = v;
qvariantPtr->convert(callType);
- } else if (QV4::SequencePrototype::isSequenceType(callType) && v.userType() == qMetaTypeId<QVariantList>()) {
- // convert the JS array to a sequence of the correct type.
- QVariant seqV = engine->toVariant(value, callType);
- *qvariantPtr = seqV;
} else {
QQmlMetaObject mo = ep ? ep->rawMetaObjectForType(callType) : QQmlMetaObject();
if (!mo.isNull()) {
diff --git a/src/qml/jsruntime/qv4variantobject.cpp b/src/qml/jsruntime/qv4variantobject.cpp
index 34657501e8..68b08fb3ca 100644
--- a/src/qml/jsruntime/qv4variantobject.cpp
+++ b/src/qml/jsruntime/qv4variantobject.cpp
@@ -59,7 +59,7 @@ VariantObject::Data::Data(ExecutionEngine *engine, const QVariant &value)
QVariant VariantObject::toVariant(const QV4::ValueRef v)
{
if (v->asObject())
- return v->engine()->v8Engine->variantFromJS(v);
+ return v->engine()->v8Engine->toVariant(v, /*typeHint*/ -1, /*createJSValueForObjects*/ false);
if (v->isString())
return QVariant(v->stringValue()->toQString());
diff --git a/src/qml/qml/v8/qv8engine.cpp b/src/qml/qml/v8/qv8engine.cpp
index b56af2e7f6..993cf96104 100644
--- a/src/qml/qml/v8/qv8engine.cpp
+++ b/src/qml/qml/v8/qv8engine.cpp
@@ -79,6 +79,37 @@ Q_DECLARE_METATYPE(QList<int>)
// QQmlEngine is not available
QT_BEGIN_NAMESPACE
+template <typename ReturnType>
+ReturnType convertJSValueToVariantType(const QJSValue &value)
+{
+ return value.toVariant().value<ReturnType>();
+}
+
+static void saveJSValue(QDataStream &stream, const void *data)
+{
+ const QJSValue *jsv = reinterpret_cast<const QJSValue *>(data);
+ const quint32 isNullOrUndefined = jsv->isNull() | (jsv->isUndefined() << 1);
+ stream << isNullOrUndefined;
+ if (!isNullOrUndefined)
+ reinterpret_cast<const QJSValue*>(data)->toVariant().save(stream);
+}
+
+static void restoreJSValue(QDataStream &stream, void *data)
+{
+ QJSValue *jsv = reinterpret_cast<QJSValue*>(data);
+ QJSValuePrivate *d = QJSValuePrivate::get(*jsv);
+
+ quint32 isNullOrUndefined;
+ stream >> isNullOrUndefined;
+ if (isNullOrUndefined & 0x1) {
+ d->value = QV4::Primitive::nullValue().asReturnedValue();
+ } else if (isNullOrUndefined & 0x2) {
+ d->value = QV4::Primitive::undefinedValue().asReturnedValue();
+ } else {
+ d->value = QV4::Primitive::emptyValue().asReturnedValue();
+ d->unboundData.load(stream);
+ }
+}
QV8Engine::QV8Engine(QJSEngine* qq)
: q(qq)
@@ -96,6 +127,14 @@ QV8Engine::QV8Engine(QJSEngine* qq)
qMetaTypeId<QJSValue>();
qMetaTypeId<QList<int> >();
+ if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QVariantMap>())
+ QMetaType::registerConverter<QJSValue, QVariantMap>(convertJSValueToVariantType<QVariantMap>);
+ if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QVariantList>())
+ QMetaType::registerConverter<QJSValue, QVariantList>(convertJSValueToVariantType<QVariantList>);
+ if (!QMetaType::hasRegisteredConverterFunction<QJSValue, QStringList>())
+ QMetaType::registerConverter<QJSValue, QStringList>(convertJSValueToVariantType<QStringList>);
+ QMetaType::registerStreamOperators(qMetaTypeId<QJSValue>(), saveJSValue, restoreJSValue);
+
m_v4Engine = new QV4::ExecutionEngine;
m_v4Engine->v8Engine = this;
@@ -116,7 +155,7 @@ QV8Engine::~QV8Engine()
delete m_v4Engine;
}
-QVariant QV8Engine::toVariant(const QV4::ValueRef value, int typeHint)
+QVariant QV8Engine::toVariant(const QV4::ValueRef value, int typeHint, bool createJSValueForObjects, V8ObjectSet *visitedObjects)
{
Q_ASSERT (!value->isEmpty());
QV4::Scope scope(m_v4Engine);
@@ -178,7 +217,88 @@ QVariant QV8Engine::toVariant(const QV4::ValueRef value, int typeHint)
return retn;
}
- return toBasicVariant(value);
+ if (value->isUndefined())
+ return QVariant();
+ if (value->isNull())
+ return QVariant(QMetaType::VoidStar, (void *)0);
+ if (value->isBoolean())
+ return value->booleanValue();
+ if (value->isInteger())
+ return value->integerValue();
+ if (value->isNumber())
+ return value->asDouble();
+ if (value->isString())
+ return value->stringValue()->toQString();
+ if (QQmlLocaleData *ld = value->as<QQmlLocaleData>())
+ return ld->d()->locale;
+ if (QV4::DateObject *d = value->asDateObject())
+ return d->toQDateTime();
+ // NOTE: since we convert QTime to JS Date, round trip will change the variant type (to QDateTime)!
+
+ QV4::ScopedObject o(scope, value);
+ Q_ASSERT(o);
+
+ if (QV4::RegExpObject *re = o->as<QV4::RegExpObject>())
+ return re->toQRegExp();
+
+ if (createJSValueForObjects)
+ return QVariant::fromValue(QJSValue(new QJSValuePrivate(o->asReturnedValue())));
+
+ return objectToVariant(o, visitedObjects);
+}
+
+QVariant QV8Engine::objectToVariant(QV4::Object *o, V8ObjectSet *visitedObjects)
+{
+ Q_ASSERT(o);
+
+ V8ObjectSet recursionGuardSet;
+ if (!visitedObjects) {
+ visitedObjects = &recursionGuardSet;
+ } else if (visitedObjects->contains(o)) {
+ // Avoid recursion.
+ // For compatibility with QVariant{List,Map} conversion, we return an
+ // empty object (and no error is thrown).
+ if (o->asArrayObject())
+ return QVariantList();
+ return QVariantMap();
+ }
+ visitedObjects->insert(o);
+
+ QVariant result;
+
+ if (o->asArrayObject()) {
+ QV4::Scope scope(m_v4Engine);
+ QV4::ScopedArrayObject a(scope, o->asReturnedValue());
+ QV4::ScopedValue v(scope);
+ QVariantList list;
+
+ int length = a->getLength();
+ for (int ii = 0; ii < length; ++ii) {
+ v = a->getIndexed(ii);
+ list << toVariant(v, -1, /*createJSValueForObjects*/false, visitedObjects);
+ }
+
+ result = list;
+ } else if (!o->asFunctionObject()) {
+ QVariantMap map;
+ QV4::Scope scope(m_v4Engine);
+ QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
+ QV4::ScopedValue name(scope);
+ QV4::ScopedValue val(scope);
+ while (1) {
+ name = it.nextPropertyNameAsString(val);
+ if (name->isNull())
+ break;
+
+ QString key = name->toQStringNoThrow();
+ map.insert(key, toVariant(val, /*type hint*/-1, /*createJSValueForObjects*/false, visitedObjects));
+ }
+
+ result = map;
+ }
+
+ visitedObjects->remove(o);
+ return result;
}
static QV4::ReturnedValue arrayFromStringList(QV8Engine *engine, const QStringList &list)
@@ -370,61 +490,6 @@ QQmlContextData *QV8Engine::callingContext()
return QV4::QmlContextWrapper::callingContext(m_v4Engine);
}
-// Converts a JS value to a QVariant.
-// Null, Undefined -> QVariant() (invalid)
-// Boolean -> QVariant(bool)
-// Number -> QVariant(double)
-// String -> QVariant(QString)
-// Array -> QVariantList(...)
-// Date -> QVariant(QDateTime)
-// RegExp -> QVariant(QRegExp)
-// [Any other object] -> QVariantMap(...)
-QVariant QV8Engine::toBasicVariant(const QV4::ValueRef value)
-{
- if (value->isUndefined())
- return QVariant();
- if (value->isNull())
- return QVariant(QMetaType::VoidStar, (void *)0);
- if (value->isBoolean())
- return value->booleanValue();
- if (value->isInteger())
- return value->integerValue();
- if (value->isNumber())
- return value->asDouble();
- if (value->isString())
- return value->stringValue()->toQString();
- if (QQmlLocaleData *ld = value->as<QQmlLocaleData>())
- return ld->d()->locale;
- if (QV4::DateObject *d = value->asDateObject())
- return d->toQDateTime();
- // NOTE: since we convert QTime to JS Date, round trip will change the variant type (to QDateTime)!
-
- QV4::Scope scope(value->engine());
- QV4::ScopedObject o(scope, value);
- Q_ASSERT(o);
-
- if (QV4::RegExpObject *re = o->as<QV4::RegExpObject>())
- return re->toQRegExp();
- if (o->asArrayObject()) {
- QV4::ScopedArrayObject a(scope, o);
- QV4::ScopedValue v(scope);
- QVariantList rv;
-
- int length = a->getLength();
- for (int ii = 0; ii < length; ++ii) {
- v = a->getIndexed(ii);
- rv << toVariant(v, -1);
- }
- return rv;
- }
- if (!value->asFunctionObject())
- return variantMapFromJS(o);
-
- return QVariant();
-}
-
-
-
void QV8Engine::initializeGlobal()
{
QV4::Scope scope(m_v4Engine);
@@ -547,36 +612,6 @@ QV4::ReturnedValue QV8Engine::variantListToJS(const QVariantList &lst)
return a.asReturnedValue();
}
-// Converts a JS Array object to a QVariantList.
-// The result is a QVariantList with length equal to the length
-// of the JS Array, and elements being the JS Array's elements
-// converted to QVariants, recursively.
-QVariantList QV8Engine::variantListFromJS(QV4::ArrayObject *a, V8ObjectSet &visitedObjects)
-{
- QVariantList result;
- if (!a)
- return result;
-
- if (visitedObjects.contains(a))
- // Avoid recursion.
- return result;
-
- visitedObjects.insert(a);
-
- QV4::Scope scope(a->engine());
- QV4::ScopedValue v(scope);
-
- quint32 length = a->getLength();
- for (quint32 i = 0; i < length; ++i) {
- v = a->getIndexed(i);
- result.append(variantFromJS(v, visitedObjects));
- }
-
- visitedObjects.remove(a);
-
- return result;
-}
-
// 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
@@ -600,43 +635,6 @@ QV4::ReturnedValue QV8Engine::variantMapToJS(const QVariantMap &vmap)
return o.asReturnedValue();
}
-// Converts a JS Object to a QVariantMap.
-// The result is a QVariantMap with keys being the property names
-// of the object, and values being the values of the JS object's
-// properties converted to QVariants, recursively.
-QVariantMap QV8Engine::variantMapFromJS(QV4::Object *o, V8ObjectSet &visitedObjects)
-{
- QVariantMap result;
-
- if (!o || o->asFunctionObject())
- return result;
-
- if (visitedObjects.contains(o)) {
- // Avoid recursion.
- // For compatibility with QVariant{List,Map} conversion, we return an
- // empty object (and no error is thrown).
- return result;
- }
- QV4::Scope scope(o->engine());
-
- visitedObjects.insert(o);
-
- QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly);
- QV4::ScopedValue name(scope);
- QV4::ScopedValue val(scope);
- while (1) {
- name = it.nextPropertyNameAsString(val);
- if (name->isNull())
- break;
-
- QString key = name->toQStringNoThrow();
- result.insert(key, variantFromJS(val, visitedObjects));
- }
-
- visitedObjects.remove(o);
- return result;
-}
-
// 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 QV8Engine::metaTypeToJS(int type, const void *data)
@@ -811,7 +809,7 @@ bool QV8Engine::metaTypeFromJS(const QV4::ValueRef value, int type, void *data)
case QMetaType::QVariantList: {
QV4::ScopedArrayObject a(scope, value);
if (a) {
- *reinterpret_cast<QVariantList *>(data) = variantListFromJS(a);
+ *reinterpret_cast<QVariantList *>(data) = toVariant(a, /*typeHint*/-1, /*createJSValueForObjects*/false).toList();
return true;
}
break;
@@ -825,7 +823,7 @@ bool QV8Engine::metaTypeFromJS(const QV4::ValueRef value, int type, void *data)
break;
}
case QMetaType::QVariant:
- *reinterpret_cast<QVariant*>(data) = variantFromJS(value);
+ *reinterpret_cast<QVariant*>(data) = toVariant(value, /*typeHint*/-1, /*createJSValueForObjects*/false);
return true;
case QMetaType::QJsonValue:
*reinterpret_cast<QJsonValue *>(data) = QV4::JsonObject::toJsonValue(value);
@@ -921,55 +919,6 @@ QV4::ReturnedValue QV8Engine::variantToJS(const QVariant &value)
return metaTypeToJS(value.userType(), value.constData());
}
-// Converts a JS value to a QVariant.
-// Undefined -> QVariant() (invalid)
-// Null -> QVariant((void*)0)
-// Boolean -> QVariant(bool)
-// Number -> QVariant(double)
-// String -> QVariant(QString)
-// Array -> QVariantList(...)
-// Date -> QVariant(QDateTime)
-// RegExp -> QVariant(QRegExp)
-// [Any other object] -> QVariantMap(...)
-QVariant QV8Engine::variantFromJS(const QV4::ValueRef value,
- V8ObjectSet &visitedObjects)
-{
- Q_ASSERT(!value->isEmpty());
- if (value->isUndefined())
- return QVariant();
- if (value->isNull())
- return QVariant(QMetaType::VoidStar, 0);
- if (value->isBoolean())
- return value->booleanValue();
- if (value->isInteger())
- return value->integerValue();
- if (value->isNumber())
- return value->asDouble();
- if (value->isString())
- return value->stringValue()->toQString();
-
- Q_ASSERT(value->isObject());
- QV4::Scope scope(value->engine());
-
- if (value->asArrayObject()) {
- QV4::ScopedArrayObject a(scope, value);
- return variantListFromJS(a, visitedObjects);
- }
- if (QV4::DateObject *d = value->asDateObject())
- return d->toQDateTime();
- if (QV4::RegExpObject *re = value->as<QV4::RegExpObject>())
- return re->toQRegExp();
- if (QV4::VariantObject *v = value->as<QV4::VariantObject>())
- return v->d()->data;
- if (value->as<QV4::QObjectWrapper>())
- return qVariantFromValue(qtObjectFromJS(value));
- if (QV4::QmlValueTypeWrapper *v = value->as<QV4::QmlValueTypeWrapper>())
- return v->toVariant();
- QV4::ScopedObject o(scope, value);
- return variantMapFromJS(o, visitedObjects);
-}
-
-
bool QV8Engine::convertToNativeQObject(const QV4::ValueRef value, const QByteArray &targetType, void **result)
{
if (!targetType.endsWith('*'))
diff --git a/src/qml/qml/v8/qv8engine_p.h b/src/qml/qml/v8/qv8engine_p.h
index e2c96ffc87..51e857c8a2 100644
--- a/src/qml/qml/v8/qv8engine_p.h
+++ b/src/qml/qml/v8/qv8engine_p.h
@@ -197,9 +197,13 @@ public:
void freezeObject(const QV4::ValueRef value);
- QVariant toVariant(const QV4::ValueRef value, int typeHint);
+ QVariant toVariant(const QV4::ValueRef value, int typeHint, bool createJSValueForObjects = true, V8ObjectSet *visitedObjects = 0);
+ QVariant objectToVariant(QV4::Object *o, V8ObjectSet *visitedObjects = 0);
QV4::ReturnedValue fromVariant(const QVariant &);
+ QVariantMap variantMapFromJS(QV4::Object *o)
+ { return objectToVariant(o).toMap(); }
+
// Return a JS string for the given QString \a string
QV4::ReturnedValue toString(const QString &string);
@@ -218,16 +222,8 @@ public:
void setExtensionData(int, Deletable *);
QV4::ReturnedValue variantListToJS(const QVariantList &lst);
- inline QVariantList variantListFromJS(QV4::ArrayObject *array)
- { V8ObjectSet visitedObjects; return variantListFromJS(array, visitedObjects); }
-
QV4::ReturnedValue variantMapToJS(const QVariantMap &vmap);
- inline QVariantMap variantMapFromJS(QV4::Object *object)
- { V8ObjectSet visitedObjects; return variantMapFromJS(object, visitedObjects); }
-
QV4::ReturnedValue variantToJS(const QVariant &value);
- inline QVariant variantFromJS(const QV4::ValueRef value)
- { V8ObjectSet visitedObjects; return variantFromJS(value, visitedObjects); }
QV4::ReturnedValue metaTypeToJS(int type, const void *data);
bool metaTypeFromJS(const QV4::ValueRef value, int type, void *data);
@@ -265,15 +261,9 @@ protected:
QHash<QString, quint32> m_consoleCount;
- QVariant toBasicVariant(const QV4::ValueRef);
-
void initializeGlobal();
private:
- QVariantList variantListFromJS(QV4::ArrayObject *array, V8ObjectSet &visitedObjects);
- QVariantMap variantMapFromJS(QV4::Object *object, V8ObjectSet &visitedObjects);
- QVariant variantFromJS(const QV4::ValueRef value, V8ObjectSet &visitedObjects);
-
Q_DISABLE_COPY(QV8Engine)
};
diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp
index 25879972ca..142625d7ae 100644
--- a/src/qml/types/qqmllistmodel.cpp
+++ b/src/qml/types/qqmllistmodel.cpp
@@ -1330,6 +1330,11 @@ void DynamicRoleModelNode::updateValues(const QVariantMap &object, QVector<int>
QVariant value = object[key];
+ // A JS array/object is translated into a (hierarchical) QQmlListModel,
+ // so translate to a variant map/list first with toVariant().
+ if (value.userType() == qMetaTypeId<QJSValue>())
+ value = value.value<QJSValue>().toVariant();
+
if (value.type() == QVariant::List) {
QQmlListModel *subModel = QQmlListModel::createWithOwner(m_owner);
@@ -1392,6 +1397,12 @@ void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
QQmlListModel *parentModel = m_owner->m_owner;
QVariant v = value(index);
+
+ // A JS array/object is translated into a (hierarchical) QQmlListModel,
+ // so translate to a variant map/list first with toVariant().
+ if (v.userType() == qMetaTypeId<QJSValue>())
+ v= v.value<QJSValue>().toVariant();
+
if (v.type() == QVariant::List) {
QQmlListModel *subModel = QQmlListModel::createWithOwner(parentModel);
diff --git a/src/qml/util/qqmllistaccessor.cpp b/src/qml/util/qqmllistaccessor.cpp
index e434d6cef4..5a199abf44 100644
--- a/src/qml/util/qqmllistaccessor.cpp
+++ b/src/qml/util/qqmllistaccessor.cpp
@@ -61,6 +61,11 @@ void QQmlListAccessor::setList(const QVariant &v, QQmlEngine *engine)
{
d = v;
+ // An incoming JS array as model is treated as a variant list, so we need to
+ // convert it first with toVariant().
+ if (d.userType() == qMetaTypeId<QJSValue>())
+ d = d.value<QJSValue>().toVariant();
+
QQmlEnginePrivate *enginePrivate = engine?QQmlEnginePrivate::get(engine):0;
if (!d.isValid()) {
@@ -73,7 +78,7 @@ void QQmlListAccessor::setList(const QVariant &v, QQmlEngine *engine)
m_type = Integer;
} else if ((!enginePrivate && QQmlMetaType::isQObject(d.userType())) ||
(enginePrivate && enginePrivate->isQObject(d.userType()))) {
- QObject *data = enginePrivate?enginePrivate->toQObject(v):QQmlMetaType::toQObject(v);
+ QObject *data = enginePrivate?enginePrivate->toQObject(d):QQmlMetaType::toQObject(d);
d = QVariant::fromValue(data);
m_type = Instance;
} else if (d.userType() == qMetaTypeId<QQmlListReference>()) {