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:
parent
4f1bfbdd46
commit
ba4dfd4290
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue