From b855240b782395f94315f43ea3e7e182299fac48 Mon Sep 17 00:00:00 2001 From: Matthew Vogt Date: Thu, 16 Feb 2012 14:43:03 +1000 Subject: Rename QDeclarative symbols to QQuick and QQml Symbols beginning with QDeclarative are already exported by the quick1 module. Users can apply the bin/rename-qtdeclarative-symbols.sh script to modify client code using the previous names of the renamed symbols. Task-number: QTBUG-23737 Change-Id: Ifaa482663767634931e8711a8e9bf6e404859e66 Reviewed-by: Martin Jones --- src/qml/qml/v8/qv8engine.cpp | 1598 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1598 insertions(+) create mode 100644 src/qml/qml/v8/qv8engine.cpp (limited to 'src/qml/qml/v8/qv8engine.cpp') diff --git a/src/qml/qml/v8/qv8engine.cpp b/src/qml/qml/v8/qv8engine.cpp new file mode 100644 index 0000000000..4c2cce1525 --- /dev/null +++ b/src/qml/qml/v8/qv8engine.cpp @@ -0,0 +1,1598 @@ +/**************************************************************************** +** +** 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 "qv8engine_p.h" + +#include + +#include "qv8contextwrapper_p.h" +#include "qv8valuetypewrapper_p.h" +#include "qv8sequencewrapper_p.h" +#include "qv8include_p.h" +#include "qjsengine_p.h" +#include "../../../3rdparty/javascriptcore/DateMath.h" + +#include +#include +#include +#include +#include +#include + +#include "qscript_impl_p.h" +#include "qv8domerrors_p.h" +#include "qv8sqlerrors_p.h" + + +Q_DECLARE_METATYPE(QJSValue) +Q_DECLARE_METATYPE(QList) + + +// XXX TODO: Need to check all the global functions will also work in a worker script where the +// QQmlEngine is not available +QT_BEGIN_NAMESPACE + +static bool ObjectComparisonCallback(v8::Local lhs, v8::Local rhs) +{ + if (lhs == rhs) + return true; + + QV8ObjectResource *lhsr = static_cast(lhs->GetExternalResource()); + QV8ObjectResource *rhsr = static_cast(rhs->GetExternalResource()); + + Q_ASSERT(lhsr->engine == rhsr->engine); + + if (lhsr && rhsr) { + QV8ObjectResource::ResourceType lhst = lhsr->resourceType(); + QV8ObjectResource::ResourceType rhst = rhsr->resourceType(); + + switch (lhst) { + case QV8ObjectResource::ValueTypeType: + // a value type might be equal to a variant or another value type + if (rhst == QV8ObjectResource::ValueTypeType) { + return lhsr->engine->valueTypeWrapper()->isEqual(lhsr, lhsr->engine->valueTypeWrapper()->toVariant(rhsr)); + } else if (rhst == QV8ObjectResource::VariantType) { + return lhsr->engine->valueTypeWrapper()->isEqual(lhsr, lhsr->engine->variantWrapper()->toVariant(rhsr)); + } + break; + case QV8ObjectResource::VariantType: + // a variant might be equal to a value type or other variant. + if (rhst == QV8ObjectResource::VariantType) { + return lhsr->engine->variantWrapper()->toVariant(lhsr) == + lhsr->engine->variantWrapper()->toVariant(rhsr); + } else if (rhst == QV8ObjectResource::ValueTypeType) { + return rhsr->engine->valueTypeWrapper()->isEqual(rhsr, rhsr->engine->variantWrapper()->toVariant(lhsr)); + } + break; + case QV8ObjectResource::SequenceType: + // a sequence might be equal to itself. + if (rhst == QV8ObjectResource::SequenceType) { + return lhsr->engine->sequenceWrapper()->isEqual(lhsr, rhsr); + } + break; + default: + break; + } + } + + return false; +} + + +QV8Engine::QV8Engine(QJSEngine* qq, QJSEngine::ContextOwnership ownership) + : q(qq) + , m_engine(0) + , m_ownsV8Context(ownership == QJSEngine::CreateNewContext) + , m_xmlHttpRequestData(0) + , m_listModelData(0) +{ + qMetaTypeId(); + qMetaTypeId >(); + + QByteArray v8args = qgetenv("V8ARGS"); + // change default v8 behaviour to not relocate breakpoints across lines + if (!v8args.contains("breakpoint_relocation")) + v8args.append(" --nobreakpoint_relocation"); + v8::V8::SetFlagsFromString(v8args.constData(), v8args.length()); + + ensurePerThreadIsolate(); + + v8::HandleScope handle_scope; + m_context = (ownership == QJSEngine::CreateNewContext) ? v8::Context::New() : v8::Persistent::New(v8::Context::GetCurrent()); + qPersistentRegister(m_context); + m_originalGlobalObject.init(m_context); + v8::Context::Scope context_scope(m_context); + + v8::V8::SetUserObjectComparisonCallbackFunction(ObjectComparisonCallback); + QV8GCCallback::registerGcPrologueCallback(); + m_strongReferencer = qPersistentNew(v8::Object::New()); + + m_stringWrapper.init(); + m_contextWrapper.init(this); + m_qobjectWrapper.init(this); + m_typeWrapper.init(this); + m_listWrapper.init(this); + m_variantWrapper.init(this); + m_valueTypeWrapper.init(this); + m_sequenceWrapper.init(this); + + { + v8::Handle v = global()->Get(v8::String::New("Object"))->ToObject()->Get(v8::String::New("getOwnPropertyNames")); + m_getOwnPropertyNames = qPersistentNew(v8::Handle::Cast(v)); + } +} + +QV8Engine::~QV8Engine() +{ + Q_ASSERT_X(v8::Isolate::GetCurrent(), "QV8Engine::~QV8Engine()", "called after v8::Isolate has exited"); + for (int ii = 0; ii < m_extensionData.count(); ++ii) + delete m_extensionData[ii]; + m_extensionData.clear(); + + qt_rem_qmlxmlhttprequest(this, m_xmlHttpRequestData); + m_xmlHttpRequestData = 0; + delete m_listModelData; + m_listModelData = 0; + + qPersistentDispose(m_freezeObject); + qPersistentDispose(m_getOwnPropertyNames); + + invalidateAllValues(); + clearExceptions(); + + qPersistentDispose(m_strongReferencer); + + m_sequenceWrapper.destroy(); + m_valueTypeWrapper.destroy(); + m_variantWrapper.destroy(); + m_listWrapper.destroy(); + m_typeWrapper.destroy(); + m_qobjectWrapper.destroy(); + m_contextWrapper.destroy(); + m_stringWrapper.destroy(); + + m_originalGlobalObject.destroy(); + + if (m_ownsV8Context) + qPersistentDispose(m_context); +} + +QString QV8Engine::toStringStatic(v8::Handle jsstr) +{ + return toStringStatic(jsstr->ToString()); +} + +QString QV8Engine::toStringStatic(v8::Handle jsstr) +{ + QString qstr; + qstr.resize(jsstr->Length()); + jsstr->Write((uint16_t*)qstr.data()); + return qstr; +} + +QVariant QV8Engine::toVariant(v8::Handle value, int typeHint) +{ + if (value.IsEmpty()) + return QVariant(); + + if (typeHint == QVariant::Bool) + return QVariant(value->BooleanValue()); + + if (value->IsObject()) { + QV8ObjectResource *r = (QV8ObjectResource *)value->ToObject()->GetExternalResource(); + if (r) { + switch (r->resourceType()) { + case QV8ObjectResource::Context2DStyleType: + case QV8ObjectResource::Context2DPixelArrayType: + case QV8ObjectResource::SignalHandlerType: + case QV8ObjectResource::IncubatorType: + case QV8ObjectResource::VisualDataItemType: + case QV8ObjectResource::ContextType: + case QV8ObjectResource::XMLHttpRequestType: + case QV8ObjectResource::DOMNodeType: + case QV8ObjectResource::SQLDatabaseType: + case QV8ObjectResource::ListModelType: + case QV8ObjectResource::Context2DType: + case QV8ObjectResource::ParticleDataType: + case QV8ObjectResource::LocaleDataType: + return QVariant(); + case QV8ObjectResource::TypeType: + return m_typeWrapper.toVariant(r); + case QV8ObjectResource::QObjectType: + return qVariantFromValue(m_qobjectWrapper.toQObject(r)); + case QV8ObjectResource::ListType: + return m_listWrapper.toVariant(r); + case QV8ObjectResource::VariantType: + return m_variantWrapper.toVariant(r); + case QV8ObjectResource::ValueTypeType: + return m_valueTypeWrapper.toVariant(r); + case QV8ObjectResource::SequenceType: + return m_sequenceWrapper.toVariant(r); + } + } + } + + if (value->IsArray()) { + v8::Handle array = v8::Handle::Cast(value); + if (typeHint == qMetaTypeId >()) { + QList list; + uint32_t length = array->Length(); + for (uint32_t ii = 0; ii < length; ++ii) { + v8::Local arrayItem = array->Get(ii); + if (arrayItem->IsObject()) { + list << toQObject(arrayItem->ToObject()); + } else { + list << 0; + } + } + + return qVariantFromValue >(list); + } + + bool succeeded = false; + QVariant retn = m_sequenceWrapper.toVariant(array, typeHint, &succeeded); + if (succeeded) + return retn; + } + + return toBasicVariant(value); +} + +static v8::Handle arrayFromStringList(QV8Engine *engine, const QStringList &list) +{ + v8::Context::Scope scope(engine->context()); + v8::Local result = v8::Array::New(list.count()); + for (int ii = 0; ii < list.count(); ++ii) + result->Set(ii, engine->toString(list.at(ii))); + return result; +} + +static v8::Handle arrayFromVariantList(QV8Engine *engine, const QVariantList &list) +{ + v8::Context::Scope scope(engine->context()); + v8::Local result = v8::Array::New(list.count()); + for (int ii = 0; ii < list.count(); ++ii) + result->Set(ii, engine->fromVariant(list.at(ii))); + return result; +} + +static v8::Handle objectFromVariantMap(QV8Engine *engine, const QVariantMap &map) +{ + v8::Context::Scope scope(engine->context()); + v8::Local object = v8::Object::New(); + for (QVariantMap::ConstIterator iter = map.begin(); iter != map.end(); ++iter) + object->Set(engine->toString(iter.key()), engine->fromVariant(iter.value())); + return object; +} + +Q_CORE_EXPORT QString qt_regexp_toCanonical(const QString &, QRegExp::PatternSyntax); + +v8::Handle QV8Engine::fromVariant(const QVariant &variant) +{ + int type = variant.userType(); + const void *ptr = variant.constData(); + + if (type < QMetaType::User) { + switch (QMetaType::Type(type)) { + case QMetaType::Void: + return v8::Undefined(); + case QMetaType::Bool: + return v8::Boolean::New(*reinterpret_cast(ptr)); + case QMetaType::Int: + return v8::Integer::New(*reinterpret_cast(ptr)); + case QMetaType::UInt: + return v8::Integer::NewFromUnsigned(*reinterpret_cast(ptr)); + case QMetaType::LongLong: + return v8::Number::New(*reinterpret_cast(ptr)); + case QMetaType::ULongLong: + return v8::Number::New(*reinterpret_cast(ptr)); + case QMetaType::Double: + return v8::Number::New(*reinterpret_cast(ptr)); + case QMetaType::QString: + return m_stringWrapper.toString(*reinterpret_cast(ptr)); + case QMetaType::Float: + return v8::Number::New(*reinterpret_cast(ptr)); + case QMetaType::Short: + return v8::Integer::New(*reinterpret_cast(ptr)); + case QMetaType::UShort: + return v8::Integer::NewFromUnsigned(*reinterpret_cast(ptr)); + case QMetaType::Char: + return v8::Integer::New(*reinterpret_cast(ptr)); + case QMetaType::UChar: + return v8::Integer::NewFromUnsigned(*reinterpret_cast(ptr)); + case QMetaType::QChar: + return v8::Integer::New((*reinterpret_cast(ptr)).unicode()); + case QMetaType::QDateTime: + return v8::Date::New(qtDateTimeToJsDate(*reinterpret_cast(ptr))); + case QMetaType::QDate: + return v8::Date::New(qtDateTimeToJsDate(QDateTime(*reinterpret_cast(ptr)))); + case QMetaType::QTime: + return v8::Date::New(qtDateTimeToJsDate(QDateTime(QDate(1970,1,1), *reinterpret_cast(ptr)))); + case QMetaType::QRegExp: + return QJSConverter::toRegExp(*reinterpret_cast(ptr)); + case QMetaType::QObjectStar: + case QMetaType::QWidgetStar: + return newQObject(*reinterpret_cast(ptr)); + case QMetaType::QStringList: + { + bool succeeded = false; + v8::Handle retn = m_sequenceWrapper.fromVariant(variant, &succeeded); + if (succeeded) + return retn; + return arrayFromStringList(this, *reinterpret_cast(ptr)); + } + case QMetaType::QVariantList: + return arrayFromVariantList(this, *reinterpret_cast(ptr)); + case QMetaType::QVariantMap: + return objectFromVariantMap(this, *reinterpret_cast(ptr)); + + default: + break; + } + + if (m_engine) { + if (QQmlValueType *vt = QQmlEnginePrivate::get(m_engine)->valueTypes[type]) + return m_valueTypeWrapper.newValueType(variant, vt); + } + + } else { + if (type == qMetaTypeId()) { + typedef QQmlListReferencePrivate QDLRP; + QDLRP *p = QDLRP::get((QQmlListReference*)ptr); + if (p->object) { + return m_listWrapper.newList(p->property, p->propertyType); + } else { + return v8::Null(); + } + } else if (type == qMetaTypeId()) { + const QJSValue *value = reinterpret_cast(ptr); + QJSValuePrivate *valuep = QJSValuePrivate::get(*value); + if (valuep->assignEngine(this)) + return v8::Local::New(*valuep); + } else if (type == qMetaTypeId >()) { + // XXX Can this be made more by using Array as a prototype and implementing + // directly against QList? + const QList &list = *(QList*)ptr; + v8::Local array = v8::Array::New(list.count()); + for (int ii = 0; ii < list.count(); ++ii) + array->Set(ii, newQObject(list.at(ii))); + return array; + } + + bool objOk; + QObject *obj = QQmlMetaType::toQObject(variant, &objOk); + if (objOk) + return newQObject(obj); + + bool succeeded = false; + v8::Handle retn = m_sequenceWrapper.fromVariant(variant, &succeeded); + if (succeeded) + return retn; + } + + // XXX TODO: To be compatible, we still need to handle: + // + QObjectList + // + QList + + return m_variantWrapper.newVariant(variant); +} + +// A handle scope and context must be entered +v8::Local QV8Engine::qmlModeCompile(const QString &source, + const QString &fileName, + int lineNumber) +{ + v8::Local v8source = m_stringWrapper.toString(source); + v8::Local v8fileName = m_stringWrapper.toString(fileName); + + v8::ScriptOrigin origin(v8fileName, v8::Integer::New(lineNumber - 1)); + + v8::Local script = v8::Script::Compile(v8source, &origin, 0, v8::Handle(), + v8::Script::QmlMode); + + return script; +} + +// A handle scope and context must be entered. +// source can be either ascii or utf8. +v8::Local QV8Engine::qmlModeCompile(const char *source, int sourceLength, + const QString &fileName, + int lineNumber) +{ + if (sourceLength == -1) + sourceLength = strlen(source); + + v8::Local v8source = v8::String::New(source, sourceLength); + v8::Local v8fileName = m_stringWrapper.toString(fileName); + + v8::ScriptOrigin origin(v8fileName, v8::Integer::New(lineNumber - 1)); + + v8::Local script = v8::Script::Compile(v8source, &origin, 0, v8::Handle(), + v8::Script::QmlMode); + + return script; +} + +QNetworkAccessManager *QV8Engine::networkAccessManager() +{ + return QQmlEnginePrivate::get(m_engine)->getNetworkAccessManager(); +} + +const QStringHash &QV8Engine::illegalNames() const +{ + return m_illegalNames; +} + +// Requires a handle scope +v8::Local QV8Engine::getOwnPropertyNames(v8::Handle o) +{ + // FIXME Newer v8 have API for this function + v8::TryCatch tc; + v8::Handle args[] = { o }; + v8::Local r = m_getOwnPropertyNames->Call(global(), 1, args); + if (tc.HasCaught()) + return v8::Array::New(); + else + return v8::Local::Cast(r); +} + +QQmlContextData *QV8Engine::callingContext() +{ + return m_contextWrapper.callingContext(); +} + +// Converts a JS value to a QVariant. +// Null, Undefined -> QVariant() (invalid) +// Boolean -> QVariant(bool) +// Number -> QVariant(double) +// String -> QVariant(QString) +// Array -> QVariantList(...) +// Date -> QVariant(QDateTime) +// RegExp -> QVariant(QRegExp) +// [Any other object] -> QVariantMap(...) +QVariant QV8Engine::toBasicVariant(v8::Handle value) +{ + if (value->IsNull() || value->IsUndefined()) + return QVariant(); + if (value->IsBoolean()) + return value->ToBoolean()->Value(); + if (value->IsInt32()) + return value->ToInt32()->Value(); + if (value->IsNumber()) + return value->ToNumber()->Value(); + if (value->IsString()) + return m_stringWrapper.toString(value->ToString()); + if (value->IsDate()) + return qtDateTimeFromJsDate(v8::Handle::Cast(value)->NumberValue()); + // NOTE: since we convert QTime to JS Date, round trip will change the variant type (to QDateTime)! + + Q_ASSERT(value->IsObject()); + + if (value->IsRegExp()) { + v8::Context::Scope scope(context()); + return QJSConverter::toRegExp(v8::Handle::Cast(value)); + } + if (value->IsArray()) { + v8::Context::Scope scope(context()); + QVariantList rv; + + v8::Handle array = v8::Handle::Cast(value); + int length = array->Length(); + for (int ii = 0; ii < length; ++ii) + rv << toVariant(array->Get(ii), -1); + return rv; + } + if (!value->IsFunction()) { + v8::Context::Scope scope(context()); + v8::Handle object = value->ToObject(); + return variantMapFromJS(object); + } + + return QVariant(); +} + + + +#include +#include + +struct StaticQtMetaObject : public QObject +{ + static const QMetaObject *get() + { return &static_cast (0)->staticQtMetaObject; } +}; + +void QV8Engine::initializeGlobal(v8::Handle global) +{ + using namespace QQmlBuiltinFunctions; + + v8::Local console = v8::Object::New(); + v8::Local consoleLogFn = V8FUNCTION(consoleLog, this); + + console->Set(v8::String::New("debug"), consoleLogFn); + console->Set(v8::String::New("log"), consoleLogFn); + console->Set(v8::String::New("info"), consoleLogFn); + console->Set(v8::String::New("warn"), V8FUNCTION(consoleWarn, this)); + console->Set(v8::String::New("error"), V8FUNCTION(consoleError, this)); + console->Set(v8::String::New("assert"), V8FUNCTION(consoleAssert, this)); + + console->Set(v8::String::New("count"), V8FUNCTION(consoleCount, this)); + console->Set(v8::String::New("profile"), V8FUNCTION(consoleProfile, this)); + console->Set(v8::String::New("profileEnd"), V8FUNCTION(consoleProfileEnd, this)); + console->Set(v8::String::New("time"), V8FUNCTION(consoleTime, this)); + console->Set(v8::String::New("timeEnd"), V8FUNCTION(consoleTimeEnd, this)); + console->Set(v8::String::New("trace"), V8FUNCTION(consoleTrace, this)); + console->Set(v8::String::New("exception"), V8FUNCTION(consoleException, this)); + + v8::Local qt = v8::Object::New(); + + // Set all the enums from the "Qt" namespace + const QMetaObject *qtMetaObject = StaticQtMetaObject::get(); + for (int ii = 0; ii < qtMetaObject->enumeratorCount(); ++ii) { + QMetaEnum enumerator = qtMetaObject->enumerator(ii); + for (int jj = 0; jj < enumerator.keyCount(); ++jj) { + qt->Set(v8::String::New(enumerator.key(jj)), v8::Integer::New(enumerator.value(jj))); + } + } + qt->Set(v8::String::New("Asynchronous"), v8::Integer::New(0)); + qt->Set(v8::String::New("Synchronous"), v8::Integer::New(1)); + + qt->Set(v8::String::New("include"), V8FUNCTION(QV8Include::include, this)); + qt->Set(v8::String::New("isQtObject"), V8FUNCTION(isQtObject, this)); + qt->Set(v8::String::New("rgba"), V8FUNCTION(rgba, this)); + qt->Set(v8::String::New("hsla"), V8FUNCTION(hsla, this)); + qt->Set(v8::String::New("rect"), V8FUNCTION(rect, this)); + qt->Set(v8::String::New("point"), V8FUNCTION(point, this)); + qt->Set(v8::String::New("size"), V8FUNCTION(size, this)); + qt->Set(v8::String::New("vector3d"), V8FUNCTION(vector3d, this)); + qt->Set(v8::String::New("vector4d"), V8FUNCTION(vector4d, this)); + + qt->Set(v8::String::New("formatDate"), V8FUNCTION(formatDate, this)); + qt->Set(v8::String::New("formatTime"), V8FUNCTION(formatTime, this)); + qt->Set(v8::String::New("formatDateTime"), V8FUNCTION(formatDateTime, this)); + + qt->Set(v8::String::New("openUrlExternally"), V8FUNCTION(openUrlExternally, this)); + qt->Set(v8::String::New("fontFamilies"), V8FUNCTION(fontFamilies, this)); + qt->Set(v8::String::New("md5"), V8FUNCTION(md5, this)); + qt->Set(v8::String::New("btoa"), V8FUNCTION(btoa, this)); + qt->Set(v8::String::New("atob"), V8FUNCTION(atob, this)); + qt->Set(v8::String::New("resolvedUrl"), V8FUNCTION(resolvedUrl, this)); + qt->Set(v8::String::New("locale"), V8FUNCTION(locale, this)); + + if (m_engine) { + qt->Set(v8::String::New("application"), newQObject(new QQuickApplication(m_engine))); + qt->Set(v8::String::New("inputMethod"), newQObject(qGuiApp->inputMethod(), CppOwnership)); + qt->Set(v8::String::New("lighter"), V8FUNCTION(lighter, this)); + qt->Set(v8::String::New("darker"), V8FUNCTION(darker, this)); + qt->Set(v8::String::New("tint"), V8FUNCTION(tint, this)); + qt->Set(v8::String::New("quit"), V8FUNCTION(quit, this)); + qt->Set(v8::String::New("createQmlObject"), V8FUNCTION(createQmlObject, this)); + qt->Set(v8::String::New("createComponent"), V8FUNCTION(createComponent, this)); + } + + global->Set(v8::String::New("qsTranslate"), V8FUNCTION(qsTranslate, this)); + global->Set(v8::String::New("QT_TRANSLATE_NOOP"), V8FUNCTION(qsTranslateNoOp, this)); + global->Set(v8::String::New("qsTr"), V8FUNCTION(qsTr, this)); + global->Set(v8::String::New("QT_TR_NOOP"), V8FUNCTION(qsTrNoOp, this)); + global->Set(v8::String::New("qsTrId"), V8FUNCTION(qsTrId, this)); + global->Set(v8::String::New("QT_TRID_NOOP"), V8FUNCTION(qsTrIdNoOp, this)); + + global->Set(v8::String::New("print"), consoleLogFn); + global->Set(v8::String::New("console"), console); + global->Set(v8::String::New("Qt"), qt); + global->Set(v8::String::New("gc"), V8FUNCTION(QQmlBuiltinFunctions::gc, this)); + + { +#define STRING_ARG "(function(stringArg) { "\ + " String.prototype.arg = (function() {"\ + " return stringArg.apply(this, arguments);"\ + " })"\ + "})" + + v8::Local registerArg = v8::Script::New(v8::String::New(STRING_ARG), 0, 0, v8::Handle(), v8::Script::NativeMode); + v8::Local result = registerArg->Run(); + Q_ASSERT(result->IsFunction()); + v8::Local registerArgFunc = v8::Local::Cast(result); + v8::Handle args = V8FUNCTION(stringArg, this); + registerArgFunc->Call(v8::Local::Cast(registerArgFunc), 1, &args); +#undef STRING_ARG + } + + QQmlLocale::registerStringLocaleCompare(this); + QQmlDateExtension::registerExtension(this); + QQmlNumberExtension::registerExtension(this); + + qt_add_domexceptions(this); + m_xmlHttpRequestData = qt_add_qmlxmlhttprequest(this); + + qt_add_sqlexceptions(this); + + { + v8::Handle args[] = { global }; + v8::Local names = m_getOwnPropertyNames->Call(global, 1, args); + v8::Local namesArray = v8::Local::Cast(names); + for (quint32 ii = 0; ii < namesArray->Length(); ++ii) + m_illegalNames.insert(toString(namesArray->Get(ii)), true); + } + + { +#define FREEZE_SOURCE "(function freeze_recur(obj) { "\ + " if (Qt.isQtObject(obj)) return;"\ + " if (obj != Function.connect && obj != Function.disconnect && "\ + " obj instanceof Object) {"\ + " var properties = Object.getOwnPropertyNames(obj);"\ + " for (var prop in properties) { "\ + " if (prop == \"connect\" || prop == \"disconnect\") {"\ + " Object.freeze(obj[prop]); "\ + " continue;"\ + " }"\ + " freeze_recur(obj[prop]);"\ + " }"\ + " }"\ + " if (obj instanceof Object) {"\ + " Object.freeze(obj);"\ + " }"\ + "})" + + v8::Local freeze = v8::Script::New(v8::String::New(FREEZE_SOURCE)); + v8::Local result = freeze->Run(); + Q_ASSERT(result->IsFunction()); + m_freezeObject = qPersistentNew(v8::Local::Cast(result)); +#undef FREEZE_SOURCE + } +} + +void QV8Engine::freezeObject(v8::Handle value) +{ + v8::Handle args[] = { value }; + m_freezeObject->Call(global(), 1, args); +} + +void QV8Engine::gc() +{ + v8::V8::LowMemoryNotification(); + while (!v8::V8::IdleNotification()) {} +} + +#ifdef QML_GLOBAL_HANDLE_DEBUGGING +#include +static QThreadStorage *> QV8Engine_activeHandles; + +void QV8Engine::registerHandle(void *handle) +{ + if (!handle) { + qWarning("Attempting to register a null handle"); + return; + } + + if (!QV8Engine_activeHandles.hasLocalData()) + QV8Engine_activeHandles.setLocalData(new QSet); + + if (QV8Engine_activeHandles.localData()->contains(handle)) { + qFatal("Handle %p already alive", handle); + } else { + QV8Engine_activeHandles.localData()->insert(handle); + } +} + +void QV8Engine::releaseHandle(void *handle) +{ + if (!handle) + return; + + if (!QV8Engine_activeHandles.hasLocalData()) + QV8Engine_activeHandles.setLocalData(new QSet); + + if (QV8Engine_activeHandles.localData()->contains(handle)) { + QV8Engine_activeHandles.localData()->remove(handle); + } else { + qFatal("Handle %p already dead", handle); + } +} +#endif + +struct QV8EngineRegistrationData +{ + QV8EngineRegistrationData() : extensionCount(0) {} + + QMutex mutex; + int extensionCount; +}; +Q_GLOBAL_STATIC(QV8EngineRegistrationData, registrationData); + +QMutex *QV8Engine::registrationMutex() +{ + return ®istrationData()->mutex; +} + +int QV8Engine::registerExtension() +{ + return registrationData()->extensionCount++; +} + +void QV8Engine::setExtensionData(int index, Deletable *data) +{ + if (m_extensionData.count() <= index) + m_extensionData.resize(index + 1); + + if (m_extensionData.at(index)) + delete m_extensionData.at(index); + + m_extensionData[index] = data; +} + +double QV8Engine::qtDateTimeToJsDate(const QDateTime &dt) +{ + // from QScriptEngine::DateTimeToMs() + if (!dt.isValid()) { + return qSNaN(); + } + QDateTime utc = dt.toUTC(); + QDate date = utc.date(); + QTime time = utc.time(); + QV8DateConverter::JSC::GregorianDateTime tm; + tm.year = date.year() - 1900; + tm.month = date.month() - 1; + tm.monthDay = date.day(); + tm.weekDay = date.dayOfWeek(); + tm.yearDay = date.dayOfYear(); + tm.hour = time.hour(); + tm.minute = time.minute(); + tm.second = time.second(); + return QV8DateConverter::JSC::gregorianDateTimeToMS(tm, time.msec()); +} + +v8::Persistent *QV8Engine::findOwnerAndStrength(QObject *object, bool *shouldBeStrong) +{ + QObject *parent = object->parent(); + if (!parent) { + // if the object has JS ownership, the object's v8object owns the lifetime of the persistent value. + if (QQmlEngine::objectOwnership(object) == QQmlEngine::JavaScriptOwnership) { + *shouldBeStrong = false; + return &(QQmlData::get(object)->v8object); + } + + // no parent, and has CPP ownership - doesn't have an implicit parent. + *shouldBeStrong = true; + return 0; + } + + // if it is owned by CPP, it's root parent may still be owned by JS. + // in that case, the owner of the persistent handle is the root parent's v8object. + while (parent->parent()) + parent = parent->parent(); + + if (QQmlEngine::objectOwnership(parent) == QQmlEngine::JavaScriptOwnership) { + // root parent is owned by JS. It's v8object owns the persistent value in question. + *shouldBeStrong = false; + return &(QQmlData::get(parent)->v8object); + } else { + // root parent has CPP ownership. The persistent value should not be made weak. + *shouldBeStrong = true; + return 0; + } +} + +QDateTime QV8Engine::qtDateTimeFromJsDate(double jsDate) +{ + // from QScriptEngine::MsToDateTime() + if (qIsNaN(jsDate)) + return QDateTime(); + QV8DateConverter::JSC::GregorianDateTime tm; + QV8DateConverter::JSC::msToGregorianDateTime(jsDate, tm); + + // from QScriptEngine::MsFromTime() + int ms = int(::fmod(jsDate, 1000.0)); + if (ms < 0) + ms += int(1000.0); + + QDateTime convertedUTC = QDateTime(QDate(tm.year + 1900, tm.month + 1, tm.monthDay), + QTime(tm.hour, tm.minute, tm.second, ms), Qt::UTC); + return convertedUTC.toLocalTime(); +} + +void QV8Engine::addRelationshipForGC(QObject *object, v8::Persistent handle) +{ + if (handle.IsEmpty()) + return; + + bool handleShouldBeStrong = false; + v8::Persistent *implicitOwner = findOwnerAndStrength(object, &handleShouldBeStrong); + if (handleShouldBeStrong) { + v8::V8::AddImplicitReferences(m_strongReferencer, &handle, 1); + } else if (!implicitOwner->IsEmpty()) { + v8::V8::AddImplicitReferences(*implicitOwner, &handle, 1); + } +} + +void QV8Engine::addRelationshipForGC(QObject *object, QObject *other) +{ + bool handleShouldBeStrong = false; + v8::Persistent *implicitOwner = findOwnerAndStrength(object, &handleShouldBeStrong); + v8::Persistent handle = QQmlData::get(other, true)->v8object; + if (handleShouldBeStrong) { + v8::V8::AddImplicitReferences(m_strongReferencer, &handle, 1); + } else if (!implicitOwner->IsEmpty()) { + v8::V8::AddImplicitReferences(*implicitOwner, &handle, 1); + } +} + +static QThreadStorage perThreadEngineData; + +bool QV8Engine::hasThreadData() +{ + return perThreadEngineData.hasLocalData(); +} + +QV8Engine::ThreadData *QV8Engine::threadData() +{ + Q_ASSERT(perThreadEngineData.hasLocalData()); + return perThreadEngineData.localData(); +} + +void QV8Engine::ensurePerThreadIsolate() +{ + if (!perThreadEngineData.hasLocalData()) + perThreadEngineData.setLocalData(new ThreadData); +} + +void QV8Engine::initDeclarativeGlobalObject() +{ + v8::HandleScope handels; + v8::Context::Scope contextScope(m_context); + initializeGlobal(m_context->Global()); + freezeObject(m_context->Global()); +} + +void QV8Engine::setEngine(QQmlEngine *engine) +{ + m_engine = engine; + initDeclarativeGlobalObject(); +} + +void QV8Engine::setException(v8::Handle value, v8::Handle msg) +{ + m_exception.set(value, msg); +} + +v8::Handle QV8Engine::throwException(v8::Handle value) +{ + setException(value); + v8::ThrowException(value); + return value; +} + +void QV8Engine::clearExceptions() +{ + m_exception.clear(); +} + +v8::Handle QV8Engine::uncaughtException() const +{ + if (!hasUncaughtException()) + return v8::Handle(); + return m_exception; +} + +bool QV8Engine::hasUncaughtException() const +{ + return m_exception; +} + +int QV8Engine::uncaughtExceptionLineNumber() const +{ + return m_exception.lineNumber(); +} + +QStringList QV8Engine::uncaughtExceptionBacktrace() const +{ + return m_exception.backtrace(); +} + +/*! + \internal + Save the current exception on stack so it can be set again later. + \sa QV8Engine::restoreException +*/ +void QV8Engine::saveException() +{ + m_exception.push(); +} + +/*! + \internal + Load a saved exception from stack. Current exception, if exists will be dropped + \sa QV8Engine::saveException +*/ +void QV8Engine::restoreException() +{ + m_exception.pop(); +} + +QV8Engine::Exception::Exception() {} + +QV8Engine::Exception::~Exception() +{ + Q_ASSERT_X(m_stack.isEmpty(), Q_FUNC_INFO, "Some saved exceptions left. Asymetric pop/push found."); + clear(); +} + +void QV8Engine::Exception::set(v8::Handle value, v8::Handle message) +{ + Q_ASSERT_X(!value.IsEmpty(), Q_FUNC_INFO, "Throwing an empty value handle is highly suspected"); + clear(); + m_value = v8::Persistent::New(value); + m_message = v8::Persistent::New(message); +} + +void QV8Engine::Exception::clear() +{ + m_value.Dispose(); + m_value.Clear(); + m_message.Dispose(); + m_message.Clear(); +} + +QV8Engine::Exception::operator bool() const +{ + return !m_value.IsEmpty(); +} + +QV8Engine::Exception::operator v8::Handle() const +{ + Q_ASSERT(*this); + return m_value; +} + +int QV8Engine::Exception::lineNumber() const +{ + if (m_message.IsEmpty()) + return -1; + return m_message->GetLineNumber(); +} + +QStringList QV8Engine::Exception::backtrace() const +{ + if (m_message.IsEmpty()) + return QStringList(); + + QStringList backtrace; + v8::Handle trace = m_message->GetStackTrace(); + if (trace.IsEmpty()) + // FIXME it should not happen (SetCaptureStackTraceForUncaughtExceptions is called). + return QStringList(); + + for (int i = 0; i < trace->GetFrameCount(); ++i) { + v8::Local frame = trace->GetFrame(i); + backtrace.append(QJSConverter::toString(frame->GetFunctionName())); + backtrace.append(QJSConverter::toString(frame->GetFunctionName())); + backtrace.append(QString::fromAscii("()@")); + backtrace.append(QJSConverter::toString(frame->GetScriptName())); + backtrace.append(QString::fromAscii(":")); + backtrace.append(QString::number(frame->GetLineNumber())); + } + return backtrace; +} + +void QV8Engine::Exception::push() +{ + m_stack.push(qMakePair(m_value, m_message)); + m_value.Clear(); + m_message.Clear(); +} + +void QV8Engine::Exception::pop() +{ + Q_ASSERT_X(!m_stack.empty(), Q_FUNC_INFO, "Attempt to load unsaved exception found"); + ValueMessagePair pair = m_stack.pop(); + clear(); + m_value = pair.first; + m_message = pair.second; +} + + +// Converts a QVariantList to JS. +// The result is a new Array object with length equal to the length +// of the QVariantList, and the elements being the QVariantList's +// elements converted to JS, recursively. +v8::Local QV8Engine::variantListToJS(const QVariantList &lst) +{ + v8::Local result = v8::Array::New(lst.size()); + for (int i = 0; i < lst.size(); ++i) + result->Set(i, variantToJS(lst.at(i))); + return result; +} + +// Converts a JS Array object to a QVariantList. +// The result is a QVariantList with length equal to the length +// of the JS Array, and elements being the JS Array's elements +// converted to QVariants, recursively. +QVariantList QV8Engine::variantListFromJS(v8::Handle jsArray) +{ + QVariantList result; + int hash = jsArray->GetIdentityHash(); + if (visitedConversionObjects.contains(hash)) + return result; // Avoid recursion. + v8::HandleScope handleScope; + visitedConversionObjects.insert(hash); + uint32_t length = jsArray->Length(); + for (uint32_t i = 0; i < length; ++i) + result.append(variantFromJS(jsArray->Get(i))); + visitedConversionObjects.remove(hash); + return result; +} + +// Converts a QVariantMap to JS. +// The result is a new Object object with property names being +// the keys of the QVariantMap, and values being the values of +// the QVariantMap converted to JS, recursively. +v8::Local QV8Engine::variantMapToJS(const QVariantMap &vmap) +{ + v8::Local result = v8::Object::New(); + QVariantMap::const_iterator it; + for (it = vmap.constBegin(); it != vmap.constEnd(); ++it) + result->Set(QJSConverter::toString(it.key()), variantToJS(it.value())); + return result; +} + +// Converts a JS Object to a QVariantMap. +// The result is a QVariantMap with keys being the property names +// of the object, and values being the values of the JS object's +// properties converted to QVariants, recursively. +QVariantMap QV8Engine::variantMapFromJS(v8::Handle jsObject) +{ + QVariantMap result; + + v8::HandleScope handleScope; + v8::Handle propertyNames = jsObject->GetPropertyNames(); + uint32_t length = propertyNames->Length(); + if (length == 0) + return result; + + int hash = jsObject->GetIdentityHash(); + if (visitedConversionObjects.contains(hash)) + return result; // Avoid recursion. + + visitedConversionObjects.insert(hash); + // TODO: Only object's own property names. Include non-enumerable properties. + for (uint32_t i = 0; i < length; ++i) { + v8::Handle name = propertyNames->Get(i); + result.insert(QJSConverter::toString(name->ToString()), variantFromJS(jsObject->Get(name))); + } + visitedConversionObjects.remove(hash); + return result; +} + +// Converts the meta-type defined by the given type and data to JS. +// Returns the value if conversion succeeded, an empty handle otherwise. +v8::Handle QV8Engine::metaTypeToJS(int type, const void *data) +{ + Q_ASSERT(data != 0); + v8::Handle result; + + // check if it's one of the types we know + switch (QMetaType::Type(type)) { + case QMetaType::Void: + return v8::Undefined(); + case QMetaType::Bool: + return v8::Boolean::New(*reinterpret_cast(data)); + case QMetaType::Int: + return v8::Int32::New(*reinterpret_cast(data)); + case QMetaType::UInt: + return v8::Uint32::New(*reinterpret_cast(data)); + case QMetaType::LongLong: + return v8::Number::New(double(*reinterpret_cast(data))); + case QMetaType::ULongLong: +#if defined(Q_OS_WIN) && defined(_MSC_FULL_VER) && _MSC_FULL_VER <= 12008804 +#pragma message("** NOTE: You need the Visual Studio Processor Pack to compile support for 64bit unsigned integers.") + return v8::Number::New(double((qlonglong)*reinterpret_cast(data))); +#elif defined(Q_CC_MSVC) && !defined(Q_CC_MSVC_NET) + return v8::Number::New(double((qlonglong)*reinterpret_cast(data))); +#else + return v8::Number::New(double(*reinterpret_cast(data))); +#endif + case QMetaType::Double: + return v8::Number::New(double(*reinterpret_cast(data))); + case QMetaType::QString: + return QJSConverter::toString(*reinterpret_cast(data)); + case QMetaType::Float: + return v8::Number::New(*reinterpret_cast(data)); + case QMetaType::Short: + return v8::Int32::New(*reinterpret_cast(data)); + case QMetaType::UShort: + return v8::Uint32::New(*reinterpret_cast(data)); + case QMetaType::Char: + return v8::Int32::New(*reinterpret_cast(data)); + case QMetaType::UChar: + return v8::Uint32::New(*reinterpret_cast(data)); + case QMetaType::QChar: + return v8::Uint32::New((*reinterpret_cast(data)).unicode()); + case QMetaType::QStringList: + result = QJSConverter::toStringList(*reinterpret_cast(data)); + break; + case QMetaType::QVariantList: + result = variantListToJS(*reinterpret_cast(data)); + break; + case QMetaType::QVariantMap: + result = variantMapToJS(*reinterpret_cast(data)); + break; + case QMetaType::QDateTime: + result = QJSConverter::toDateTime(*reinterpret_cast(data)); + break; + case QMetaType::QDate: + result = QJSConverter::toDateTime(QDateTime(*reinterpret_cast(data))); + break; + case QMetaType::QRegExp: + result = QJSConverter::toRegExp(*reinterpret_cast(data)); + break; + case QMetaType::QObjectStar: + case QMetaType::QWidgetStar: + result = newQObject(*reinterpret_cast(data)); + break; + case QMetaType::QVariant: + result = variantToJS(*reinterpret_cast(data)); + break; + default: + if (type == qMetaTypeId()) { + return QJSValuePrivate::get(*reinterpret_cast(data))->asV8Value(this); + } else { + QByteArray typeName = QMetaType::typeName(type); + if (typeName.endsWith('*') && !*reinterpret_cast(data)) { + return v8::Null(); + } else { + // Fall back to wrapping in a QVariant. + result = newVariant(QVariant(type, data)); + } + } + } + return result; +} + +// Converts a JS value to a meta-type. +// data must point to a place that can store a value of the given type. +// Returns true if conversion succeeded, false otherwise. +bool QV8Engine::metaTypeFromJS(v8::Handle value, int type, void *data) { + // check if it's one of the types we know + switch (QMetaType::Type(type)) { + case QMetaType::Bool: + *reinterpret_cast(data) = value->ToBoolean()->Value(); + return true; + case QMetaType::Int: + *reinterpret_cast(data) = value->ToInt32()->Value(); + return true; + case QMetaType::UInt: + *reinterpret_cast(data) = value->ToUint32()->Value(); + return true; + case QMetaType::LongLong: + *reinterpret_cast(data) = qlonglong(value->ToInteger()->Value()); + return true; + case QMetaType::ULongLong: + *reinterpret_cast(data) = qulonglong(value->ToInteger()->Value()); + return true; + case QMetaType::Double: + *reinterpret_cast(data) = value->ToNumber()->Value(); + return true; + case QMetaType::QString: + if (value->IsUndefined() || value->IsNull()) + *reinterpret_cast(data) = QString(); + else + *reinterpret_cast(data) = QJSConverter::toString(value->ToString()); + return true; + case QMetaType::Float: + *reinterpret_cast(data) = value->ToNumber()->Value(); + return true; + case QMetaType::Short: + *reinterpret_cast(data) = short(value->ToInt32()->Value()); + return true; + case QMetaType::UShort: + *reinterpret_cast(data) = ushort(value->ToInt32()->Value()); // ### QScript::ToUInt16() + return true; + case QMetaType::Char: + *reinterpret_cast(data) = char(value->ToInt32()->Value()); + return true; + case QMetaType::UChar: + *reinterpret_cast(data) = (unsigned char)(value->ToInt32()->Value()); + return true; + case QMetaType::QChar: + if (value->IsString()) { + QString str = QJSConverter::toString(v8::Handle::Cast(value)); + *reinterpret_cast(data) = str.isEmpty() ? QChar() : str.at(0); + } else { + *reinterpret_cast(data) = QChar(ushort(value->ToInt32()->Value())); // ### QScript::ToUInt16() + } + return true; + case QMetaType::QDateTime: + if (value->IsDate()) { + *reinterpret_cast(data) = QJSConverter::toDateTime(v8::Handle::Cast(value)); + return true; + } break; + case QMetaType::QDate: + if (value->IsDate()) { + *reinterpret_cast(data) = QJSConverter::toDateTime(v8::Handle::Cast(value)).date(); + return true; + } break; + case QMetaType::QRegExp: + if (value->IsRegExp()) { + *reinterpret_cast(data) = QJSConverter::toRegExp(v8::Handle::Cast(value)); + return true; + } break; + case QMetaType::QObjectStar: + if (isQObject(value) || value->IsNull()) { + *reinterpret_cast(data) = qtObjectFromJS(value); + return true; + } break; + case QMetaType::QWidgetStar: + if (isQObject(value) || value->IsNull()) { + QObject *qo = qtObjectFromJS(value); + if (!qo || qo->isWidgetType()) { + *reinterpret_cast(data) = reinterpret_cast(qo); + return true; + } + } break; + case QMetaType::QStringList: + if (value->IsArray()) { + *reinterpret_cast(data) = QJSConverter::toStringList(v8::Handle::Cast(value)); + return true; + } break; + case QMetaType::QVariantList: + if (value->IsArray()) { + *reinterpret_cast(data) = variantListFromJS(v8::Handle::Cast(value)); + return true; + } break; + case QMetaType::QVariantMap: + if (value->IsObject()) { + *reinterpret_cast(data) = variantMapFromJS(v8::Handle::Cast(value)); + return true; + } break; + case QMetaType::QVariant: + *reinterpret_cast(data) = variantFromJS(value); + return true; + default: + ; + } + +#if 0 + if (isQtVariant(value)) { + const QVariant &var = variantValue(value); + // ### Enable once constructInPlace() is in qt master. + if (var.userType() == type) { + QMetaType::constructInPlace(type, data, var.constData()); + return true; + } + if (var.canConvert(QVariant::Type(type))) { + QVariant vv = var; + vv.convert(QVariant::Type(type)); + Q_ASSERT(vv.userType() == type); + QMetaType::constructInPlace(type, data, vv.constData()); + return true; + } + + } +#endif + + // Try to use magic; for compatibility with qscriptvalue_cast. + + QByteArray name = QMetaType::typeName(type); + if (convertToNativeQObject(value, name, reinterpret_cast(data))) + return true; + if (isVariant(value) && name.endsWith('*')) { + int valueType = QMetaType::type(name.left(name.size()-1)); + QVariant &var = variantValue(value); + if (valueType == var.userType()) { + // We have T t, T* is requested, so return &t. + *reinterpret_cast(data) = var.data(); + return true; + } else { + // Look in the prototype chain. + v8::Handle proto = value->ToObject()->GetPrototype(); + while (proto->IsObject()) { + bool canCast = false; + if (isVariant(proto)) { + canCast = (type == variantValue(proto).userType()) + || (valueType && (valueType == variantValue(proto).userType())); + } + else if (isQObject(proto)) { + QByteArray className = name.left(name.size()-1); + if (QObject *qobject = qtObjectFromJS(proto)) + canCast = qobject->qt_metacast(className) != 0; + } + if (canCast) { + QByteArray varTypeName = QMetaType::typeName(var.userType()); + if (varTypeName.endsWith('*')) + *reinterpret_cast(data) = *reinterpret_cast(var.data()); + else + *reinterpret_cast(data) = var.data(); + return true; + } + proto = proto->ToObject()->GetPrototype(); + } + } + } else if (value->IsNull() && name.endsWith('*')) { + *reinterpret_cast(data) = 0; + return true; + } else if (type == qMetaTypeId()) { + *reinterpret_cast(data) = QJSValuePrivate::get(new QJSValuePrivate(this, value)); + return true; + } + + return false; +} + +// Converts a QVariant to JS. +v8::Handle QV8Engine::variantToJS(const QVariant &value) +{ + return metaTypeToJS(value.userType(), value.constData()); +} + +// Converts a JS value to a QVariant. +// Null, Undefined -> QVariant() (invalid) +// Boolean -> QVariant(bool) +// Number -> QVariant(double) +// String -> QVariant(QString) +// Array -> QVariantList(...) +// Date -> QVariant(QDateTime) +// RegExp -> QVariant(QRegExp) +// [Any other object] -> QVariantMap(...) +QVariant QV8Engine::variantFromJS(v8::Handle value) +{ + Q_ASSERT(!value.IsEmpty()); + if (value->IsNull() || value->IsUndefined()) + return QVariant(); + if (value->IsBoolean()) + return value->ToBoolean()->Value(); + if (value->IsInt32()) + return value->ToInt32()->Value(); + if (value->IsNumber()) + return value->ToNumber()->Value(); + if (value->IsString()) + return QJSConverter::toString(value->ToString()); + Q_ASSERT(value->IsObject()); + if (value->IsArray()) + return variantListFromJS(v8::Handle::Cast(value)); + if (value->IsDate()) + return QJSConverter::toDateTime(v8::Handle::Cast(value)); + if (value->IsRegExp()) + return QJSConverter::toRegExp(v8::Handle::Cast(value)); + if (isVariant(value)) + return variantValue(value); + if (isQObject(value)) + return qVariantFromValue(qtObjectFromJS(value)); + return variantMapFromJS(value->ToObject()); +} + +bool QV8Engine::convertToNativeQObject(v8::Handle value, + const QByteArray &targetType, + void **result) +{ + if (!targetType.endsWith('*')) + return false; + if (QObject *qobject = qtObjectFromJS(value)) { + int start = targetType.startsWith("const ") ? 6 : 0; + QByteArray className = targetType.mid(start, targetType.size()-start-1); + if (void *instance = qobject->qt_metacast(className)) { + *result = instance; + return true; + } + } + return false; +} + +QObject *QV8Engine::qtObjectFromJS(v8::Handle value) +{ + if (!value->IsObject()) + return 0; + + QV8ObjectResource *r = (QV8ObjectResource *)value->ToObject()->GetExternalResource(); + if (!r) + return 0; + QV8ObjectResource::ResourceType type = r->resourceType(); + if (type == QV8ObjectResource::QObjectType) + return qobjectWrapper()->toQObject(r); + else if (type == QV8ObjectResource::VariantType) { + QVariant variant = variantWrapper()->toVariant(r); + int type = variant.userType(); + if ((type == QMetaType::QObjectStar) || (type == QMetaType::QWidgetStar)) + return *reinterpret_cast(variant.constData()); + } + return 0; +} + + +QVariant &QV8Engine::variantValue(v8::Handle value) +{ + return variantWrapper()->variantValue(value); +} + +// Creates a QVariant wrapper object. +v8::Local QV8Engine::newVariant(const QVariant &value) +{ + return variantWrapper()->newVariant(value); +} + +QScriptPassPointer QV8Engine::evaluate(v8::Handle script, v8::TryCatch& tryCatch) +{ + v8::HandleScope handleScope; + + clearExceptions(); + if (script.IsEmpty()) { + v8::Handle exception = tryCatch.Exception(); + if (exception.IsEmpty()) { + // This is possible on syntax errors like { a:12, b:21 } <- missing "(", ")" around expression. + return new QJSValuePrivate(this); + } + setException(exception, tryCatch.Message()); + return new QJSValuePrivate(this, exception); + } + v8::Handle result; + result = script->Run(); + if (result.IsEmpty()) { + v8::Handle exception = tryCatch.Exception(); + // TODO: figure out why v8 doesn't always produce an exception value + //Q_ASSERT(!exception.IsEmpty()); + if (exception.IsEmpty()) + exception = v8::Exception::Error(v8::String::New("missing exception value")); + setException(exception, tryCatch.Message()); + return new QJSValuePrivate(this, exception); + } + return new QJSValuePrivate(this, result); +} + +QJSValue QV8Engine::scriptValueFromInternal(v8::Handle value) const +{ + if (value.IsEmpty()) + return QJSValuePrivate::get(new QJSValuePrivate(const_cast(this))); + return QJSValuePrivate::get(new QJSValuePrivate(const_cast(this), value)); +} + +QScriptPassPointer QV8Engine::newArray(uint length) +{ + return new QJSValuePrivate(this, v8::Array::New(length)); +} + +void QV8Engine::emitSignalHandlerException() +{ + emit q->signalHandlerException(scriptValueFromInternal(uncaughtException())); +} + +void QV8Engine::startTimer(const QString &timerName) +{ + if (!m_time.isValid()) + m_time.start(); + m_startedTimers[timerName] = m_time.elapsed(); +} + +qint64 QV8Engine::stopTimer(const QString &timerName, bool *wasRunning) +{ + if (!m_startedTimers.contains(timerName)) { + *wasRunning = false; + return 0; + } + *wasRunning = true; + qint64 startedAt = m_startedTimers.take(timerName); + return m_time.elapsed() - startedAt; +} + +int QV8Engine::consoleCountHelper(const QString &file, int line, int column) +{ + const QString key = file + QString::number(line) + QString::number(column); + int number = m_consoleCount.value(key, 0); + number++; + m_consoleCount.insert(key, number); + return number; +} + +void QV8GCCallback::registerGcPrologueCallback() +{ + QV8Engine::ThreadData *td = QV8Engine::threadData(); + if (!td->gcPrologueCallbackRegistered) { + td->gcPrologueCallbackRegistered = true; + v8::V8::AddGCPrologueCallback(QV8GCCallback::garbageCollectorPrologueCallback, v8::kGCTypeMarkSweepCompact); + } +} + +QV8GCCallback::Node::Node(PrologueCallback callback) + : prologueCallback(callback) +{ +} + +QV8GCCallback::Node::~Node() +{ + node.remove(); +} + +/* + Ensure that each persistent handle is strong if it has CPP ownership + and has no implicitly JS owned object owner in its parent chain, and + weak otherwise. + + Any weak handle whose parent object is still alive will have an implicit + reference (between the parent and the handle) added, so that it will + not be collected. + + Note that this callback is registered only for kGCTypeMarkSweepCompact + collection cycles, as it is during collection cycles of that type + in which weak persistent handle callbacks are called when required. + */ +void QV8GCCallback::garbageCollectorPrologueCallback(v8::GCType, v8::GCCallbackFlags) +{ + if (!QV8Engine::hasThreadData()) + return; + + QV8Engine::ThreadData *td = QV8Engine::threadData(); + QV8GCCallback::Node *currNode = td->gcCallbackNodes.first(); + + while (currNode) { + // The client which adds itself to the list is responsible + // for maintaining the correct implicit references in the + // specified callback. + currNode->prologueCallback(currNode); + currNode = td->gcCallbackNodes.next(currNode); + } +} + +void QV8GCCallback::addGcCallbackNode(QV8GCCallback::Node *node) +{ + QV8Engine::ThreadData *td = QV8Engine::threadData(); + td->gcCallbackNodes.insert(node); +} + +QV8Engine::ThreadData::ThreadData() + : gcPrologueCallbackRegistered(false) +{ + if (!v8::Isolate::GetCurrent()) { + isolate = v8::Isolate::New(); + isolate->Enter(); + } else { + isolate = 0; + } +} + +QV8Engine::ThreadData::~ThreadData() +{ + if (isolate) { + isolate->Exit(); + isolate->Dispose(); + isolate = 0; + } +} + +QT_END_NAMESPACE + -- cgit v1.2.3