First step towards porting sequence wrappers to V4

The idea is to wrap sequence container types such as QStringList or
QList<int> in a JS object that behaves like an array and also shares the
prototype.

The next step is to generalize the QStringList implementation to be re-usable
for the other sequence types.

This also required extending the object iterator with support for these
kind-of array types.

Change-Id: I5f0a14f904233944297708037c944964f1b74923
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
This commit is contained in:
Simon Hausmann 2013-05-14 15:59:10 +02:00 committed by Lars Knoll
parent 4f1bfbdd46
commit ba4dfd4290
7 changed files with 221 additions and 8 deletions

View File

@ -50,6 +50,11 @@ QT_BEGIN_NAMESPACE
class QQmlLocaleData;
class QQuickJSContext2D;
class QQmlIntList;
class QQmlRealList;
class QQmlBoolList;
class QQmlStringList;
class QQmlUrlList;
namespace QV4 {
@ -165,7 +170,14 @@ public:
// QML bindings
Type_QmlLocale,
Type_QQuickJSContext2D
Type_QQuickJSContext2D,
// QML sequence types
Type_QmlIntList,
Type_QmlRealList,
Type_QmlBoolList,
Type_QmlStringList,
Type_QmlUrlList
};
ExecutionEngine *engine() const;
@ -184,9 +196,19 @@ public:
JSONObject *asJSONObject() { return type == Type_JSONObject ? reinterpret_cast<JSONObject *>(this) : 0; }
ForeachIteratorObject *asForeachIteratorObject() { return type == Type_ForeachIteratorObject ? reinterpret_cast<ForeachIteratorObject *>(this) : 0; }
RegExp *asRegExp() { return type == Type_RegExp ? reinterpret_cast<RegExp *>(this) : 0; }
QQmlLocaleData *asQmlLocale() { return type == Type_QmlLocale ? reinterpret_cast<QQmlLocaleData *>(this) : 0; }
QQuickJSContext2D *asQQuickJSContext2D() { return type == Type_QQuickJSContext2D ? reinterpret_cast<QQuickJSContext2D *>(this) : 0; }
QQmlIntList *asQmlIntList() { return type == Type_QmlIntList ? reinterpret_cast<QQmlIntList *>(this): 0; }
QQmlRealList *asQmlRealList() { return type == Type_QmlRealList ? reinterpret_cast<QQmlRealList *>(this): 0; }
QQmlBoolList *asQmlBoolList() { return type == Type_QmlBoolList ? reinterpret_cast<QQmlBoolList *>(this): 0; }
QQmlStringList *asQmlStringList() { return type == Type_QmlStringList ? reinterpret_cast<QQmlStringList *>(this): 0; }
QQmlUrlList *asQmlUrlList() { return type == Type_QmlUrlList ? reinterpret_cast<QQmlUrlList *>(this): 0; }
bool isListType() const { return type >= Type_QmlIntList && type <= Type_QmlUrlList; }
bool isArrayObject() const { return type == Type_ArrayObject; }
bool isStringObject() const { return type == Type_StringObject; }

View File

