/**************************************************************************** ** ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/ ** ** This file is part of the QtQml module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qv8qobjectwrapper_p.h" #include "qv8contextwrapper_p.h" #include "qv8engine_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QJSValue); Q_DECLARE_METATYPE(QQmlV8Handle); QT_BEGIN_NAMESPACE #if defined(__GNUC__) && !defined(__INTEL_COMPILER) # if (__GNUC__ * 100 + __GNUC_MINOR__) >= 405 // The code in this file does not violate strict aliasing, but GCC thinks it does // so turn off the warnings for us to have a clean build # pragma GCC diagnostic ignored "-Wstrict-aliasing" # endif #endif #define QOBJECT_TOSTRING_INDEX -2 #define QOBJECT_DESTROY_INDEX -3 // XXX TODO: Need to review all calls to QQmlEngine *engine() to confirm QObjects work // correctly in a worker thread class QV8QObjectInstance : public QQmlGuard { public: QV8QObjectInstance(QObject *o, QV8QObjectWrapper *w) : QQmlGuard(o), wrapper(w) { } ~QV8QObjectInstance() { qPersistentDispose(v8object); } virtual void objectDestroyed(QObject *o) { if (wrapper) wrapper->m_taintedObjects.remove(o); delete this; } v8::Persistent v8object; QV8QObjectWrapper *wrapper; }; class QV8SignalHandlerResource : public QV8ObjectResource { V8_RESOURCE_TYPE(SignalHandlerType) public: QV8SignalHandlerResource(QV8Engine *engine, QObject *object, int index); QQmlGuard object; int index; }; namespace { template class MaxSizeOf8 { template struct SMax { char dummy[sizeof(Z) > sizeof(X) ? sizeof(Z) : sizeof(X)]; }; public: static const size_t Size = sizeof(SMax > > > > > >); }; struct CallArgument { inline CallArgument(); inline ~CallArgument(); inline void *dataPtr(); inline void initAsType(int type); inline void fromValue(int type, QV8Engine *, v8::Handle); inline v8::Handle toValue(QV8Engine *); private: CallArgument(const CallArgument &); inline void cleanup(); union { float floatValue; double doubleValue; quint32 intValue; bool boolValue; QObject *qobjectPtr; char allocData[MaxSizeOf8, QJSValue, QQmlV8Handle, QJsonArray, QJsonObject, QJsonValue>::Size]; qint64 q_for_alignment; }; // Pointers to allocData union { QString *qstringPtr; QVariant *qvariantPtr; QList *qlistPtr; QJSValue *qjsValuePtr; QQmlV8Handle *handlePtr; QJsonArray *jsonArrayPtr; QJsonObject *jsonObjectPtr; QJsonValue *jsonValuePtr; }; int type; }; } QV8QObjectResource::QV8QObjectResource(QV8Engine *engine, QObject *object) : QV8ObjectResource(engine), object(object) { } QV8SignalHandlerResource::QV8SignalHandlerResource(QV8Engine *engine, QObject *object, int index) : QV8ObjectResource(engine), object(object), index(index) { } static QAtomicInt objectIdCounter(1); QV8QObjectWrapper::QV8QObjectWrapper() : m_engine(0), m_id(objectIdCounter.fetchAndAddOrdered(1)) { } QV8QObjectWrapper::~QV8QObjectWrapper() { for (TaintedHash::Iterator iter = m_taintedObjects.begin(); iter != m_taintedObjects.end(); ++iter) { (*iter)->wrapper = 0; } m_taintedObjects.clear(); } void QV8QObjectWrapper::destroy() { qDeleteAll(m_connections); m_connections.clear(); qPersistentDispose(m_hiddenObject); qPersistentDispose(m_destroySymbol); qPersistentDispose(m_toStringSymbol); qPersistentDispose(m_signalHandlerConstructor); qPersistentDispose(m_methodConstructor); qPersistentDispose(m_constructor); QIntrusiveList::iterator i = m_javaScriptOwnedWeakQObjects.begin(); for (; i != m_javaScriptOwnedWeakQObjects.end(); ++i) { QV8QObjectResource *resource = *i; Q_ASSERT(resource); deleteWeakQObject(resource, true); } } struct ReadAccessor { static inline void Indirect(QObject *object, const QQmlPropertyData &property, void *output, QQmlNotifier **n) { Q_ASSERT(n == 0); Q_UNUSED(n); void *args[] = { output, 0 }; QMetaObject::metacall(object, QMetaObject::ReadProperty, property.coreIndex, args); } static inline void Direct(QObject *object, const QQmlPropertyData &property, void *output, QQmlNotifier **n) { Q_ASSERT(n == 0); Q_UNUSED(n); void *args[] = { output, 0 }; object->qt_metacall(QMetaObject::ReadProperty, property.coreIndex, args); } static inline void Accessor(QObject *object, const QQmlPropertyData &property, void *output, QQmlNotifier **n) { Q_ASSERT(property.accessors); property.accessors->read(object, property.accessorData, output); if (n) property.accessors->notifier(object, property.accessorData, n); } }; static inline v8::Handle valueToHandle(QV8Engine *, int v) { return v8::Integer::New(v); } static inline v8::Handle valueToHandle(QV8Engine *, uint v) { return v8::Integer::NewFromUnsigned(v); } static inline v8::Handle valueToHandle(QV8Engine *, bool v) { return v8::Boolean::New(v); } static inline v8::Handle valueToHandle(QV8Engine *e, const QString &v) { return e->toString(v); } static inline v8::Handle valueToHandle(QV8Engine *, float v) { return v8::Number::New(v); } static inline v8::Handle valueToHandle(QV8Engine *, double v) { return v8::Number::New(v); } static inline v8::Handle valueToHandle(QV8Engine *e, QObject *v) { return e->newQObject(v); } template static v8::Handle GenericValueGetter(v8::Local, const v8::AccessorInfo &info) { v8::Handle This = info.This(); QV8QObjectResource *resource = v8_resource_check(This); QObject *object = resource->object; if (QQmlData::wasDeleted(object)) return v8::Undefined(); QQmlPropertyData *property = (QQmlPropertyData *)v8::External::Unwrap(info.Data()); QQmlEngine *engine = resource->engine->engine(); QQmlEnginePrivate *ep = engine?QQmlEnginePrivate::get(engine):0; T value = T(); if (ep && ep->propertyCapture) { if (ReadFunction == ReadAccessor::Accessor && property->accessors->notifier) { QQmlNotifier *notifier = 0; ReadFunction(object, *property, &value, ¬ifier); if (notifier) ep->captureProperty(notifier); } else if (!property->isConstant()) { ep->captureProperty(object, property->coreIndex, property->notifyIndex); ReadFunction(object, *property, &value, 0); } else { ReadFunction(object, *property, &value, 0); } } else { ReadFunction(object, *property, &value, 0); } return valueToHandle(resource->engine, value); } #define FAST_GETTER_FUNCTION(property, cpptype) \ (property->hasAccessors()?((v8::AccessorGetter)GenericValueGetter):(property->isDirect()?((v8::AccessorGetter)GenericValueGetter):((v8::AccessorGetter)GenericValueGetter))) static quint32 toStringHash = quint32(-1); static quint32 destroyHash = quint32(-1); void QV8QObjectWrapper::init(QV8Engine *engine) { m_engine = engine; m_toStringSymbol = qPersistentNew(v8::String::NewSymbol("toString")); m_destroySymbol = qPersistentNew(v8::String::NewSymbol("destroy")); m_hiddenObject = qPersistentNew(v8::Object::New()); m_toStringString = QHashedV8String(m_toStringSymbol); m_destroyString = QHashedV8String(m_destroySymbol); toStringHash = m_toStringString.hash(); destroyHash = m_destroyString.hash(); { v8::Local ft = v8::FunctionTemplate::New(); ft->InstanceTemplate()->SetFallbackPropertyHandler(Getter, Setter, Query, 0, Enumerator); ft->InstanceTemplate()->SetHasExternalResource(true); m_constructor = qPersistentNew(ft->GetFunction()); } { v8::ScriptOrigin origin(m_hiddenObject); // Hack to allow us to identify these functions #define CREATE_FUNCTION_SOURCE \ "(function(method) { "\ "return (function(object, data, qmlglobal) { "\ "return (function() { "\ "return method(object, data, qmlglobal, arguments.length, arguments); "\ "});"\ "});"\ "})" v8::Local script = v8::Script::New(v8::String::New(CREATE_FUNCTION_SOURCE), &origin, 0, v8::Handle(), v8::Script::NativeMode); #undef CREATE_FUNCTION_SOURCE v8::Local fn = v8::Local::Cast(script->Run()); v8::Handle invokeFn = v8::FunctionTemplate::New(Invoke)->GetFunction(); v8::Handle args[] = { invokeFn }; v8::Local createFn = v8::Local::Cast(fn->Call(engine->global(), 1, args)); m_methodConstructor = qPersistentNew(createFn); } v8::Local connect = V8FUNCTION(Connect, engine); v8::Local disconnect = V8FUNCTION(Disconnect, engine); { v8::Local ft = v8::FunctionTemplate::New(); ft->InstanceTemplate()->SetHasExternalResource(true); ft->PrototypeTemplate()->Set(v8::String::New("connect"), connect, v8::DontEnum); ft->PrototypeTemplate()->Set(v8::String::New("disconnect"), disconnect, v8::DontEnum); m_signalHandlerConstructor = qPersistentNew(ft->GetFunction()); } { v8::Local prototype = engine->global()->Get(v8::String::New("Function"))->ToObject()->Get(v8::String::New("prototype"))->ToObject(); prototype->Set(v8::String::New("connect"), connect, v8::DontEnum); prototype->Set(v8::String::New("disconnect"), disconnect, v8::DontEnum); } } bool QV8QObjectWrapper::isQObject(v8::Handle obj) { return v8_resource_cast(obj) != 0; } QObject *QV8QObjectWrapper::toQObject(v8::Handle obj) { QV8QObjectResource *r = v8_resource_cast(obj); return r?r->object:0; } // r *MUST* be a QV8ObjectResource (r->type() == QV8ObjectResource::QObjectType) QObject *QV8QObjectWrapper::toQObject(QV8ObjectResource *r) { Q_ASSERT(r->resourceType() == QV8ObjectResource::QObjectType); return static_cast(r)->object; } // Load value properties template static v8::Handle LoadProperty(QV8Engine *engine, QObject *object, const QQmlPropertyData &property, QQmlNotifier **notifier) { Q_ASSERT(!property.isFunction()); if (property.isQObject()) { QObject *rv = 0; ReadFunction(object, property, &rv, notifier); return engine->newQObject(rv); } else if (property.isQList()) { return engine->listWrapper()->newList(object, property.coreIndex, property.propType); } else if (property.propType == QMetaType::QReal) { qreal v = 0; ReadFunction(object, property, &v, notifier); return valueToHandle(engine, v); } else if (property.propType == QMetaType::Int || property.isEnum()) { int v = 0; ReadFunction(object, property, &v, notifier); return valueToHandle(engine, v); } else if (property.propType == QMetaType::Bool) { bool v = false; ReadFunction(object, property, &v, notifier); return valueToHandle(engine, v); } else if (property.propType == QMetaType::QString) { QString v; ReadFunction(object, property, &v, notifier); return valueToHandle(engine, v); } else if (property.propType == QMetaType::UInt) { uint v = 0; ReadFunction(object, property, &v, notifier); return valueToHandle(engine, v); } else if (property.propType == QMetaType::Float) { float v = 0; ReadFunction(object, property, &v, notifier); return valueToHandle(engine, v); } else if (property.propType == QMetaType::Double) { double v = 0; ReadFunction(object, property, &v, notifier); return valueToHandle(engine, v); } else if (property.isV8Handle()) { QQmlV8Handle handle; ReadFunction(object, property, &handle, notifier); return handle.toHandle(); } else if (property.propType == qMetaTypeId()) { QJSValue v; ReadFunction(object, property, &v, notifier); return QJSValuePrivate::get(v)->asV8Value(engine); } else if (property.isQVariant()) { QVariant v; ReadFunction(object, property, &v, notifier); if (QQmlValueTypeFactory::isValueType(v.userType())) { if (QQmlValueType *valueType = QQmlValueTypeFactory::valueType(v.userType())) return engine->newValueType(object, property.coreIndex, valueType); // VariantReference value-type. } return engine->fromVariant(v); } else if (QQmlValueTypeFactory::isValueType(property.propType)) { Q_ASSERT(notifier == 0); if (QQmlValueType *valueType = QQmlValueTypeFactory::valueType(property.propType)) return engine->newValueType(object, property.coreIndex, valueType); } else { Q_ASSERT(notifier == 0); // see if it's a sequence type bool succeeded = false; v8::Handle retn = engine->newSequence(property.propType, object, property.coreIndex, &succeeded); if (succeeded) return retn; } if (property.propType == QMetaType::UnknownType) { QMetaProperty p = object->metaObject()->property(property.coreIndex); qWarning("QMetaProperty::read: Unable to handle unregistered datatype '%s' for property " "'%s::%s'", p.typeName(), object->metaObject()->className(), p.name()); return v8::Undefined(); } else { QVariant v(property.propType, (void *)0); ReadFunction(object, property, v.data(), notifier); return engine->fromVariant(v); } } v8::Handle QV8QObjectWrapper::GetProperty(QV8Engine *engine, QObject *object, v8::Handle *objectHandle, const QHashedV8String &property, QV8QObjectWrapper::RevisionMode revisionMode) { // XXX More recent versions of V8 introduced "Callable" objects. It is possible that these // will be a faster way of creating QObject method objects. struct MethodClosure { static v8::Handle create(QV8Engine *engine, QObject *object, v8::Handle *objectHandle, int index) { v8::Handle argv[] = { objectHandle?*objectHandle:engine->newQObject(object), v8::Integer::New(index) }; Q_ASSERT(argv[0]->IsObject()); return engine->qobjectWrapper()->m_methodConstructor->Call(engine->global(), 2, argv); } static v8::Handle createWithGlobal(QV8Engine *engine, QObject *object, v8::Handle *objectHandle, int index) { v8::Handle argv[] = { objectHandle?*objectHandle:engine->newQObject(object), v8::Integer::New(index), v8::Context::GetCallingQmlGlobal() }; Q_ASSERT(argv[0]->IsObject()); return engine->qobjectWrapper()->m_methodConstructor->Call(engine->global(), 3, argv); } }; if (QQmlData::wasDeleted(object)) return v8::Handle(); { // Comparing the hash first actually makes a measurable difference here, at least on x86 quint32 hash = property.hash(); if (hash == toStringHash && engine->qobjectWrapper()->m_toStringString == property) { return MethodClosure::create(engine, object, objectHandle, QOBJECT_TOSTRING_INDEX); } else if (hash == destroyHash && engine->qobjectWrapper()->m_destroyString == property) { return MethodClosure::create(engine, object, objectHandle, QOBJECT_DESTROY_INDEX); } } QQmlPropertyData local; QQmlPropertyData *result = 0; { QQmlData *ddata = QQmlData::get(object, false); if (ddata && ddata->propertyCache) result = ddata->propertyCache->property(property); else result = QQmlPropertyCache::property(engine->engine(), object, property, local); } if (!result) return v8::Handle(); if (revisionMode == QV8QObjectWrapper::CheckRevision && result->hasRevision()) { QQmlData *ddata = QQmlData::get(object); if (ddata && ddata->propertyCache && !ddata->propertyCache->isAllowedInRevision(result)) return v8::Handle(); } if (result->isFunction() && !result->isVarProperty()) { if (result->isVMEFunction()) { QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(object); Q_ASSERT(vmemo); return vmemo->vmeMethod(result->coreIndex); } else if (result->isV8Function()) { return MethodClosure::createWithGlobal(engine, object, objectHandle, result->coreIndex); } else if (result->isSignalHandler()) { v8::Local handler = engine->qobjectWrapper()->m_signalHandlerConstructor->NewInstance(); QV8SignalHandlerResource *r = new QV8SignalHandlerResource(engine, object, result->coreIndex); handler->SetExternalResource(r); return handler; } else { return MethodClosure::create(engine, object, objectHandle, result->coreIndex); } } QQmlEnginePrivate *ep = engine->engine()?QQmlEnginePrivate::get(engine->engine()):0; if (result->hasAccessors()) { QQmlNotifier *n = 0; QQmlNotifier **nptr = 0; if (ep && ep->propertyCapture && result->accessors->notifier) nptr = &n; v8::Handle rv = LoadProperty(engine, object, *result, nptr); if (result->accessors->notifier) { if (n) ep->captureProperty(n); } else { ep->captureProperty(object, result->coreIndex, result->notifyIndex); } return rv; } if (ep && !result->isConstant()) ep->captureProperty(object, result->coreIndex, result->notifyIndex); if (result->isVarProperty()) { QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(object); Q_ASSERT(vmemo); return vmemo->vmeProperty(result->coreIndex); } else if (result->isDirect()) { return LoadProperty(engine, object, *result, 0); } else { return LoadProperty(engine, object, *result, 0); } } // Setter for writable properties. Shared between the interceptor and fast property accessor static inline void StoreProperty(QV8Engine *engine, QObject *object, QQmlPropertyData *property, v8::Handle value) { QQmlBinding *newBinding = 0; if (value->IsFunction()) { if (value->ToObject()->GetHiddenValue(engine->bindingFlagKey()).IsEmpty()) { if (!property->isVarProperty() && property->propType != qMetaTypeId()) { // assigning a JS function to a non var or QJSValue property or is not allowed. QString error = QLatin1String("Cannot assign JavaScript function to "); if (!QMetaType::typeName(property->propType)) error += QLatin1String("[unknown property type]"); else error += QLatin1String(QMetaType::typeName(property->propType)); v8::ThrowException(v8::Exception::Error(engine->toString(error))); return; } } else { // binding assignment. QQmlContextData *context = engine->callingContext(); v8::Handle function = v8::Handle::Cast(value); v8::Local trace = v8::StackTrace::CurrentStackTrace(1, (v8::StackTrace::StackTraceOptions)(v8::StackTrace::kLineNumber | v8::StackTrace::kScriptName)); v8::Local frame = trace->GetFrame(0); int lineNumber = frame->GetLineNumber(); int columnNumber = frame->GetColumn(); QString url = engine->toString(frame->GetScriptName()); newBinding = new QQmlBinding(&function, object, context, url, lineNumber, columnNumber); newBinding->setTarget(object, *property, context); newBinding->setEvaluateFlags(newBinding->evaluateFlags() | QQmlBinding::RequiresThisObject); } } QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::setBinding(object, property->coreIndex, -1, newBinding); if (oldBinding) oldBinding->destroy(); if (!newBinding && property->isVarProperty()) { // allow assignment of "special" values (null, undefined, function) to var properties QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(object); Q_ASSERT(vmemo); vmemo->setVMEProperty(property->coreIndex, value); return; } #define PROPERTY_STORE(cpptype, value) \ cpptype o = value; \ int status = -1; \ int flags = 0; \ void *argv[] = { &o, 0, &status, &flags }; \ QMetaObject::metacall(object, QMetaObject::WriteProperty, property->coreIndex, argv); if (value->IsNull() && property->isQObject()) { PROPERTY_STORE(QObject*, 0); } else if (value->IsUndefined() && property->isResettable()) { void *a[] = { 0 }; QMetaObject::metacall(object, QMetaObject::ResetProperty, property->coreIndex, a); } else if (value->IsUndefined() && property->propType == qMetaTypeId()) { PROPERTY_STORE(QVariant, QVariant()); } else if (value->IsUndefined() && property->propType == QMetaType::QJsonValue) { PROPERTY_STORE(QJsonValue, QJsonValue(QJsonValue::Undefined)); } else if (!newBinding && property->propType == qMetaTypeId()) { PROPERTY_STORE(QJSValue, engine->scriptValueFromInternal(value)); } else if (value->IsUndefined()) { QString error = QLatin1String("Cannot assign [undefined] to "); if (!QMetaType::typeName(property->propType)) error += QLatin1String("[unknown property type]"); else error += QLatin1String(QMetaType::typeName(property->propType)); v8::ThrowException(v8::Exception::Error(engine->toString(error))); } else if (value->IsFunction()) { // this is handled by the binding creation above } else if (property->propType == QMetaType::Int && value->IsNumber()) { PROPERTY_STORE(int, qRound(value->ToNumber()->Value())); } else if (property->propType == QMetaType::QReal && value->IsNumber()) { PROPERTY_STORE(qreal, qreal(value->ToNumber()->Value())); } else if (property->propType == QMetaType::Float && value->IsNumber()) { PROPERTY_STORE(float, float(value->ToNumber()->Value())); } else if (property->propType == QMetaType::Double && value->IsNumber()) { PROPERTY_STORE(double, double(value->ToNumber()->Value())); } else if (property->propType == QMetaType::QString && value->IsString()) { PROPERTY_STORE(QString, engine->toString(value->ToString())); } else if (property->isVarProperty()) { QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(object); Q_ASSERT(vmemo); vmemo->setVMEProperty(property->coreIndex, value); } else { QVariant v; if (property->isQList()) v = engine->toVariant(value, qMetaTypeId >()); else v = engine->toVariant(value, property->propType); QQmlContextData *context = engine->callingContext(); if (!QQmlPropertyPrivate::write(object, *property, v, context)) { const char *valueType = 0; if (v.userType() == QVariant::Invalid) valueType = "null"; else valueType = QMetaType::typeName(v.userType()); const char *targetTypeName = QMetaType::typeName(property->propType); if (!targetTypeName) targetTypeName = "an unregistered type"; QString error = QLatin1String("Cannot assign ") + QLatin1String(valueType) + QLatin1String(" to ") + QLatin1String(targetTypeName); v8::ThrowException(v8::Exception::Error(engine->toString(error))); } } } bool QV8QObjectWrapper::SetProperty(QV8Engine *engine, QObject *object, const QHashedV8String &property, v8::Handle value, QV8QObjectWrapper::RevisionMode revisionMode) { if (engine->qobjectWrapper()->m_toStringString == property || engine->qobjectWrapper()->m_destroyString == property) return true; if (QQmlData::wasDeleted(object)) return false; QQmlPropertyData local; QQmlPropertyData *result = 0; result = QQmlPropertyCache::property(engine->engine(), object, property, local); if (!result) return false; if (revisionMode == QV8QObjectWrapper::CheckRevision && result->hasRevision()) { QQmlData *ddata = QQmlData::get(object); if (ddata && ddata->propertyCache && !ddata->propertyCache->isAllowedInRevision(result)) return false; } if (!result->isWritable() && !result->isQList()) { QString error = QLatin1String("Cannot assign to read-only property \"") + engine->toString(property.string()) + QLatin1Char('\"'); v8::ThrowException(v8::Exception::Error(engine->toString(error))); return true; } StoreProperty(engine, object, result, value); return true; } v8::Handle QV8QObjectWrapper::Getter(v8::Local property, const v8::AccessorInfo &info) { QV8QObjectResource *resource = v8_resource_check(info.This()); if (QQmlData::wasDeleted(resource->object)) return v8::Handle(); QObject *object = resource->object; QHashedV8String propertystring(property); QV8Engine *v8engine = resource->engine; v8::Handle This = info.This(); v8::Handle result = GetProperty(v8engine, object, &This, propertystring, QV8QObjectWrapper::IgnoreRevision); if (!result.IsEmpty()) return result; if (QV8Engine::startsWithUpper(property)) { // Check for attached properties QQmlContextData *context = v8engine->callingContext(); if (context && context->imports) { QQmlTypeNameCache::Result r = context->imports->query(propertystring); if (r.isValid()) { if (r.scriptIndex != -1) { return v8::Undefined(); } else if (r.type) { return v8engine->typeWrapper()->newObject(object, r.type, QV8TypeWrapper::ExcludeEnums); } else if (r.importNamespace) { return v8engine->typeWrapper()->newObject(object, context->imports, r.importNamespace, QV8TypeWrapper::ExcludeEnums); } Q_ASSERT(!"Unreachable"); } } } return v8::Handle(); } v8::Handle QV8QObjectWrapper::Setter(v8::Local property, v8::Local value, const v8::AccessorInfo &info) { QV8QObjectResource *resource = v8_resource_check(info.This()); if (QQmlData::wasDeleted(resource->object)) return value; QObject *object = resource->object; QHashedV8String propertystring(property); QV8Engine *v8engine = resource->engine; bool result = SetProperty(v8engine, object, propertystring, value, QV8QObjectWrapper::IgnoreRevision); if (!result) { QString error = QLatin1String("Cannot assign to non-existent property \"") + v8engine->toString(property) + QLatin1Char('\"'); v8::ThrowException(v8::Exception::Error(v8engine->toString(error))); return value; } return value; } v8::Handle QV8QObjectWrapper::Query(v8::Local property, const v8::AccessorInfo &info) { QV8QObjectResource *resource = v8_resource_check(info.This()); if (resource->object.isNull()) return v8::Handle(); QV8Engine *engine = resource->engine; QObject *object = resource->object; QHashedV8String propertystring(property); QQmlPropertyData local; QQmlPropertyData *result = 0; result = QQmlPropertyCache::property(engine->engine(), object, propertystring, local); if (!result) return v8::Handle(); else if (!result->isWritable() && !result->isQList()) return v8::Integer::New(v8::ReadOnly | v8::DontDelete); else return v8::Integer::New(v8::DontDelete); } v8::Handle QV8QObjectWrapper::Enumerator(const v8::AccessorInfo &info) { QV8QObjectResource *resource = v8_resource_check(info.This()); if (resource->object.isNull()) return v8::Array::New(); QObject *object = resource->object; QStringList result; QQmlEnginePrivate *ep = resource->engine->engine() ? QQmlEnginePrivate::get(resource->engine->engine()) : 0; QQmlPropertyCache *cache = 0; QQmlData *ddata = QQmlData::get(object); if (ddata) cache = ddata->propertyCache; if (!cache) { cache = ep ? ep->cache(object) : 0; if (cache) { if (ddata) { cache->addref(); ddata->propertyCache = cache; } } else { // Not cachable - fall back to QMetaObject (eg. dynamic meta object) const QMetaObject *mo = object->metaObject(); int pc = mo->propertyCount(); int po = mo->propertyOffset(); for (int i=po; iproperty(i).name()); } } else { result = cache->propertyNames(); } v8::Local rv = v8::Array::New(result.count()); for (int ii = 0; ii < result.count(); ++ii) rv->Set(ii, resource->engine->toString(result.at(ii))); return rv; } static void FastValueSetter(v8::Local, v8::Local value, const v8::AccessorInfo& info) { QV8QObjectResource *resource = v8_resource_check(info.This()); if (QQmlData::wasDeleted(resource->object)) return; QObject *object = resource->object; QQmlPropertyData *property = (QQmlPropertyData *)v8::External::Unwrap(info.Data()); int index = property->coreIndex; QQmlData *ddata = QQmlData::get(object, false); Q_ASSERT(ddata); Q_ASSERT(ddata->propertyCache); QQmlPropertyData *pdata = ddata->propertyCache->property(index); Q_ASSERT(pdata); Q_ASSERT(pdata->isWritable() || pdata->isQList()); StoreProperty(resource->engine, object, pdata, value); } static void FastValueSetterReadOnly(v8::Local property, v8::Local, const v8::AccessorInfo& info) { QV8QObjectResource *resource = v8_resource_check(info.This()); if (QQmlData::wasDeleted(resource->object)) return; QV8Engine *v8engine = resource->engine; QString error = QLatin1String("Cannot assign to read-only property \"") + v8engine->toString(property) + QLatin1Char('\"'); v8::ThrowException(v8::Exception::Error(v8engine->toString(error))); } void QV8QObjectWrapper::WeakQObjectReferenceCallback(v8::Persistent handle, void *wrapper) { Q_ASSERT(handle->IsObject()); QV8QObjectResource *resource = v8_resource_check(handle->ToObject()); Q_ASSERT(resource); static_cast(wrapper)->unregisterWeakQObjectReference(resource); if (static_cast(wrapper)->deleteWeakQObject(resource, false)) { qPersistentDispose(handle); // dispose. } else { handle.MakeWeak(0, WeakQObjectReferenceCallback); // revive. } } static void WeakQObjectInstanceCallback(v8::Persistent handle, void *data) { QV8QObjectInstance *instance = (QV8QObjectInstance *)data; instance->v8object.Clear(); qPersistentDispose(handle); } v8::Local QQmlPropertyCache::newQObject(QObject *object, QV8Engine *engine) { Q_ASSERT(object); Q_ASSERT(this->engine); Q_ASSERT(QQmlData::get(object, false)); Q_ASSERT(QQmlData::get(object, false)->propertyCache == this); // Setup constructor if (constructor.IsEmpty()) { v8::Local ft; QString toString = QLatin1String("toString"); QString destroy = QLatin1String("destroy"); // As we use hash linking, it is possible that iterating over the values can give duplicates. // To combat this, we must unique'ify our properties. StringCache uniqueHash; if (stringCache.isLinked()) uniqueHash.reserve(stringCache.count()); // XXX TODO: Enables fast property accessors. These more than double the property access // performance, but the cost of setting up this structure hasn't been measured so // its not guarenteed that this is a win overall. We need to try and measure the cost. for (StringCache::ConstIterator iter = stringCache.begin(); iter != stringCache.end(); ++iter) { if (stringCache.isLinked()) { if (uniqueHash.contains(iter)) continue; uniqueHash.insert(iter); } QQmlPropertyData *property = *iter; if (property->notFullyResolved()) resolve(property); if (property->isFunction()) continue; v8::AccessorGetter fastgetter = 0; v8::AccessorSetter fastsetter = FastValueSetter; if (!property->isWritable()) fastsetter = FastValueSetterReadOnly; if (property->isQObject()) fastgetter = FAST_GETTER_FUNCTION(property, QObject*); else if (property->propType == QMetaType::Int || property->isEnum()) fastgetter = FAST_GETTER_FUNCTION(property, int); else if (property->propType == QMetaType::Bool) fastgetter = FAST_GETTER_FUNCTION(property, bool); else if (property->propType == QMetaType::QString) fastgetter = FAST_GETTER_FUNCTION(property, QString); else if (property->propType == QMetaType::UInt) fastgetter = FAST_GETTER_FUNCTION(property, uint); else if (property->propType == QMetaType::Float) fastgetter = FAST_GETTER_FUNCTION(property, float); else if (property->propType == QMetaType::Double) fastgetter = FAST_GETTER_FUNCTION(property, double); if (fastgetter) { QString name = iter.key(); if (name == toString || name == destroy) continue; if (ft.IsEmpty()) { ft = v8::FunctionTemplate::New(); ft->InstanceTemplate()->SetFallbackPropertyHandler(QV8QObjectWrapper::Getter, QV8QObjectWrapper::Setter, QV8QObjectWrapper::Query, 0, QV8QObjectWrapper::Enumerator); ft->InstanceTemplate()->SetHasExternalResource(true); } // We wrap the raw QQmlPropertyData pointer here. This is safe as the // pointer will remain valid at least as long as the lifetime of any QObject's of // this type and the property accessor checks if the object is 0 (deleted) before // dereferencing the pointer. ft->InstanceTemplate()->SetAccessor(engine->toString(name), fastgetter, fastsetter, v8::External::Wrap(property)); } } if (ft.IsEmpty()) { constructor = qPersistentNew(engine->qobjectWrapper()->m_constructor); } else { ft->InstanceTemplate()->SetFallbackPropertyHandler(QV8QObjectWrapper::Getter, QV8QObjectWrapper::Setter, QV8QObjectWrapper::Query, 0, QV8QObjectWrapper::Enumerator); ft->InstanceTemplate()->SetHasExternalResource(true); constructor = qPersistentNew(ft->GetFunction()); } QQmlCleanup::addToEngine(this->engine); } v8::Local result = constructor->NewInstance(); QV8QObjectResource *r = new QV8QObjectResource(engine, object); result->SetExternalResource(r); return result; } v8::Local QV8QObjectWrapper::newQObject(QObject *object, QQmlData *ddata, QV8Engine *engine) { v8::Local rv; if (!ddata->propertyCache && engine->engine()) { ddata->propertyCache = QQmlEnginePrivate::get(engine->engine())->cache(object); if (ddata->propertyCache) ddata->propertyCache->addref(); } if (ddata->propertyCache && ddata->propertyCache->qmlEngine() == engine->engine()) { rv = ddata->propertyCache->newQObject(object, engine); } else { // XXX NewInstance() should be optimized rv = m_constructor->NewInstance(); QV8QObjectResource *r = new QV8QObjectResource(engine, object); rv->SetExternalResource(r); } return rv; } /* As V8 doesn't support an equality callback, for QObject's we have to return exactly the same V8 handle for subsequent calls to newQObject for the same QObject. To do this we have a two pronged strategy: 1. If there is no current outstanding V8 handle to the QObject, we create one and store a persistent handle in QQmlData::v8object. We mark the QV8QObjectWrapper that "owns" this handle by setting the QQmlData::v8objectid to the id of this QV8QObjectWrapper. 2. If another QV8QObjectWrapper has create the handle in QQmlData::v8object we create an entry in the m_taintedObject hash where we store the handle and mark the object as "tainted" in the QQmlData::hasTaintedV8Object flag. We have to mark the object as tainted to ensure that we search our m_taintedObject hash even in the case that the original QV8QObjectWrapper owner of QQmlData::v8object has released the handle. */ v8::Handle QV8QObjectWrapper::newQObject(QObject *object) { if (QQmlData::wasDeleted(object)) return v8::Null(); QQmlData *ddata = QQmlData::get(object, true); if (!ddata) return v8::Undefined(); if (ddata->v8objectid == m_id && !ddata->v8object.IsEmpty()) { // We own the v8object return v8::Local::New(ddata->v8object); } else if (ddata->v8object.IsEmpty() && (ddata->v8objectid == m_id || // We own the QObject ddata->v8objectid == 0 || // No one owns the QObject !ddata->hasTaintedV8Object)) { // Someone else has used the QObject, but it isn't tainted v8::Local rv = newQObject(object, ddata, m_engine); ddata->v8object = qPersistentNew(rv); ddata->v8object.MakeWeak(this, WeakQObjectReferenceCallback); ddata->v8objectid = m_id; QV8QObjectResource *resource = v8_resource_check(rv); registerWeakQObjectReference(resource); return rv; } else { // If this object is tainted, we have to check to see if it is in our // tainted object list TaintedHash::Iterator iter = ddata->hasTaintedV8Object?m_taintedObjects.find(object):m_taintedObjects.end(); bool found = iter != m_taintedObjects.end(); // If our tainted handle doesn't exist or has been collected, and there isn't // a handle in the ddata, we can assume ownership of the ddata->v8object if ((!found || (*iter)->v8object.IsEmpty()) && ddata->v8object.IsEmpty()) { v8::Local rv = newQObject(object, ddata, m_engine); ddata->v8object = qPersistentNew(rv); ddata->v8object.MakeWeak(this, WeakQObjectReferenceCallback); ddata->v8objectid = m_id; QV8QObjectResource *resource = v8_resource_check(rv); registerWeakQObjectReference(resource); if (found) { delete (*iter); m_taintedObjects.erase(iter); } return rv; } else if (!found) { QV8QObjectInstance *instance = new QV8QObjectInstance(object, this); iter = m_taintedObjects.insert(object, instance); ddata->hasTaintedV8Object = true; } if ((*iter)->v8object.IsEmpty()) { v8::Local rv = newQObject(object, ddata, m_engine); (*iter)->v8object = qPersistentNew(rv); (*iter)->v8object.MakeWeak((*iter), WeakQObjectInstanceCallback); } return v8::Local::New((*iter)->v8object); } } // returns true if the object's qqmldata v8object handle should // be disposed by the caller, false if it should not be (due to // creation status, etc). bool QV8QObjectWrapper::deleteWeakQObject(QV8QObjectResource *resource, bool calledFromEngineDtor) { QObject *object = resource->object; if (object) { QQmlData *ddata = QQmlData::get(object, false); if (ddata) { if (!calledFromEngineDtor && ddata->rootObjectInCreation) { // if weak ref callback is triggered (by gc) for a root object // prior to completion of creation, we should NOT delete it. return false; } ddata->v8object.Clear(); if (!object->parent() && !ddata->indestructible) { // This object is notionally destroyed now if (ddata->ownContext && ddata->context) ddata->context->emitDestruction(); ddata->isQueuedForDeletion = true; object->deleteLater(); } } } return true; } QPair QV8QObjectWrapper::ExtractQtSignal(QV8Engine *engine, v8::Handle object) { if (object->IsFunction()) return ExtractQtMethod(engine, v8::Handle::Cast(object)); if (QV8SignalHandlerResource *resource = v8_resource_cast(object)) return qMakePair(resource->object.data(), resource->index); return qMakePair((QObject *)0, -1); } QPair QV8QObjectWrapper::ExtractQtMethod(QV8Engine *engine, v8::Handle function) { v8::ScriptOrigin origin = function->GetScriptOrigin(); if (origin.ResourceName()->StrictEquals(engine->qobjectWrapper()->m_hiddenObject)) { // This is one of our special QObject method wrappers v8::Handle args[] = { engine->qobjectWrapper()->m_hiddenObject }; v8::Local data = function->Call(engine->global(), 1, args); if (data->IsArray()) { v8::Local array = v8::Local::Cast(data); return qMakePair(engine->toQObject(array->Get(0)), array->Get(1)->Int32Value()); } // In theory this can't fall through, but I suppose V8 might run out of memory or something } return qMakePair((QObject *)0, -1); } class QV8QObjectConnectionList : public QObject, public QQmlGuard { public: QV8QObjectConnectionList(QObject *object, QV8Engine *engine); ~QV8QObjectConnectionList(); struct Connection { Connection() : needsDestroy(false) {} Connection(const Connection &other) : thisObject(other.thisObject), function(other.function), needsDestroy(false) {} Connection &operator=(const Connection &other) { thisObject = other.thisObject; function = other.function; needsDestroy = other.needsDestroy; return *this; } v8::Persistent thisObject; v8::Persistent function; void dispose() { qPersistentDispose(thisObject); qPersistentDispose(function); } bool needsDestroy; }; struct ConnectionList : public QList { ConnectionList() : connectionsInUse(0), connectionsNeedClean(false) {} int connectionsInUse; bool connectionsNeedClean; }; QV8Engine *engine; typedef QHash SlotHash; SlotHash slotHash; bool needsDestroy; int inUse; virtual void objectDestroyed(QObject *); virtual int qt_metacall(QMetaObject::Call, int, void **); }; QV8QObjectConnectionList::QV8QObjectConnectionList(QObject *object, QV8Engine *engine) : QQmlGuard(object), engine(engine), needsDestroy(false), inUse(0) { } QV8QObjectConnectionList::~QV8QObjectConnectionList() { for (SlotHash::Iterator iter = slotHash.begin(); iter != slotHash.end(); ++iter) { QList &connections = *iter; for (int ii = 0; ii < connections.count(); ++ii) { qPersistentDispose(connections[ii].thisObject); qPersistentDispose(connections[ii].function); } } slotHash.clear(); } void QV8QObjectConnectionList::objectDestroyed(QObject *object) { engine->qobjectWrapper()->m_connections.remove(object); if (inUse) needsDestroy = true; else delete this; } int QV8QObjectConnectionList::qt_metacall(QMetaObject::Call method, int index, void **metaArgs) { if (method == QMetaObject::InvokeMetaMethod) { SlotHash::Iterator iter = slotHash.find(index); if (iter == slotHash.end()) return -1; ConnectionList &connectionList = *iter; if (connectionList.isEmpty()) return -1; inUse++; connectionList.connectionsInUse++; QList connections = connectionList; QVarLengthArray dummy; int *argsTypes = QQmlPropertyCache::methodParameterTypes(data(), index, dummy, 0); v8::HandleScope handle_scope; v8::Context::Scope scope(engine->context()); int argCount = argsTypes?argsTypes[0]:0; QVarLengthArray, 9> args(argCount); for (int ii = 0; ii < argCount; ++ii) { int type = argsTypes[ii + 1]; if (type == qMetaTypeId()) { args[ii] = engine->fromVariant(*((QVariant *)metaArgs[ii + 1])); } else { args[ii] = engine->fromVariant(QVariant(type, metaArgs[ii + 1])); } } for (int ii = 0; ii < connections.count(); ++ii) { Connection &connection = connections[ii]; if (connection.needsDestroy) continue; v8::TryCatch try_catch; if (connection.thisObject.IsEmpty()) { connection.function->Call(engine->global(), argCount, args.data()); } else { connection.function->Call(connection.thisObject, argCount, args.data()); } if (try_catch.HasCaught()) { QQmlError error; error.setDescription(QString(QLatin1String("Unknown exception occurred during evaluation of connected function: %1")).arg(engine->toString(connection.function->GetName()))); v8::Local message = try_catch.Message(); if (!message.IsEmpty()) QQmlExpressionPrivate::exceptionToError(message, error); QQmlEnginePrivate::get(engine->engine())->warning(error); } } connectionList.connectionsInUse--; if (connectionList.connectionsInUse == 0 && connectionList.connectionsNeedClean) { for (QList::Iterator iter = connectionList.begin(); iter != connectionList.end(); ) { if (iter->needsDestroy) { iter->dispose(); iter = connectionList.erase(iter); } else { ++iter; } } } inUse--; if (inUse == 0 && needsDestroy) delete this; } return -1; } v8::Handle QV8QObjectWrapper::Connect(const v8::Arguments &args) { if (args.Length() == 0) V8THROW_ERROR("Function.prototype.connect: no arguments given"); QV8Engine *engine = V8ENGINE(); QPair signalInfo = ExtractQtSignal(engine, args.This()); QObject *signalObject = signalInfo.first; int signalIndex = signalInfo.second; if (signalIndex == -1) V8THROW_ERROR("Function.prototype.connect: this object is not a signal"); if (!signalObject) V8THROW_ERROR("Function.prototype.connect: cannot connect to deleted QObject"); if (signalIndex < 0 || signalObject->metaObject()->method(signalIndex).methodType() != QMetaMethod::Signal) V8THROW_ERROR("Function.prototype.connect: this object is not a signal"); v8::Local functionValue; v8::Local functionThisValue; if (args.Length() == 1) { functionValue = args[0]; } else { functionThisValue = args[0]; functionValue = args[1]; } if (!functionValue->IsFunction()) V8THROW_ERROR("Function.prototype.connect: target is not a function"); if (!functionThisValue.IsEmpty() && !functionThisValue->IsObject()) V8THROW_ERROR("Function.prototype.connect: target this is not an object"); QV8QObjectWrapper *qobjectWrapper = engine->qobjectWrapper(); QHash &connections = qobjectWrapper->m_connections; QHash::Iterator iter = connections.find(signalObject); if (iter == connections.end()) iter = connections.insert(signalObject, new QV8QObjectConnectionList(signalObject, engine)); QV8QObjectConnectionList *connectionList = *iter; QV8QObjectConnectionList::SlotHash::Iterator slotIter = connectionList->slotHash.find(signalIndex); if (slotIter == connectionList->slotHash.end()) { slotIter = connectionList->slotHash.insert(signalIndex, QV8QObjectConnectionList::ConnectionList()); QMetaObject::connect(signalObject, signalIndex, connectionList, signalIndex); } QV8QObjectConnectionList::Connection connection; if (!functionThisValue.IsEmpty()) connection.thisObject = qPersistentNew(functionThisValue->ToObject()); connection.function = qPersistentNew(v8::Handle::Cast(functionValue)); slotIter->append(connection); return v8::Undefined(); } v8::Handle QV8QObjectWrapper::Disconnect(const v8::Arguments &args) { if (args.Length() == 0) V8THROW_ERROR("Function.prototype.disconnect: no arguments given"); QV8Engine *engine = V8ENGINE(); QPair signalInfo = ExtractQtSignal(engine, args.This()); QObject *signalObject = signalInfo.first; int signalIndex = signalInfo.second; if (signalIndex == -1) V8THROW_ERROR("Function.prototype.disconnect: this object is not a signal"); if (!signalObject) V8THROW_ERROR("Function.prototype.disconnect: cannot disconnect from deleted QObject"); if (signalIndex < 0 || signalObject->metaObject()->method(signalIndex).methodType() != QMetaMethod::Signal) V8THROW_ERROR("Function.prototype.disconnect: this object is not a signal"); v8::Local functionValue; v8::Local functionThisValue; if (args.Length() == 1) { functionValue = args[0]; } else { functionThisValue = args[0]; functionValue = args[1]; } if (!functionValue->IsFunction()) V8THROW_ERROR("Function.prototype.disconnect: target is not a function"); if (!functionThisValue.IsEmpty() && !functionThisValue->IsObject()) V8THROW_ERROR("Function.prototype.disconnect: target this is not an object"); QV8QObjectWrapper *qobjectWrapper = engine->qobjectWrapper(); QHash &connectionsList = qobjectWrapper->m_connections; QHash::Iterator iter = connectionsList.find(signalObject); if (iter == connectionsList.end()) return v8::Undefined(); // Nothing to disconnect from QV8QObjectConnectionList *connectionList = *iter; QV8QObjectConnectionList::SlotHash::Iterator slotIter = connectionList->slotHash.find(signalIndex); if (slotIter == connectionList->slotHash.end()) return v8::Undefined(); // Nothing to disconnect from QV8QObjectConnectionList::ConnectionList &connections = *slotIter; v8::Local function = v8::Local::Cast(functionValue); QPair functionData = ExtractQtMethod(engine, function); if (functionData.second != -1) { // This is a QObject function wrapper for (int ii = 0; ii < connections.count(); ++ii) { QV8QObjectConnectionList::Connection &connection = connections[ii]; if (connection.thisObject.IsEmpty() == functionThisValue.IsEmpty() && (connection.thisObject.IsEmpty() || connection.thisObject->StrictEquals(functionThisValue))) { QPair connectedFunctionData = ExtractQtMethod(engine, connection.function); if (connectedFunctionData == functionData) { // Match! if (connections.connectionsInUse) { connection.needsDestroy = true; connections.connectionsNeedClean = true; } else { connection.dispose(); connections.removeAt(ii); } return v8::Undefined(); } } } } else { // This is a normal JS function for (int ii = 0; ii < connections.count(); ++ii) { QV8QObjectConnectionList::Connection &connection = connections[ii]; if (connection.function->StrictEquals(function) && connection.thisObject.IsEmpty() == functionThisValue.IsEmpty() && (connection.thisObject.IsEmpty() || connection.thisObject->StrictEquals(functionThisValue))) { // Match! if (connections.connectionsInUse) { connection.needsDestroy = true; connections.connectionsNeedClean = true; } else { connection.dispose(); connections.removeAt(ii); } return v8::Undefined(); } } } return v8::Undefined(); } /*! \fn v8::Handle QV8QObjectWrapper::getProperty(QObject *object, const QHashedV8String &property, QV8QObjectWrapper::RevisionMode revisionMode) Get the \a property of \a object. Returns an empty handle if the property doesn't exist. Only searches for real properties of \a object (including methods), not attached properties etc. */ /* \fn bool QV8QObjectWrapper::setProperty(QObject *object, const QHashedV8String &property, v8::Handle value, RevisionMode revisionMode) Set the \a property of \a object to \a value. Returns true if the property was "set" - even if this results in an exception being thrown - and false if the object has no such property. Only searches for real properties of \a object (including methods), not attached properties etc. */ namespace { struct CallArgs { CallArgs(int length, v8::Handle *args) : _length(length), _args(args) {} int Length() const { return _length; } v8::Local operator[](int idx) { return (*_args)->Get(idx); } private: int _length; v8::Handle *_args; }; } static v8::Handle CallMethod(QObject *object, int index, int returnType, int argCount, int *argTypes, QV8Engine *engine, CallArgs &callArgs) { if (argCount > 0) { // Special handling is required for value types. // We need to save the current value in a temporary, // and reapply it after converting all arguments. // This avoids the "overwriting copy-value-type-value" // problem during Q_INVOKABLE function invocation. QQmlValueType *valueTypeObject = qobject_cast(object); QVariant valueTypeValue; if (valueTypeObject) valueTypeValue = valueTypeObject->value(); // Convert all arguments. QVarLengthArray args(argCount + 1); args[0].initAsType(returnType); for (int ii = 0; ii < argCount; ++ii) args[ii + 1].fromValue(argTypes[ii], engine, callArgs[ii]); QVarLengthArray argData(args.count()); for (int ii = 0; ii < args.count(); ++ii) argData[ii] = args[ii].dataPtr(); // Reinstate saved value type object value if required. if (valueTypeObject) valueTypeObject->setValue(valueTypeValue); QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, index, argData.data()); return args[0].toValue(engine); } else if (returnType != QMetaType::Void) { CallArgument arg; arg.initAsType(returnType); void *args[] = { arg.dataPtr() }; QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, index, args); return arg.toValue(engine); } else { void *args[] = { 0 }; QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, index, args); return v8::Undefined(); } } /*! Returns the match score for converting \a actual to be of type \a conversionType. A zero score means "perfect match" whereas a higher score is worse. The conversion table is copied out of the QtScript callQtMethod() function. */ static int MatchScore(v8::Handle actual, int conversionType) { if (actual->IsNumber()) { switch (conversionType) { case QMetaType::Double: return 0; case QMetaType::Float: return 1; case QMetaType::LongLong: case QMetaType::ULongLong: return 2; case QMetaType::Long: case QMetaType::ULong: return 3; case QMetaType::Int: case QMetaType::UInt: return 4; case QMetaType::Short: case QMetaType::UShort: return 5; break; case QMetaType::Char: case QMetaType::UChar: return 6; case QMetaType::QJsonValue: return 5; default: return 10; } } else if (actual->IsString()) { switch (conversionType) { case QMetaType::QString: return 0; case QMetaType::QJsonValue: return 5; default: return 10; } } else if (actual->IsBoolean()) { switch (conversionType) { case QMetaType::Bool: return 0; case QMetaType::QJsonValue: return 5; default: return 10; } } else if (actual->IsDate()) { switch (conversionType) { case QMetaType::QDateTime: return 0; case QMetaType::QDate: return 1; case QMetaType::QTime: return 2; default: return 10; } } else if (actual->IsRegExp()) { switch (conversionType) { case QMetaType::QRegExp: return 0; default: return 10; } } else if (actual->IsArray()) { switch (conversionType) { case QMetaType::QJsonArray: return 3; case QMetaType::QStringList: case QMetaType::QVariantList: return 5; case QMetaType::QVector4D: case QMetaType::QMatrix4x4: return 6; case QMetaType::QVector3D: return 7; default: return 10; } } else if (actual->IsNull()) { switch (conversionType) { case QMetaType::VoidStar: case QMetaType::QObjectStar: case QMetaType::QJsonValue: return 0; default: { const char *typeName = QMetaType::typeName(conversionType); if (typeName && typeName[strlen(typeName) - 1] == '*') return 0; else return 10; } } } else if (actual->IsObject()) { v8::Handle obj = v8::Handle::Cast(actual); QV8ObjectResource *r = static_cast(obj->GetExternalResource()); if (r && r->resourceType() == QV8ObjectResource::QObjectType) { switch (conversionType) { case QMetaType::QObjectStar: return 0; default: return 10; } } else if (r && r->resourceType() == QV8ObjectResource::VariantType) { if (conversionType == qMetaTypeId()) return 0; else if (r->engine->toVariant(actual, -1).userType() == conversionType) return 0; else return 10; } else if (r && r->resourceType() == QV8ObjectResource::ValueTypeType) { if (r->engine->toVariant(actual, -1).userType() == conversionType) return 0; return 10; } else if (conversionType == QMetaType::QJsonObject) { return 5; } else { return 10; } } else { return 10; } } static inline int QMetaObject_methods(const QMetaObject *metaObject) { struct Private { int revision; int className; int classInfoCount, classInfoData; int methodCount, methodData; }; return reinterpret_cast(metaObject->d.data)->methodCount; } /*! Returns the next related method, if one, or 0. */ static const QQmlPropertyData * RelatedMethod(QObject *object, const QQmlPropertyData *current, QQmlPropertyData &dummy) { QQmlPropertyCache *cache = QQmlData::get(object)->propertyCache; if (!current->isOverload()) return 0; Q_ASSERT(!current->overrideIndexIsProperty); if (cache) { return cache->method(current->overrideIndex); } else { const QMetaObject *mo = object->metaObject(); int methodOffset = mo->methodCount() - QMetaObject_methods(mo); while (methodOffset > current->overrideIndex) { mo = mo->superClass(); methodOffset -= QMetaObject_methods(mo); } QMetaMethod method = mo->method(current->overrideIndex); dummy.load(method); // Look for overloaded methods QByteArray methodName = method.name(); for (int ii = current->overrideIndex - 1; ii >= methodOffset; --ii) { if (methodName == mo->method(ii).name()) { dummy.setFlags(dummy.getFlags() | QQmlPropertyData::IsOverload); dummy.overrideIndexIsProperty = 0; dummy.overrideIndex = ii; return &dummy; } } return &dummy; } } static v8::Handle CallPrecise(QObject *object, const QQmlPropertyData &data, QV8Engine *engine, CallArgs &callArgs) { QByteArray unknownTypeError; int returnType = QQmlPropertyCache::methodReturnType(object, data, &unknownTypeError); if (returnType == QMetaType::UnknownType) { QString typeName = QString::fromLatin1(unknownTypeError); QString error = QString::fromLatin1("Unknown method return type: %1").arg(typeName); v8::ThrowException(v8::Exception::Error(engine->toString(error))); return v8::Handle(); } if (data.hasArguments()) { int *args = 0; QVarLengthArray dummy; args = QQmlPropertyCache::methodParameterTypes(object, data.coreIndex, dummy, &unknownTypeError); if (!args) { QString typeName = QString::fromLatin1(unknownTypeError); QString error = QString::fromLatin1("Unknown method parameter type: %1").arg(typeName); v8::ThrowException(v8::Exception::Error(engine->toString(error))); return v8::Handle(); } if (args[0] > callArgs.Length()) { QString error = QLatin1String("Insufficient arguments"); v8::ThrowException(v8::Exception::Error(engine->toString(error))); return v8::Handle(); } return CallMethod(object, data.coreIndex, returnType, args[0], args + 1, engine, callArgs); } else { return CallMethod(object, data.coreIndex, returnType, 0, 0, engine, callArgs); } } /*! Resolve the overloaded method to call. The algorithm works conceptually like this: 1. Resolve the set of overloads it is *possible* to call. Impossible overloads include those that have too many parameters or have parameters of unknown type. 2. Filter the set of overloads to only contain those with the closest number of parameters. For example, if we are called with 3 parameters and there are 2 overloads that take 2 parameters and one that takes 3, eliminate the 2 parameter overloads. 3. Find the best remaining overload based on its match score. If two or more overloads have the same match score, call the last one. The match score is constructed by adding the matchScore() result for each of the parameters. */ static v8::Handle CallOverloaded(QObject *object, const QQmlPropertyData &data, QV8Engine *engine, CallArgs &callArgs) { int argumentCount = callArgs.Length(); const QQmlPropertyData *best = 0; int bestParameterScore = INT_MAX; int bestMatchScore = INT_MAX; // Special handling is required for value types. // We need to save the current value in a temporary, // and reapply it after converting all arguments. // This avoids the "overwriting copy-value-type-value" // problem during Q_INVOKABLE function invocation. QQmlValueType *valueTypeObject = qobject_cast(object); QVariant valueTypeValue; if (valueTypeObject) valueTypeValue = valueTypeObject->value(); QQmlPropertyData dummy; const QQmlPropertyData *attempt = &data; do { QVarLengthArray dummy; int methodArgumentCount = 0; int *methodArgTypes = 0; if (attempt->hasArguments()) { typedef QQmlPropertyCache PC; int *args = PC::methodParameterTypes(object, attempt->coreIndex, dummy, 0); if (!args) // Must be an unknown argument continue; methodArgumentCount = args[0]; methodArgTypes = args + 1; } if (methodArgumentCount > argumentCount) continue; // We don't have sufficient arguments to call this method int methodParameterScore = argumentCount - methodArgumentCount; if (methodParameterScore > bestParameterScore) continue; // We already have a better option int methodMatchScore = 0; for (int ii = 0; ii < methodArgumentCount; ++ii) methodMatchScore += MatchScore(callArgs[ii], methodArgTypes[ii]); if (bestParameterScore > methodParameterScore || bestMatchScore > methodMatchScore) { best = attempt; bestParameterScore = methodParameterScore; bestMatchScore = methodMatchScore; } if (bestParameterScore == 0 && bestMatchScore == 0) break; // We can't get better than that } while((attempt = RelatedMethod(object, attempt, dummy)) != 0); if (best) { if (valueTypeObject) valueTypeObject->setValue(valueTypeValue); return CallPrecise(object, *best, engine, callArgs); } else { QString error = QLatin1String("Unable to determine callable overload. Candidates are:"); const QQmlPropertyData *candidate = &data; while (candidate) { error += QLatin1String("\n ") + QString::fromUtf8(object->metaObject()->method(candidate->coreIndex).methodSignature().constData()); candidate = RelatedMethod(object, candidate, dummy); } v8::ThrowException(v8::Exception::Error(engine->toString(error))); return v8::Handle(); } } static v8::Handle ToString(QV8Engine *engine, QObject *object, int, v8::Handle) { QString result; if (object) { QString objectName = object->objectName(); result += QString::fromUtf8(object->metaObject()->className()); result += QLatin1String("(0x"); result += QString::number((quintptr)object,16); if (!objectName.isEmpty()) { result += QLatin1String(", \""); result += objectName; result += QLatin1Char('\"'); } result += QLatin1Char(')'); } else { result = QLatin1String("null"); } return engine->toString(result); } static v8::Handle Destroy(QV8Engine *, QObject *object, int argCount, v8::Handle args) { QQmlData *ddata = QQmlData::get(object, false); if (!ddata || ddata->indestructible || ddata->rootObjectInCreation) { const char *error = "Invalid attempt to destroy() an indestructible object"; v8::ThrowException(v8::Exception::Error(v8::String::New(error))); return v8::Undefined(); } int delay = 0; if (argCount > 0) delay = args->Get(0)->Uint32Value(); if (delay > 0) QTimer::singleShot(delay, object, SLOT(deleteLater())); else object->deleteLater(); return v8::Undefined(); } v8::Handle QV8QObjectWrapper::Invoke(const v8::Arguments &args) { // object, index, qmlglobal, argCount, args Q_ASSERT(args.Length() == 5); Q_ASSERT(args[0]->IsObject()); QV8QObjectResource *resource = v8_resource_cast(args[0]->ToObject()); if (!resource) return v8::Undefined(); int argCount = args[3]->Int32Value(); v8::Handle arguments = v8::Handle::Cast(args[4]); // Special hack to return info about this closure. if (argCount == 1 && arguments->Get(0)->StrictEquals(resource->engine->qobjectWrapper()->m_hiddenObject)) { v8::Local data = v8::Array::New(2); data->Set(0, args[0]); data->Set(1, args[1]); return data; } QObject *object = resource->object; int index = args[1]->Int32Value(); if (!object) return v8::Undefined(); if (index < 0) { // Builtin functions if (index == QOBJECT_TOSTRING_INDEX) { return ToString(resource->engine, object, argCount, arguments); } else if (index == QOBJECT_DESTROY_INDEX) { return Destroy(resource->engine, object, argCount, arguments); } else { return v8::Undefined(); } } QQmlPropertyData method; if (QQmlData *ddata = static_cast(QObjectPrivate::get(object)->declarativeData)) { if (ddata->propertyCache) { QQmlPropertyData *d = ddata->propertyCache->method(index); if (!d) return v8::Undefined(); method = *d; } } if (method.coreIndex == -1) { method.load(object->metaObject()->method(index)); if (method.coreIndex == -1) return v8::Undefined(); } if (method.isV8Function()) { v8::Handle rv; v8::Handle qmlglobal = args[2]->ToObject(); QQmlV8Function func(argCount, arguments, rv, qmlglobal, resource->engine->contextWrapper()->context(qmlglobal), resource->engine); QQmlV8Function *funcptr = &func; void *args[] = { 0, &funcptr }; QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, method.coreIndex, args); if (rv.IsEmpty()) return v8::Undefined(); return rv; } CallArgs callArgs(argCount, &arguments); if (!method.isOverload()) { return CallPrecise(object, method, resource->engine, callArgs); } else { return CallOverloaded(object, method, resource->engine, callArgs); } } CallArgument::CallArgument() : type(QVariant::Invalid) { } CallArgument::~CallArgument() { cleanup(); } void CallArgument::cleanup() { if (type == QMetaType::QString) { qstringPtr->~QString(); } else if (type == -1 || type == QMetaType::QVariant) { qvariantPtr->~QVariant(); } else if (type == qMetaTypeId()) { qjsValuePtr->~QJSValue(); } else if (type == qMetaTypeId >()) { qlistPtr->~QList(); } else if (type == QMetaType::QJsonArray) { jsonArrayPtr->~QJsonArray(); } else if (type == QMetaType::QJsonObject) { jsonObjectPtr->~QJsonObject(); } else if (type == QMetaType::QJsonValue) { jsonValuePtr->~QJsonValue(); } } void *CallArgument::dataPtr() { if (type == -1) return qvariantPtr->data(); else return (void *)&allocData; } void CallArgument::initAsType(int callType) { if (type != 0) { cleanup(); type = 0; } if (callType == QMetaType::UnknownType) return; if (callType == qMetaTypeId()) { qjsValuePtr = new (&allocData) QJSValue(); type = callType; } else if (callType == QMetaType::Int || callType == QMetaType::UInt || callType == QMetaType::Bool || callType == QMetaType::Double || callType == QMetaType::Float) { type = callType; } else if (callType == QMetaType::QObjectStar) { qobjectPtr = 0; type = callType; } else if (callType == QMetaType::QString) { qstringPtr = new (&allocData) QString(); type = callType; } else if (callType == QMetaType::QVariant) { type = callType; qvariantPtr = new (&allocData) QVariant(); } else if (callType == qMetaTypeId >()) { type = callType; qlistPtr = new (&allocData) QList(); } else if (callType == qMetaTypeId()) { type = callType; handlePtr = new (&allocData) QQmlV8Handle; } else if (callType == QMetaType::QJsonArray) { type = callType; jsonArrayPtr = new (&allocData) QJsonArray(); } else if (callType == QMetaType::QJsonObject) { type = callType; jsonObjectPtr = new (&allocData) QJsonObject(); } else if (callType == QMetaType::QJsonValue) { type = callType; jsonValuePtr = new (&allocData) QJsonValue(); } else if (callType == QMetaType::Void) { type = -1; qvariantPtr = new (&allocData) QVariant(); } else { type = -1; qvariantPtr = new (&allocData) QVariant(callType, (void *)0); } } void CallArgument::fromValue(int callType, QV8Engine *engine, v8::Handle value) { if (type != 0) { cleanup(); type = 0; } if (callType == qMetaTypeId()) { qjsValuePtr = new (&allocData) QJSValue(QJSValuePrivate::get(new QJSValuePrivate(engine, value))); type = qMetaTypeId(); } else if (callType == QMetaType::Int) { intValue = quint32(value->Int32Value()); type = callType; } else if (callType == QMetaType::UInt) { intValue = quint32(value->Uint32Value()); type = callType; } else if (callType == QMetaType::Bool) { boolValue = value->BooleanValue(); type = callType; } else if (callType == QMetaType::Double) { doubleValue = double(value->NumberValue()); type = callType; } else if (callType == QMetaType::Float) { floatValue = float(value->NumberValue()); type = callType; } else if (callType == QMetaType::QString) { if (value->IsNull() || value->IsUndefined()) qstringPtr = new (&allocData) QString(); else qstringPtr = new (&allocData) QString(engine->toString(value->ToString())); type = callType; } else if (callType == QMetaType::QObjectStar) { qobjectPtr = engine->toQObject(value); type = callType; } else if (callType == qMetaTypeId()) { qvariantPtr = new (&allocData) QVariant(engine->toVariant(value, -1)); type = callType; } else if (callType == qMetaTypeId >()) { qlistPtr = new (&allocData) QList(); if (value->IsArray()) { v8::Handle array = v8::Handle::Cast(value); uint32_t length = array->Length(); for (uint32_t ii = 0; ii < length; ++ii) qlistPtr->append(engine->toQObject(array->Get(ii))); } else { qlistPtr->append(engine->toQObject(value)); } type = callType; } else if (callType == qMetaTypeId()) { handlePtr = new (&allocData) QQmlV8Handle(QQmlV8Handle::fromHandle(value)); type = callType; } else if (callType == QMetaType::QJsonArray) { jsonArrayPtr = new (&allocData) QJsonArray(engine->jsonArrayFromJS(value)); type = callType; } else if (callType == QMetaType::QJsonObject) { jsonObjectPtr = new (&allocData) QJsonObject(engine->jsonObjectFromJS(value)); type = callType; } else if (callType == QMetaType::QJsonValue) { jsonValuePtr = new (&allocData) QJsonValue(engine->jsonValueFromJS(value)); type = callType; } else if (callType == QMetaType::Void) { *qvariantPtr = QVariant(); } else { qvariantPtr = new (&allocData) QVariant(); type = -1; QQmlEnginePrivate *ep = engine->engine() ? QQmlEnginePrivate::get(engine->engine()) : 0; QVariant v = engine->toVariant(value, -1); // why -1 instead of callType? if (v.userType() == callType) { *qvariantPtr = v; } else if (v.canConvert(callType)) { *qvariantPtr = v; qvariantPtr->convert(callType); } else if (engine->sequenceWrapper()->isSequenceType(callType) && v.userType() == qMetaTypeId()) { // 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()) { QObject *obj = ep->toQObject(v); if (obj != 0 && !QQmlMetaObject::canConvert(obj, mo)) obj = 0; *qvariantPtr = QVariant(callType, &obj); } else { *qvariantPtr = QVariant(callType, (void *)0); } } } } v8::Handle CallArgument::toValue(QV8Engine *engine) { if (type == qMetaTypeId()) { return QJSValuePrivate::get(*qjsValuePtr)->asV8Value(engine); } else if (type == QMetaType::Int) { return v8::Integer::New(int(intValue)); } else if (type == QMetaType::UInt) { return v8::Integer::NewFromUnsigned(intValue); } else if (type == QMetaType::Bool) { return v8::Boolean::New(boolValue); } else if (type == QMetaType::Double) { return v8::Number::New(doubleValue); } else if (type == QMetaType::Float) { return v8::Number::New(floatValue); } else if (type == QMetaType::QString) { return engine->toString(*qstringPtr); } else if (type == QMetaType::QObjectStar) { QObject *object = qobjectPtr; if (object) QQmlData::get(object, true)->setImplicitDestructible(); return engine->newQObject(object); } else if (type == qMetaTypeId >()) { // XXX Can this be made more by using Array as a prototype and implementing // directly against QList? QList &list = *qlistPtr; v8::Local array = v8::Array::New(list.count()); for (int ii = 0; ii < list.count(); ++ii) array->Set(ii, engine->newQObject(list.at(ii))); return array; } else if (type == qMetaTypeId()) { return handlePtr->toHandle(); } else if (type == QMetaType::QJsonArray) { return engine->jsonArrayToJS(*jsonArrayPtr); } else if (type == QMetaType::QJsonObject) { return engine->jsonObjectToJS(*jsonObjectPtr); } else if (type == QMetaType::QJsonValue) { return engine->jsonValueToJS(*jsonValuePtr); } else if (type == -1 || type == qMetaTypeId()) { QVariant value = *qvariantPtr; v8::Handle rv = engine->fromVariant(value); if (QObject *object = engine->toQObject(rv)) QQmlData::get(object, true)->setImplicitDestructible(); return rv; } else { return v8::Undefined(); } } QT_END_NAMESPACE