@ -55,6 +55,7 @@ ObjectIterator::ObjectIterator(Object *o, uint flags)
, flags(flags)
, dynamicProperties(0)
, dynamicPropertyIndex(0)
, wrappedListLength(0)
{
tmpDynamicProperty.value = Value::undefinedValue();
if (current) {
@ -62,6 +63,11 @@ ObjectIterator::ObjectIterator(Object *o, uint flags)
this->flags |= CurrentIsString;
if (current->dynamicPropertyEnumerator)
dynamicProperties = current->dynamicPropertyEnumerator(current).asArrayObject();
if (current->isListType()) {
wrappedListLength = current->get(o->engine()->id_length).toUInt32();
assert(current->arrayDataLen == 0);
}
}
}
@ -128,6 +134,18 @@ Property *ObjectIterator::next(String **name, uint *index, PropertyAttributes *a
}
}
while (arrayIndex < wrappedListLength) {
PropertyAttributes a = current->vtbl->queryIndexed(current, current->engine()->current, arrayIndex);
++arrayIndex;
if (!(flags & EnumerableOnly) || a.isEnumerable()) {
*index = arrayIndex - 1;
if (attrs)
*attrs = a;
tmpDynamicProperty.value = current->getIndexed(*index);
return &tmpDynamicProperty;
}
}
if (dynamicProperties) {
const int len = dynamicProperties->arrayLength();
while (dynamicPropertyIndex < len) {
@ -168,6 +186,11 @@ Property *ObjectIterator::next(String **name, uint *index, PropertyAttributes *a
if (current && current->dynamicPropertyEnumerator)
dynamicProperties = current->dynamicPropertyEnumerator(current).asArrayObject();
dynamicPropertyIndex = 0;
if (current && current->isListType()) {
wrappedListLength = current->get(current->engine()->id_length).toUInt32();
assert(current->arrayDataLen == 0);
}
continue;
}
String *n = internalClass->nameMap.at(memberIndex);

View File

@ -78,6 +78,7 @@ struct ObjectIterator
ArrayObject *dynamicProperties;
uint dynamicPropertyIndex;
Property tmpDynamicProperty;
uint wrappedListLength;
ObjectIterator(Object *o, uint flags);
Property *next(String **name, uint *index, PropertyAttributes *attributes = 0);

View File

@ -202,7 +202,7 @@ QVariant QV8Engine::toVariant(const QV4::Value &value, int typeHint)
if (typeHint == qMetaTypeId<QJSValue>())
return QVariant::fromValue(scriptValueFromInternal(value));
if (value.isObject()) {
if (QV4::Object *object = value.asObject()) {
QV8ObjectResource *r = (QV8ObjectResource *)v8::Handle<v8::Value>(value)->ToObject()->GetExternalResource();
if (r) {
switch (r->resourceType()) {
@ -235,7 +235,8 @@ QVariant QV8Engine::toVariant(const QV4::Value &value, int typeHint)
} else if (typeHint == QMetaType::QJsonObject
&& !value.asArrayObject() && !value.asFunctionObject()) {
return QVariant::fromValue(jsonObjectFromJS(value));
}
} else if (object->isListType())
return m_sequenceWrapper.toVariant(object);
}
if (QV4::ArrayObject *a = value.asArrayObject()) {

View File

@ -113,7 +113,11 @@ void QV8SequenceWrapper::destroy()
bool QV8SequenceWrapper::isSequenceType(int sequenceTypeId) const
{
FOREACH_QML_SEQUENCE_TYPE(IS_SEQUENCE) { /* else */ return false; }
FOREACH_QML_SEQUENCE_TYPE(IS_SEQUENCE)
if (sequenceTypeId == qMetaTypeId<QStringList>()) {
return true;
} else
{ /* else */ return false; }
}
#undef IS_SEQUENCE
@ -140,13 +144,18 @@ quint32 QV8SequenceWrapper::sequenceLength(QV8ObjectResource *r)
v8::Handle<v8::Object> QV8SequenceWrapper::newSequence(int sequenceType, QObject *object, int propertyIndex, bool *succeeded)
{
QV4::ExecutionEngine *v4 = QV8Engine::getV4(m_engine);
// This function is called when the property is a QObject Q_PROPERTY of
// the given sequence type. Internally we store a typed-sequence
// (as well as object ptr + property index for updated-read and write-back)
// and so access/mutate avoids variant conversion.
*succeeded = true;
QV8SequenceResource *r = 0;
FOREACH_QML_SEQUENCE_TYPE(NEW_REFERENCE_SEQUENCE) { /* else */ *succeeded = false; return v8::Handle<v8::Object>(); }
FOREACH_QML_SEQUENCE_TYPE(NEW_REFERENCE_SEQUENCE)
if (sequenceType == qMetaTypeId<QStringList>()) {
return QV4::Value::fromObject(new (v4->memoryManager) QQmlStringList(v4, object, propertyIndex));
} else { /* else */ *succeeded = false; return v8::Handle<v8::Object>(); }
v8::Handle<v8::Object> rv = m_constructor.value().asFunctionObject()->newInstance();
rv->SetExternalResource(r);
@ -162,6 +171,8 @@ v8::Handle<v8::Object> QV8SequenceWrapper::newSequence(int sequenceType, QObject
v8::Handle<v8::Object> QV8SequenceWrapper::fromVariant(const QVariant& v, bool *succeeded)
{
QV4::ExecutionEngine *v4 = QV8Engine::getV4(m_engine);
// This function is called when assigning a sequence value to a normal JS var
// in a JS block. Internally, we store a sequence of the specified type.
// Access and mutation is extremely fast since it will not need to modify any
@ -169,7 +180,10 @@ v8::Handle<v8::Object> QV8SequenceWrapper::fromVariant(const QVariant& v, bool *
int sequenceType = v.userType();
*succeeded = true;
QV8SequenceResource *r = 0;
FOREACH_QML_SEQUENCE_TYPE(NEW_COPY_SEQUENCE) { /* else */ *succeeded = false; return v8::Handle<v8::Object>(); }
FOREACH_QML_SEQUENCE_TYPE(NEW_COPY_SEQUENCE)
if (sequenceType == qMetaTypeId<QStringList>()) { \
return QV4::Value::fromObject(new (v4->memoryManager) QQmlStringList(v4, v.value<QStringList>()));
} else { /* else */ *succeeded = false; return v8::Handle<v8::Object>(); }
v8::Handle<v8::Object> rv = m_constructor.value().asFunctionObject()->newInstance();
rv->SetExternalResource(r);
@ -185,6 +199,14 @@ QVariant QV8SequenceWrapper::toVariant(QV8ObjectResource *r)
return resource->toVariant();
}
QVariant QV8SequenceWrapper::toVariant(QV4::Object *object)
{
Q_ASSERT(object->isListType());
if (QQmlStringList *list = object->asQmlStringList())
return list->toVariant();
return QVariant();
}
#define SEQUENCE_TO_VARIANT(ElementType, ElementTypeName, SequenceType, unused) \
if (typeHint == qMetaTypeId<SequenceType>()) { \
return QV8##ElementTypeName##SequenceResource::toVariant(m_engine, array, length, succeeded); \
@ -194,7 +216,15 @@ QVariant QV8SequenceWrapper::toVariant(v8::Handle<v8::Array> array, int typeHint
{
*succeeded = true;
uint32_t length = array->Length();
FOREACH_QML_SEQUENCE_TYPE(SEQUENCE_TO_VARIANT) { /* else */ *succeeded = false; return QVariant(); }
FOREACH_QML_SEQUENCE_TYPE(SEQUENCE_TO_VARIANT)
if (typeHint == qMetaTypeId<QStringList>()) { \
QV4::ArrayObject *a = array->v4Value().asArrayObject();
if (!a) {
*succeeded = false;
return QVariant();
}
return QVariant::fromValue(a->toQStringList());
} else { /* else */ *succeeded = false; return QVariant(); }
}
#undef SEQUENCE_TO_VARIANT

View File

@ -82,6 +82,7 @@ public:
v8::Handle<v8::Object> newSequence(int sequenceTypeId, QObject *object, int propertyIndex, bool *succeeded);
v8::Handle<v8::Object> fromVariant(const QVariant& v, bool *succeeded);
QVariant toVariant(QV8ObjectResource *);
QVariant toVariant(QV4::Object *object);
QVariant toVariant(v8::Handle<v8::Array> array, int typeHint, bool *succeeded);
private:

View File

@ -56,6 +56,8 @@
#include <private/qqmlengine_p.h>
#include <private/qqmlmetatype_p.h>
#include <private/qv4arrayobject_p.h>
QT_BEGIN_NAMESPACE
/*!
@ -239,7 +241,6 @@ static QString convertUrlToString(QV8Engine *, const QUrl &v)
F(qreal, Real, QList<qreal>, 0.0) \
F(bool, Bool, QList<bool>, false) \
F(QString, String, QList<QString>, QString()) \
F(QString, QString, QStringList, QString()) \
F(QUrl, Url, QList<QUrl>, QUrl())
#define QML_SEQUENCE_TYPE_RESOURCE(SequenceElementType, SequenceElementTypeName, SequenceType, DefaultValue, ConversionToV8fn, ConversionFromV8fn, ToStringfn) \
@ -507,6 +508,140 @@ FOREACH_QML_SEQUENCE_TYPE(GENERATE_QML_SEQUENCE_TYPE_RESOURCE)
#undef GENERATE_QML_SEQUENCE_TYPE_RESOURCE
#undef QML_SEQUENCE_TYPE_RESOURCE
class QQmlStringList : public QV4::Object
{
public:
QQmlStringList(QV4::ExecutionEngine *engine, const QStringList &container)
: QV4::Object(engine)
, m_container(container)
{
type = Type_QmlStringList;
vtbl = &static_vtbl;
m_lengthProperty = insertMember(engine->id_length, QV4::Attr_ReadOnly);
prototype = engine->arrayPrototype;
updateLength();
}
QQmlStringList(QV4::ExecutionEngine *engine, QObject *object, int propertyIndex)
: QV4::Object(engine)
{
type = Type_QmlStringList;
vtbl = &static_vtbl;
m_lengthProperty = insertMember(engine->id_length, QV4::Attr_ReadOnly);
prototype = engine->arrayPrototype;
void *a[] = { &m_container, 0 };
QMetaObject::metacall(object, QMetaObject::ReadProperty, propertyIndex, a);
updateLength();
}
QV4::Value containerGetIndexed(QV4::ExecutionContext *ctx, uint index, bool *hasProperty)
{
/* Qt containers have int (rather than uint) allowable indexes. */
if (index > INT_MAX) {
generateWarning(QV8Engine::get(ctx->engine->publicEngine), QLatin1String("Index out of range during indexed get"));
if (hasProperty)
*hasProperty = false;
return QV4::Value::undefinedValue();
}
qint32 signedIdx = static_cast<qint32>(index);
if (signedIdx < m_container.count()) {
if (hasProperty)
*hasProperty = true;
return QV4::Value::fromString(ctx, m_container.at(signedIdx));
}
if (hasProperty)
*hasProperty = false;
return QV4::Value::undefinedValue();
}
void containerPutIndexed(QV4::ExecutionContext *ctx, uint index, const QV4::Value &value)
{
/* Qt containers have int (rather than uint) allowable indexes. */
if (index > INT_MAX) {
generateWarning(QV8Engine::get(ctx->engine->publicEngine), QLatin1String("Index out of range during indexed put"));
return;
}
qint32 signedIdx = static_cast<qint32>(index);
int count = m_container.count();
QString element = value.toQString();
if (signedIdx == count) {
m_container.append(element);
updateLength();
} else if (signedIdx < count) {
m_container[signedIdx] = element;
} else {
/* according to ECMA262r3 we need to insert */
/* the value at the given index, increasing length to index+1. */
m_container.reserve(signedIdx + 1);
while (signedIdx > count++) {
m_container.append(QString());
}
m_container.append(element);
updateLength();
}
}
QV4::PropertyAttributes containerQueryIndexed(QV4::ExecutionContext *ctx, uint index)
{
/* Qt containers have int (rather than uint) allowable indexes. */
if (index > INT_MAX) {
generateWarning(QV8Engine::get(ctx->engine->publicEngine), QLatin1String("Index out of range during indexed query"));
return QV4::Attr_Invalid;
}
qint32 signedIdx = static_cast<qint32>(index);
return (index < m_container.count()) ? QV4::Attr_Data : QV4::Attr_Invalid;
}
bool containerDeleteIndexedProperty(QV4::ExecutionContext *ctx, uint index)
{
/* Qt containers have int (rather than uint) allowable indexes. */
if (index > INT_MAX)
return false;
qint32 signedIdx = static_cast<qint32>(index);
if (signedIdx >= m_container.count())
return false;
/* according to ECMA262r3 it should be Undefined, */
/* but we cannot, so we insert a default-value instead. */
m_container.replace(signedIdx, QString());
return true;
}
QVariant toVariant() const
{ return QVariant::fromValue<QStringList>(m_container); }
private:
void updateLength()
{
m_lengthProperty->value = QV4::Value::fromInt32(m_container.length());
}
QStringList m_container;
QV4::Property *m_lengthProperty;
static QV4::Value getIndexed(QV4::Managed *that, QV4::ExecutionContext *ctx, uint index, bool *hasProperty)
{ return static_cast<QQmlStringList *>(that)->containerGetIndexed(ctx, index, hasProperty); }
static void putIndexed(Managed *that, QV4::ExecutionContext *ctx, uint index, const QV4::Value &value)
{ static_cast<QQmlStringList *>(that)->containerPutIndexed(ctx, index, value); }
static QV4::PropertyAttributes queryIndexed(QV4::Managed *that, QV4::ExecutionContext *ctx, uint index)
{ return static_cast<QQmlStringList *>(that)->containerQueryIndexed(ctx, index); }
static bool deleteIndexedProperty(QV4::Managed *that, QV4::ExecutionContext *ctx, uint index)
{ return static_cast<QQmlStringList *>(that)->containerDeleteIndexedProperty(ctx, index); }
static void destroy(Managed *that)
{
static_cast<QQmlStringList *>(that)->~QQmlStringList();
}
static const QV4::ManagedVTable static_vtbl;
};
DEFINE_MANAGED_VTABLE(QQmlStringList);
QT_END_NAMESPACE
#endif // QV8SEQUENCEWRAPPER_P_P_H