diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2020-12-11 13:35:53 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2020-12-18 17:26:51 +0100 |
commit | 6f181768a3147bbfa9a33cf2c05453365693f5b9 (patch) | |
tree | 3ff80238b5784032c86cfc1a70088e17b62a7127 | |
parent | d5ac54da624dbaebc865c8243a5e1c33d5e1c7ba (diff) |
Add a QJSManagedValue
A QJSManagedValue is a view on a QJSValue which always knows the engine
the value belongs to. This allows us to implement the JavaScript
semantics of the various QJSValue methods in a much more rigorous way.
[ChangeLog][QtQml] The new QJSManagedValue should be used instead of
QJSValue for manipulating properties and prototypes of JavaScript
values, as well as for calling JavaScript functions.
Change-Id: I9d445ffcf68dfa72dba9bae0818e83c80665ad66
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | src/qml/.prev_CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/qml/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/qml/jsapi/jsapi.pri | 2 | ||||
-rw-r--r-- | src/qml/jsapi/qjsengine.cpp | 12 | ||||
-rw-r--r-- | src/qml/jsapi/qjsengine.h | 38 | ||||
-rw-r--r-- | src/qml/jsapi/qjsmanagedvalue.cpp | 1028 | ||||
-rw-r--r-- | src/qml/jsapi/qjsmanagedvalue.h | 158 | ||||
-rw-r--r-- | src/qml/jsapi/qjsprimitivevalue.h | 1 | ||||
-rw-r--r-- | src/qml/jsapi/qjsvalue.cpp | 15 | ||||
-rw-r--r-- | src/qml/jsapi/qjsvalue.h | 4 | ||||
-rw-r--r-- | src/qml/jsapi/qjsvalue_p.h | 29 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4value.cpp | 147 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4value_p.h | 3 | ||||
-rw-r--r-- | tests/auto/qml/.prev_CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/qml/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/qml/qjsmanagedvalue/.prev_CMakeLists.txt | 19 | ||||
-rw-r--r-- | tests/auto/qml/qjsmanagedvalue/CMakeLists.txt | 16 | ||||
-rw-r--r-- | tests/auto/qml/qjsmanagedvalue/qjsmanagedvalue.pro | 6 | ||||
-rw-r--r-- | tests/auto/qml/qjsmanagedvalue/tst_qjsmanagedvalue.cpp | 1746 | ||||
-rw-r--r-- | tests/auto/qml/qjsmanagedvalue/tst_qjsmanagedvalue.h | 116 | ||||
-rw-r--r-- | tests/auto/qml/qml.pro | 1 |
21 files changed, 3280 insertions, 65 deletions
diff --git a/src/qml/.prev_CMakeLists.txt b/src/qml/.prev_CMakeLists.txt index 8c0534c846..93f1a9d50b 100644 --- a/src/qml/.prev_CMakeLists.txt +++ b/src/qml/.prev_CMakeLists.txt @@ -114,6 +114,7 @@ qt_internal_add_module(Qml debugger/qqmlprofiler_p.h inlinecomponentutils_p.h jsapi/qjsengine.cpp jsapi/qjsengine.h jsapi/qjsengine_p.h + jsapi/qjsmanagedvalue.cpp jsapi/qjsmanagedvalue.h jsapi/qjsprimitivevalue.cpp jsapi/qjsprimitivevalue.h jsapi/qjsvalue.cpp jsapi/qjsvalue.h jsapi/qjsvalue_p.h jsapi/qjsvalueiterator.cpp jsapi/qjsvalueiterator.h jsapi/qjsvalueiterator_p.h diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt index 41ca9c80f5..6407c90b21 100644 --- a/src/qml/CMakeLists.txt +++ b/src/qml/CMakeLists.txt @@ -115,6 +115,7 @@ qt_internal_add_module(Qml debugger/qqmlprofiler_p.h inlinecomponentutils_p.h jsapi/qjsengine.cpp jsapi/qjsengine.h jsapi/qjsengine_p.h + jsapi/qjsmanagedvalue.cpp jsapi/qjsmanagedvalue.h jsapi/qjsprimitivevalue.cpp jsapi/qjsprimitivevalue.h jsapi/qjsvalue.cpp jsapi/qjsvalue.h jsapi/qjsvalue_p.h jsapi/qjsvalueiterator.cpp jsapi/qjsvalueiterator.h jsapi/qjsvalueiterator_p.h diff --git a/src/qml/jsapi/jsapi.pri b/src/qml/jsapi/jsapi.pri index f96f8c959b..3463a3aafe 100644 --- a/src/qml/jsapi/jsapi.pri +++ b/src/qml/jsapi/jsapi.pri @@ -1,5 +1,6 @@ SOURCES += \ $$PWD/qjsengine.cpp \ + $$PWD/qjsmanagedvalue.cpp \ $$PWD/qjsprimitivevalue.cpp \ $$PWD/qjsvalue.cpp \ $$PWD/qjsvalueiterator.cpp \ @@ -7,6 +8,7 @@ SOURCES += \ HEADERS += \ $$PWD/qjsengine.h \ $$PWD/qjsengine_p.h \ + $$PWD/qjsmanagedvalue.h \ $$PWD/qjsprimitivevalue.h \ $$PWD/qjsvalue.h \ $$PWD/qjsvalue_p.h \ diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index a0e2cb2c3a..f0e3e69fbb 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -728,6 +728,13 @@ QJSValue QJSEngine::globalObject() const return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); } +QJSManagedValue QJSEngine::createManaged(QMetaType type, const void *ptr) +{ + QJSManagedValue result(m_v4Engine); + *result.d = m_v4Engine->metaTypeToJS(type.id(), ptr); + return result; +} + /*! * \internal * used by QJSEngine::toScriptValue @@ -739,6 +746,11 @@ QJSValue QJSEngine::create(int type, const void *ptr) return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); } +bool QJSEngine::convertManaged(const QJSManagedValue &value, int type, void *ptr) +{ + return QV4::ExecutionEngine::metaTypeFromJS(*value.d, type, ptr); +} + /*! \internal convert \a value to \a type, store the result in \a ptr diff --git a/src/qml/jsapi/qjsengine.h b/src/qml/jsapi/qjsengine.h index c480a77599..9b02469b23 100644 --- a/src/qml/jsapi/qjsengine.h +++ b/src/qml/jsapi/qjsengine.h @@ -46,7 +46,7 @@ #include <QtCore/qsharedpointer.h> #include <QtCore/qobject.h> #include <QtQml/qjsvalue.h> - +#include <QtQml/qjsmanagedvalue.h> #include <QtQml/qqmldebug.h> QT_BEGIN_NAMESPACE @@ -92,12 +92,25 @@ public: { return create(qMetaTypeId<T>(), &value); } + + template <typename T> + inline QJSManagedValue toManagedValue(const T &value) + { + return createManaged(QMetaType::fromType<T>(), &value); + } + template <typename T> inline T fromScriptValue(const QJSValue &value) { return qjsvalue_cast<T>(value); } + template <typename T> + inline T fromManagedValue(const QJSManagedValue &value) + { + return qjsvalue_cast<T>(value); + } + void collectGarbage(); enum ObjectOwnership { CppOwnership, JavaScriptOwnership }; @@ -131,13 +144,18 @@ Q_SIGNALS: void uiLanguageChanged(); private: + QJSManagedValue createManaged(QMetaType type, const void *ptr); QJSValue create(int type, const void *ptr); + static bool convertManaged(const QJSManagedValue &value, int type, void *ptr); static bool convertV2(const QJSValue &value, int type, void *ptr); template<typename T> friend inline T qjsvalue_cast(const QJSValue &); + template<typename T> + friend inline T qjsvalue_cast(const QJSManagedValue &); + protected: QJSEngine(QJSEnginePrivate &dd, QObject *parent = nullptr); @@ -163,12 +181,30 @@ T qjsvalue_cast(const QJSValue &value) return T(); } +template<typename T> +T qjsvalue_cast(const QJSManagedValue &value) +{ + { + T t; + if (QJSEngine::convertManaged(value, qMetaTypeId<T>(), &t)) + return t; + } + + return qvariant_cast<T>(value.toVariant()); +} + template <> inline QVariant qjsvalue_cast<QVariant>(const QJSValue &value) { return value.toVariant(); } +template <> +inline QVariant qjsvalue_cast<QVariant>(const QJSManagedValue &value) +{ + return value.toVariant(); +} + Q_QML_EXPORT QJSEngine *qjsEngine(const QObject *); QT_END_NAMESPACE diff --git a/src/qml/jsapi/qjsmanagedvalue.cpp b/src/qml/jsapi/qjsmanagedvalue.cpp new file mode 100644 index 0000000000..fee2d9da10 --- /dev/null +++ b/src/qml/jsapi/qjsmanagedvalue.cpp @@ -0,0 +1,1028 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQml/qjsmanagedvalue.h> +#include <QtQml/qjsengine.h> +#include <QtQml/private/qv4persistent_p.h> +#include <QtQml/private/qv4engine_p.h> +#include <QtQml/private/qv4mm_p.h> +#include <QtQml/private/qjsvalue_p.h> +#include <QtQml/private/qv4runtime_p.h> +#include <QtQml/private/qv4functionobject_p.h> +#include <QtQml/private/qv4jscall_p.h> +#include <QtQml/private/qv4urlobject_p.h> +#include <QtQml/private/qv4variantobject_p.h> +#include <QtQml/private/qv4qobjectwrapper_p.h> +#include <QtQml/private/qv4regexpobject_p.h> +#include <QtQml/private/qv4dateobject_p.h> +#include <QtQml/private/qv4errorobject_p.h> + +#include <QtCore/qregularexpression.h> +#include <QtCore/qurl.h> +#include <QtCore/qdatetime.h> + +QT_BEGIN_NAMESPACE + +/*! + * \class QJSManagedValue + * \since 6.1 + * + * \brief QJSManagedValue represents a value on the JavaScript heap belonging to a QJSEngine. + * + * The QJSManagedValue class allows interaction with JavaScript values in most + * ways you can interact with them from JavaScript itself. You can get and set + * properties and prototypes, and you can access arrays. Additionally, you can + * transform the value into the Qt counterparts of JavaScript objects. For + * example, a Url object may be transformed into a QUrl. + * + * A QJSManagedValue is always bound to a particular QJSEngine. You cannot use + * it independently. This means that you cannot have a QJSManagedValue from one + * engine be a property or a proptotype of a QJSManagedValue from a different + * engine. + * + * In contrast to QJSValue, almost all values held by QJSManagedValue live on + * the JavaScript heap. There is no inline or unmanaged storage. Therefore, you + * can get the prototype of a primitive value, and you can get the \c length + * property of a string. + * + * Only default-constructed or moved-from QJSManagedValues do not hold a value + * on the JavaScript heap. They represent \c undefined, which doesn't have any + * properties or prototypes. + * + * Also in contrast to QJSValue, QJSManagedValue does not catch any JavaScript + * exceptions. If an operation on a QJSManagedValue causes an error, it will + * generally return an \c undefined value and QJSEngine::hasError() will return + * \c true afterwards. You can then catch the exception using + * QJSEngine::catchError(), or pass it up the stack, at your own discretion. + * + * \note As the reference to the value on the JavaScript heap has to be freed + * on destruction, you cannot move a QJSManagedValue to a different thread. + * The destruction would take place in the new thread, which would create a race + * condition with the garbage collector on the original thread. This also means + * that you cannot hold a QJSManagedValue beyond the lifespan of its engine. + * + * The recommended way of working with a QJSManagedValue is creating it + * on the stack, possibly by moving a QJSValue and adding an engine, then + * performing the necessary operations on it, and finally moving it back into a + * QJSValue for storage. Moving between QJSManagedValue and QJSValue is fast. + */ + +/*! + * \enum QJSManagedValue::Type + * + * This enum represents the JavaScript native types, as specified by + * \l{ECMA-26}. + * + * \value Undefined The \c undefined type + * \value Boolean The \c boolean type + * \value Number The \c number type + * \value String The \c string type + * \value Object The \c object type + * \value Symbol The \c symbol type + * + * Note that the \c null value is not a type of itself but rather a special kind + * of object. You can query a QJSManagedValue for this condition using the + * isNull() method. Furthermore, JavaScript has no integer type, but it knows a + * special treatment of numbers in preparation for integer only operations. You + * can query a QJSManagedValue to find out whether it holds the result of such a + * treatment by using the isInteger() method. + */ + +/*! + * \fn QJSManagedValue::QJSManagedValue() + * + * Creates a QJSManagedValue that represents the JavaScript \c undefined value. + * This is the only value not stored on the JavaScript heap. Calling engine() + * on a default-constructed QJSManagedValue will return nullptr. + */ + +static QV4::ExecutionEngine *v4Engine(QV4::Value *d) +{ + return d ? QV4::PersistentValueStorage::getEngine(d) : nullptr; +} + +/*! + * Creates a QJSManagedValue from \a value, using the heap of \a engine. If + * \a value is itself managed and the engine it belongs to is not \a engine, + * the result is an \c undefined value, and a warning is generated. + */ +QJSManagedValue::QJSManagedValue(QJSValue value, QJSEngine *engine) +{ + QV4::ExecutionEngine *v4 = engine->handle(); + + if (QV4::Value *m = QJSValuePrivate::takeManagedValue(&value)) { + if (Q_UNLIKELY(v4Engine(m) != v4)) { + qWarning("QJSManagedValue(QJSValue, QJSEngine *) failed: " + "Value was created in different engine."); + QV4::PersistentValueStorage::free(m); + return; + } + + d = m; + return; + } + + d = v4->memoryManager->m_persistentValues->allocate(); + + if (const QString *string = QJSValuePrivate::asQString(&value)) + *d = v4->newString(*string); + else + *d = QJSValuePrivate::asReturnedValue(&value); +} + +/*! + * Creates a QJSManagedValue from \a value using the heap of \a engine. + */ +QJSManagedValue::QJSManagedValue(const QJSPrimitiveValue &value, QJSEngine *engine) : + QJSManagedValue(engine->handle()) +{ + switch (value.type()) { + case QJSPrimitiveValue::Undefined: + *d = QV4::Encode::undefined(); + return; + case QJSPrimitiveValue::Null: + *d = QV4::Encode::null(); + return; + case QJSPrimitiveValue::Boolean: + *d = QV4::Encode(value.asBoolean()); + return; + case QJSPrimitiveValue::Integer: + *d = QV4::Encode(value.asInteger()); + return; + case QJSPrimitiveValue::Double: + *d = QV4::Encode(value.asDouble()); + return; + case QJSPrimitiveValue::String: + *d = engine->handle()->newString(value.asString()); + return; + } + + Q_UNREACHABLE(); +} + +/*! + * Creates a QJSManagedValue from \a variant using the heap of \a engine. + */ +QJSManagedValue::QJSManagedValue(const QVariant &variant, QJSEngine *engine) : + QJSManagedValue(engine->handle()) +{ + *d = engine->handle()->fromVariant(variant); +} + +/*! + * Creates a QJSManagedValue from \a string using the heap of \a engine. + */ +QJSManagedValue::QJSManagedValue(const QString &string, QJSEngine *engine) : + QJSManagedValue(engine->handle()) +{ + *d = engine->handle()->newString(string); +} + +/*! + * Destroys the QJSManagedValue. + * + * \note This frees the memory slot it holds on the JavaScript heap. You must + * not destroy a QJSManagedValue from a different thread than the one + * where the QJSEngine it belongs to lives. + */ +QJSManagedValue::~QJSManagedValue() +{ + QV4::PersistentValueStorage::free(d); +} + +/*! + * Move-constructs a QJSManagedValue from \a other. This leaves \a other in + * the default-constructed state where it represents undefined and does not + * belong to any engine. + */ +QJSManagedValue::QJSManagedValue(QJSManagedValue &&other) +{ + qSwap(d, other.d); +} + +/*! + * Move-assigns a QJSManagedValue from \a other. This leaves \a other in + * the default-constructed state where it represents undefined and does not + * belong to any engine. + * + * \note This frees the memory slot this QJSManagedValue holds on the + * JavaScript heap. You must not move-assign a QJSManagedValue on a + * different thread than the one where the QJSEngine it belongs to lives. + */ +QJSManagedValue &QJSManagedValue::operator=(QJSManagedValue &&other) +{ + if (this != &other) { + QV4::PersistentValueStorage::free(d); + d = nullptr; + qSwap(d, other.d); + } + return *this; +} + +/*! + * Invokes the JavaScript '==' operator on this QJSManagedValue and \a other, + * and returns the result. + * + * \sa strictlyEquals + */ +bool QJSManagedValue::equals(const QJSManagedValue &other) const +{ + if (!d) + return !other.d || other.d->isNullOrUndefined(); + if (!other.d) + return d->isNullOrUndefined(); + + return QV4::Runtime::CompareEqual::call(*d, *other.d); +} + +/*! + * Invokes the JavaScript '===' operator on this QJSManagedValue and \a other, + * and returns the result. + * + * \sa equals + */ +bool QJSManagedValue::strictlyEquals(const QJSManagedValue &other) const +{ + if (!d) + return !other.d || other.d->isUndefined(); + if (!other.d) + return d->isUndefined(); + + return QV4::RuntimeHelpers::strictEqual(*d, *other.d); +} + +/*! + * Returns the QJSEngine this QJSManagedValue belongs to. Mind that the engine + * is always valid, unless the QJSManagedValue is default-constructed or moved + * from. In the latter case a nullptr is returned. + */ +QJSEngine *QJSManagedValue::engine() const +{ + if (!d) + return nullptr; + if (QV4::ExecutionEngine *v4 = QV4::PersistentValueStorage::getEngine(d)) + return v4->jsEngine(); + return nullptr; +} + +/*! + * Returns the prototype for this QJSManagedValue. This works on any value. You + * can, for example retrieve the JavaScript \c boolean prototype from a \c boolean + * value. + */ +QJSManagedValue QJSManagedValue::prototype() const +{ + if (!d) + return QJSManagedValue(); + + QV4::ExecutionEngine *v4 = v4Engine(d); + QJSManagedValue result(v4); + + if (auto object = d->as<QV4::Object>()) + *result.d = object->getPrototypeOf(); + else if (auto managed = d->as<QV4::Managed>()) + *result.d = managed->internalClass()->prototype; + else if (d->isBoolean()) + *result.d = v4->booleanPrototype(); + else if (d->isNumber()) + *result.d = v4->numberPrototype(); + + // If the prototype appears to be undefined, then it's actually null in JS terms. + if (result.d->isUndefined()) + *result.d = QV4::Encode::null(); + + return result; +} + +/*! + * Sets the prototype of this QJSManagedValue to \a prototype. A precondition + * is that \a prototype belongs to the same QJSEngine as this QJSManagedValue + * and is an object (including null). Furthermore, this QJSManagedValue has to + * be an object (excluding null), too, and you cannot create prototype cycles. + */ +void QJSManagedValue::setPrototype(const QJSManagedValue &prototype) +{ + auto object = d ? d->as<QV4::Object>() : nullptr; + if (!object) { + qWarning("QJSManagedValue::setPrototype() failed: " + "Can only set a prototype on an object (excluding null)."); + return; + } + + // Object includes null ... + if (prototype.type() != QJSManagedValue::Object) { + qWarning("QJSManagedValue::setPrototype() failed: " + "Can only set objects (including null) as prototypes."); + return; + } + + if (Q_UNLIKELY(object->engine() != v4Engine(prototype.d))) { + qWarning("QJSManagedValue::setPrototype() failed: " + "Prototype was created in differen engine."); + return; + } + + // ... Null becomes nullptr here. That is why it appears as undefined later. + if (!object->setPrototypeOf(prototype.d->as<QV4::Object>())) { + qWarning("QJSManagedValue::setPrototype() failed: " + "Prototype cycle detected."); + } +} + +/*! + * Returns the JavaScript type of this QJSManagedValue. + */ +QJSManagedValue::Type QJSManagedValue::type() const +{ + if (!d || d->isUndefined()) + return Undefined; + if (d->isBoolean()) + return Boolean; + if (d->isNumber()) + return Number; + if (d->isString()) + return String; + if (d->isSymbol()) + return Symbol; + return Object; +} + +/*! + * \fn QJSManagedValue::isUndefined() const + * + * Returns \c true if the type of this QJSManagedValue is \c undefined, + * or \c false otherwise. + */ + +/*! + * \fn QJSManagedValue::isBoolean() const + * + * Returns \c true if the type of this QJSManagedValue is \c boolean, + * or \c false otherwise. + */ + +/*! + * \fn QJSManagedValue::isNumber() const + * + * Returns \c true if the type of this QJSManagedValue is \c number, + * or \c false otherwise. + */ + +/*! + * \fn QJSManagedValue::isString() const + * + * Returns \c true if the type of this QJSManagedValue is \c string, + * or \c false otherwise. + */ + +/*! + * \fn QJSManagedValue::isSymbol() const + * + * Returns \c true if the type of this QJSManagedValue is \c symbol, + * or \c false otherwise. + */ + +/*! + * \fn QJSManagedValue::isObject() const + * + * Returns \c true if the type of this QJSManagedValue is \c object, + * or \c false otherwise. + */ + +/*! + * Returns \c true if this QJSManagedValue holds the JavaScript \c null value, + * or \c false otherwise. + */ +bool QJSManagedValue::isNull() const +{ + return d && d->isNull(); +} + +/*! + * Returns \c true if this QJSManagedValue holds an integer value, or \c false + * otherwise. The storage format of a number does not affect the result of any + * operations performed on it, but if an integer is stored, many operations are + * faster. + */ +bool QJSManagedValue::isInteger() const +{ + return d && d->isInteger(); +} + +/*! + * Returns \c true if this value represents a JavaScript regular expression + * object, or \c false otherwise. + */ +bool QJSManagedValue::isRegularExpression() const +{ + return d && d->as<QV4::RegExpObject>(); +} + +/*! + * Returns \c true if this value represents a JavaScript Array + * object, or \c false otherwise. + */ +bool QJSManagedValue::isArray() const +{ + return d && d->as<QV4::ArrayObject>(); +} + +/*! + * Returns \c true if this value represents a JavaScript Url + * object, or \c false otherwise. + */ +bool QJSManagedValue::isUrl() const +{ + return d && d->as<QV4::UrlObject>(); +} + +/*! + * Returns \c true if this value represents a QVariant managed on the JavaScript + * heap, or \c false otherwise. + */ +bool QJSManagedValue::isVariant() const +{ + return d && d->as<QV4::VariantObject>(); +} + +/*! + * Returns \c true if this value represents a QObject pointer managed on the + * JavaScript heap, or \c false otherwise. + */ +bool QJSManagedValue::isQObject() const +{ + return d && d->as<QV4::QObjectWrapper>(); +} + +/*! + * Returns \c true if this value represents a QMetaObject pointer managed on the + * JavaScript heap, or \c false otherwise. + */ +bool QJSManagedValue::isQMetaObject() const +{ + return d && d->as<QV4::QMetaObjectWrapper>(); +} + +/*! + * Returns \c true if this value represents a JavaScript Date object, or + * \c false otherwise. + */ +bool QJSManagedValue::isDate() const +{ + return d && d->as<QV4::DateObject>(); +} + +/*! + * Returns \c true if this value represents a JavaScript Error object, or + * \c false otherwise. + */ +bool QJSManagedValue::isError() const +{ + return d && d->as<QV4::ErrorObject>(); +} + +/*! + * Converts the manged value to a string. If the managed value holds a string, + * that one is returned. Otherwise a string coercion by JavaScript rules is + * performed. + * + * \note Conversion of a managed value to a string can throw an exception. In + * particular, symbols cannot be coerced into strings, or a custom + * toString() method may throw. In this case the result is an empty + * string and the engine carries an error after the conversion. + */ +QString QJSManagedValue::toString() const +{ + return d ? d->toQString() : QStringLiteral("undefined"); +} + +/*! + * Converts the manged value to a number. If the managed value holds a number, + * that one is returned. Otherwise a number coercion by JavaScript rules is + * performed. + * + * \note Conversion of a managed value to a number can throw an exception. In + * particular, symbols cannot be coerced into numbers, or a custom + * valueOf() method may throw. In this case the result is 0 and the + * engine carries an error after the conversion. + */ +double QJSManagedValue::toNumber() const +{ + return d ? d->toNumber() : 0; +} + +/*! + * Converts the manged value to a boolean. If the managed value holds a boolean, + * that one is returned. Otherwise a boolean coercion by JavaScript rules is + * performed. + */ +bool QJSManagedValue::toBoolean() const +{ + return d ? d->toBoolean() : false; +} + +/*! + * Converts the manged value to an integer. This first converts the value to a + * number by the rules of toNumber(), and then clamps it into the integer range + * by the rules given for coercing the arguments to JavaScript bit shift + * operators into 32bit integers. + * + * Internally, the value may already be stored as an integer, in which case a + * fast path is taken. + * + * \note Conversion of a managed value to a number can throw an exception. In + * particular, symbols cannot be coerced into numbers, or a custom + * valueOf() method may throw. In this case the result is 0 and the + * engine carries an error after the conversion. + * + * \note The JavaScript rules for coercing numbers into 32bit integers are + * unintuitive. + */ +int QJSManagedValue::toInteger() const +{ + return d ? d->toInt32() : 0; +} + +/*! + * Converts the manged value to a QJSPrimitiveValue. If the managed value holds + * a type supported by QJSPrimitiveValue, the value is copied. Otherwise the + * value is converted to a string, and the string is stored in + * QJSPrimitiveValue. + * + * \note Conversion of a managed value to a string can throw an exception. In + * particular, symbols cannot be coerced into strings, or a custom + * toString() method may throw. In this case the result is the undefined + * value and the engine carries an error after the conversion. + */ +QJSPrimitiveValue QJSManagedValue::toPrimitive() const +{ + if (!d || d->isUndefined()) + return QJSPrimitiveUndefined(); + if (d->isNull()) + return QJSPrimitiveNull(); + if (d->isBoolean()) + return d->booleanValue(); + if (d->isInteger()) + return d->integerValue(); + if (d->isNumber()) + return d->doubleValue(); + + bool ok; + const QString result = d->toQString(&ok); + return ok ? QJSPrimitiveValue(result) : QJSPrimitiveValue(QJSPrimitiveUndefined()); +} + +/*! + * Copies this QJSManagedValue into a new QJSValue. This is less efficient than + * move-constructing a QJSValue from a QJSManagedValue, but retains the + * QJSManagedValue. + */ +QJSValue QJSManagedValue::toJSValue() const +{ + return d ? QJSValuePrivate::fromReturnedValue(d->asReturnedValue()) : QJSValue(); +} + +/*! + * Copies this QJSManagedValue into a new QVariant. This also creates a useful + * QVariant if QJSManagedValue::isVariant() returns false. QVariant can hold all + * types supported by QJSManagedValue. + */ +QVariant QJSManagedValue::toVariant() const +{ + if (!d || d->isUndefined()) + return QVariant(); + if (d->isNull()) + return QVariant(QMetaType::fromType<std::nullptr_t>(), nullptr); + if (d->isBoolean()) + return QVariant(d->booleanValue()); + if (d->isInteger()) + return QVariant(d->integerValue()); + if (d->isNumber()) + return QVariant(d->doubleValue()); + if (d->isString()) + return QVariant(d->toQString()); + if (QV4::Managed *m = d->as<QV4::Managed>()) + return m->engine()->toVariant(*d, -1, true); + + Q_UNREACHABLE(); + return QVariant(); +} + +/*! + * If this QJSManagedValue holds a JavaScript regular expression object, returns + * an equivalent QRegularExpression. Otherwise returns an invalid one. + */ +QRegularExpression QJSManagedValue::toRegularExpression() const +{ + if (const auto *r = d ? d->as<QV4::RegExpObject>() : nullptr) + return r->toQRegularExpression(); + return {}; +} + +/*! + * If this QJSManagedValue holds a JavaScript Url object, returns + * an equivalent QUrl. Otherwise returns an invalid one. + */ +QUrl QJSManagedValue::toUrl() const +{ + if (const auto *u = d ? d->as<QV4::UrlObject>() : nullptr) + return u->toQUrl(); + return {}; +} + +/*! + * If this QJSManagedValue holds a QObject pointer, returns it. Otherwise + * returns nullptr. + */ +QObject *QJSManagedValue::toQObject() const +{ + if (const auto *o = d ? d->as<QV4::QObjectWrapper>() : nullptr) + return o->object(); + return {}; +} + +/*! + * If this QJSManagedValue holds a QMetaObject pointer, returns it. + * Otherwise returns nullptr. + */ +const QMetaObject *QJSManagedValue::toQMetaObject() const +{ + if (const auto *m = d ? d->as<QV4::QMetaObjectWrapper>() : nullptr) + return m->metaObject(); + return {}; +} + +/*! + * If this QJSManagedValue holds a JavaScript Date object, returns an equivalent + * QDateTime. Otherwise returns an invalid one. + */ +QDateTime QJSManagedValue::toDateTime() const +{ + if (const auto *t = d ? d->as<QV4::DateObject>() : nullptr) + return t->toQDateTime(); + return {}; +} + +/*! + * Returns \c true if this QJSManagedValue has a property \a name, otherwise + * returns \c false. The properties of the prototype chain are considered. + */ +bool QJSManagedValue::hasProperty(const QString &name) const +{ + if (!d || d->isNullOrUndefined()) + return false; + + if (d->isString() && name == QStringLiteral("length")) + return true; + + if (QV4::Object *obj = d->as<QV4::Object>()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedString str(scope, obj->engine()->newString(name)); + return obj->hasProperty(str->toPropertyKey()); + } + + return prototype().hasProperty(name); +} + +/*! + * Returns \c true if this QJSManagedValue has a property \a name, otherwise + * returns \c false. The properties of the prototype chain are not considered. + */ +bool QJSManagedValue::hasOwnProperty(const QString &name) const +{ + if (!d || d->isNullOrUndefined()) + return false; + + if (d->isString() && name == QStringLiteral("length")) + return true; + + if (QV4::Object *obj = d->as<QV4::Object>()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedString str(scope, obj->engine()->newString(name)); + return obj->getOwnProperty(str->toPropertyKey()) != QV4::Attr_Invalid; + } + + return false; +} + +/*! + * Returns the property \a name of this QJSManagedValue. The prototype chain + * is searched if the property is not found on the actual object. + */ +QJSValue QJSManagedValue::property(const QString &name) const +{ + if (!d || d->isNullOrUndefined()) + return QJSValue(); + + if (QV4::String *string = d->as<QV4::String>()) { + if (name == QStringLiteral("length")) + return QJSValue(string->d()->length()); + } + + if (QV4::Object *obj = d->as<QV4::Object>()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedString str(scope, obj->engine()->newString(name)); + return QJSValuePrivate::fromReturnedValue(obj->get(str->toPropertyKey())); + } + + return prototype().property(name); +} + +/*! + * Sets the property \a name to \a value on this QJSManagedValue. This can only + * be done on JavaScript values of type \c object. Furhermore, \a value has to be + * either a primitive or belong to the same engine as this value. + */ +void QJSManagedValue::setProperty(const QString &name, const QJSValue &value) +{ + if (!d) + return; + + if (QV4::Object *obj = d->as<QV4::Object>()) { + QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(&value); + if (Q_UNLIKELY(v4 && v4 != obj->engine())) { + qWarning("QJSManagedValue::setProperty() failed: " + "Value was created in different engine."); + return; + } + QV4::Scope scope(obj->engine()); + QV4::ScopedString str(scope, obj->engine()->newString(name)); + obj->put(str->toPropertyKey(), QJSValuePrivate::convertToReturnedValue(v4, value)); + } +} + +/*! + * Deletes the property \a name from this QJSManagedValue. Returns \c true if + * the deletion succeeded, or \a false otherwise. + */ +bool QJSManagedValue::deleteProperty(const QString &name) +{ + if (!d) + return false; + + if (QV4::Object *obj = d->as<QV4::Object>()) { + QV4::Scope scope(obj->engine()); + QV4::ScopedString str(scope, obj->engine()->newString(name)); + return obj->deleteProperty(str->toPropertyKey()); + } + + return false; +} + +/*! + * Returns \c true if this QJSManagedValue has an array index \a arrayIndex, + * otherwise returns \c false. The properties of the prototype chain are + * considered. + */ +bool QJSManagedValue::hasProperty(quint32 arrayIndex) const +{ + if (!d || d->isNullOrUndefined()) + return false; + + if (QV4::Object *obj = d->as<QV4::Object>()) { + bool hasProperty = false; + if (arrayIndex == std::numeric_limits<quint32>::max()) + obj->get(obj->engine()->id_uintMax(), &hasProperty); + else + obj->get(arrayIndex, &hasProperty); + return hasProperty; + } + + return prototype().hasProperty(arrayIndex); +} + +/*! + * Returns \c true if this QJSManagedValue has an array index \a arrayIndex, + * otherwise returns \c false. The properties of the prototype chain are not + * considered. + */ +bool QJSManagedValue::hasOwnProperty(quint32 arrayIndex) const +{ + if (!d || d->isNullOrUndefined()) + return false; + + if (QV4::Object *obj = d->as<QV4::Object>()) { + if (arrayIndex == std::numeric_limits<quint32>::max()) { + return obj->getOwnProperty(obj->engine()->id_uintMax()->toPropertyKey()) + != QV4::Attr_Invalid; + } else { + return obj->getOwnProperty(QV4::PropertyKey::fromArrayIndex(arrayIndex)) + != QV4::Attr_Invalid; + } + } + + return false; +} + +/*! + * Returns the property stored at \a arrayIndex of this QJSManagedValue. The + * prototype chain is searched if the property is not found on the actual + * object. + */ +QJSValue QJSManagedValue::property(quint32 arrayIndex) const +{ + if (!d || d->isNullOrUndefined()) + return QJSValue(); + + if (QV4::Object *obj = d->as<QV4::Object>()) { + if (arrayIndex == std::numeric_limits<quint32>::max()) + return QJSValuePrivate::fromReturnedValue(obj->get(obj->engine()->id_uintMax())); + else + return QJSValuePrivate::fromReturnedValue(obj->get(arrayIndex)); + } + + return prototype().property(arrayIndex); +} + +/*! + * Stores the \a value at \a arrayIndex in this QJSManagedValue. This can only + * be done on JavaScript values of type \c object, and it's not recommended if the + * value is not an array. Furhermore, \a value has to be either a primitive or + * belong to the same engine as this value. + */ +void QJSManagedValue::setProperty(quint32 arrayIndex, const QJSValue &value) +{ + if (!d) + return; + + if (QV4::Object *obj = d->as<QV4::Object>()) { + QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(&value); + if (Q_UNLIKELY(v4 && v4 != obj->engine())) { + qWarning("QJSManagedValue::setProperty() failed: " + "Value was created in different engine."); + return; + } + obj->put(arrayIndex, QJSValuePrivate::convertToReturnedValue(v4, value)); + } +} + +/*! + * Deletes the value stored at \a arrayIndex from this QJSManagedValue. Returns + * \c true if the deletion succeeded, or \a false otherwise. + */ +bool QJSManagedValue::deleteProperty(quint32 arrayIndex) +{ + if (!d) + return false; + + if (QV4::Object *obj = d->as<QV4::Object>()) + return obj->deleteProperty(QV4::PropertyKey::fromArrayIndex(arrayIndex)); + + return false; +} + +/*! + * Returns \c true if this QJSManagedValue is a JavaScript FunctionObject, or + * \c false otherwise. + */ +bool QJSManagedValue::isCallable() const +{ + return d && d->isFunctionObject(); +} + +/*! + * If this QJSManagedValue represents a JavaScript FunctionObject, calls it with + * the given \a arguments, and returns the result. Otherwise returns a + * JavaScript \c undefined value. + * + * The \a arguments have to be either primitive values or belong to the same + * QJSEngine as this QJSManagedValue. Otherwise the call is not carried + * out and a JavaScript \c undefined value is returned. + */ +QJSValue QJSManagedValue::call(const QJSValueList &arguments) const +{ + const QV4::FunctionObject *f = d ? d->as<QV4::FunctionObject>() : nullptr; + if (!f) + return QJSValue(); + + QV4::ExecutionEngine *engine = f->engine(); + + QV4::Scope scope(engine); + QV4::JSCallData jsCallData(scope, arguments.length()); + *jsCallData->thisObject = engine->globalObject; + int i = 0; + for (const QJSValue &arg : arguments) { + if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { + qWarning("QJSManagedValue::call() failed: Argument was created in different engine."); + return QJSValue(); + } + jsCallData->args[i++] = QJSValuePrivate::convertToReturnedValue(engine, arg); + } + + return QJSValuePrivate::fromReturnedValue(f->call(jsCallData)); +} + +/*! + * If this QJSManagedValue represents a JavaScript FunctionObject, calls it on + * \a instance with the given \a arguments, and returns the result. Otherwise + * returns a JavaScript \c undefined value. + * + * The \a arguments and the \a instance have to be either primitive values or + * belong to the same QJSEngine as this QJSManagedValue. Otherwise the call is + * not carried out and a JavaScript \c undefined value is returned. + */ +QJSValue QJSManagedValue::callWithInstance(const QJSValue &instance, + const QJSValueList &arguments) const +{ + const QV4::FunctionObject *f = d ? d->as<QV4::FunctionObject>() : nullptr; + if (!f) + return QJSValue(); + + QV4::ExecutionEngine *engine = f->engine(); + + if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, instance))) { + qWarning("QJSManagedValue::callWithInstance() failed: " + "Instance was created in different engine."); + return QJSValue(); + } + + QV4::Scope scope(engine); + QV4::JSCallData jsCallData(scope, arguments.length()); + *jsCallData->thisObject = QJSValuePrivate::convertToReturnedValue(engine, instance); + int i = 0; + for (const QJSValue &arg : arguments) { + if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { + qWarning("QJSManagedValue::callWithInstance() failed: " + "Argument was created in different engine."); + return QJSValue(); + } + jsCallData->args[i++] = QJSValuePrivate::convertToReturnedValue(engine, arg); + } + + return QJSValuePrivate::fromReturnedValue(f->call(jsCallData)); +} + +/*! + * If this QJSManagedValue represents a JavaScript FunctionObject, calls it as + * constructor with the given \a arguments, and returns the result. Otherwise + * returns a JavaScript \c undefined value. + * + * The \a arguments have to be either primitive values or belong to the same + * QJSEngine as this QJSManagedValue. Otherwise the call is not carried + * out and a JavaScript \c undefined value is returned. + */ +QJSValue QJSManagedValue::callAsConstructor(const QJSValueList &arguments) const +{ + const QV4::FunctionObject *f = d ? d->as<QV4::FunctionObject>() : nullptr; + if (!f) + return QJSValue(); + + QV4::ExecutionEngine *engine = f->engine(); + + QV4::Scope scope(engine); + QV4::JSCallData jsCallData(scope, arguments.length()); + int i = 0; + for (const QJSValue &arg : arguments) { + if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { + qWarning("QJSManagedValue::callAsConstructor() failed: " + "Argument was created in different engine."); + return QJSValue(); + } + jsCallData->args[i++] = QJSValuePrivate::convertToReturnedValue(engine, arg); + } + + return QJSValuePrivate::fromReturnedValue(f->callAsConstructor(jsCallData)); +} + +QJSManagedValue::QJSManagedValue(QV4::ExecutionEngine *engine) : + d(engine ? engine->memoryManager->m_persistentValues->allocate() : nullptr) +{ +} + +QT_END_NAMESPACE diff --git a/src/qml/jsapi/qjsmanagedvalue.h b/src/qml/jsapi/qjsmanagedvalue.h new file mode 100644 index 0000000000..67a3f3758a --- /dev/null +++ b/src/qml/jsapi/qjsmanagedvalue.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QJSMANAGEDVALUE_H +#define QJSMANAGEDVALUE_H + +#include <QtQml/qtqmlglobal.h> +#include <QtQml/qjsprimitivevalue.h> +#include <QtQml/qjsvalue.h> + +QT_BEGIN_NAMESPACE + +namespace QV4 { +struct Value; +struct ExecutionEngine; +} + +class QJSEngine; +class Q_QML_EXPORT QJSManagedValue +{ + Q_DISABLE_COPY(QJSManagedValue) +public: + enum Type { + Undefined, + Boolean, + Number, + String, + Object, + Symbol + }; + + QJSManagedValue() = default; + QJSManagedValue(QJSValue value, QJSEngine *engine); + QJSManagedValue(const QJSPrimitiveValue &value, QJSEngine *engine); + QJSManagedValue(const QVariant &variant, QJSEngine *engine); + QJSManagedValue(const QString &string, QJSEngine *engine); + + ~QJSManagedValue(); + QJSManagedValue(QJSManagedValue &&other); + QJSManagedValue &operator=(QJSManagedValue &&other); + + bool equals(const QJSManagedValue &other) const; + bool strictlyEquals(const QJSManagedValue &other) const; + + QJSEngine *engine() const; + + QJSManagedValue prototype() const; + void setPrototype(const QJSManagedValue &prototype); + + Type type() const; + + // Compatibility with QJSValue + bool isUndefined() const { return type() == Undefined; } + bool isBoolean() const { return type() == Boolean; } + bool isNumber() const { return type() == Number; } + bool isString() const { return type() == String; } + bool isObject() const { return type() == Object; } + bool isSymbol() const { return type() == Symbol; } + + // Special case of Number + bool isInteger() const; + + // Selected special cases of Object + bool isNull() const; + bool isRegularExpression() const; + bool isArray() const; + bool isUrl() const; + bool isVariant() const; + bool isQObject() const; + bool isQMetaObject() const; + bool isDate() const; + bool isError() const; + bool isCallable() const; + + // Native type transformations + QString toString() const; + double toNumber() const; + bool toBoolean() const; + + // Variant-like type transformations + QJSPrimitiveValue toPrimitive() const; + QJSValue toJSValue() const; + QVariant toVariant() const; + + // Special cases + int toInteger() const; + QRegularExpression toRegularExpression() const; + QUrl toUrl() const; + QObject *toQObject() const; + const QMetaObject *toQMetaObject() const; + QDateTime toDateTime() const; + + // Properties of objects + bool hasProperty(const QString &name) const; + bool hasOwnProperty(const QString &name) const; + QJSValue property(const QString &name) const; + void setProperty(const QString &name, const QJSValue &value); + bool deleteProperty(const QString &name); + + // Array indexing + bool hasProperty(quint32 arrayIndex) const; + bool hasOwnProperty(quint32 arrayIndex) const; + QJSValue property(quint32 arrayIndex) const; + void setProperty(quint32 arrayIndex, const QJSValue &value); + bool deleteProperty(quint32 arrayIndex); + + // Calling functions + QJSValue call(const QJSValueList &arguments = {}) const; + QJSValue callWithInstance(const QJSValue &instance, const QJSValueList &arguments = {}) const; + QJSValue callAsConstructor(const QJSValueList &arguments = {}) const; + +private: + friend class QJSValue; + friend class QJSEngine; + + QJSManagedValue(QV4::ExecutionEngine *engine); + QV4::Value *d = nullptr; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/qml/jsapi/qjsprimitivevalue.h b/src/qml/jsapi/qjsprimitivevalue.h index 1a12104a61..27f38aec93 100644 --- a/src/qml/jsapi/qjsprimitivevalue.h +++ b/src/qml/jsapi/qjsprimitivevalue.h @@ -439,6 +439,7 @@ public: } private: + friend class QJSManagedValue; friend class QJSValue; constexpr bool asBoolean() const { return std::get<bool>(d); } diff --git a/src/qml/jsapi/qjsvalue.cpp b/src/qml/jsapi/qjsvalue.cpp index ce3ab0076b..5525aee595 100644 --- a/src/qml/jsapi/qjsvalue.cpp +++ b/src/qml/jsapi/qjsvalue.cpp @@ -43,6 +43,7 @@ #include "qjsengine.h" #include "qjsvalue.h" #include "qjsprimitivevalue.h" +#include "qjsmanagedvalue.h" #include "qjsvalue_p.h" #include "qv4value_p.h" #include "qv4object_p.h" @@ -884,6 +885,20 @@ QJSValue::QJSValue(QJSPrimitiveValue &&value) Q_UNREACHABLE(); } +QJSValue::QJSValue(QJSManagedValue &&value) +{ + if (!value.d) { + d = QV4::Encode::undefined(); + } else if (value.d->isManaged()) { + QJSValuePrivate::setRawValue(this, value.d); + value.d = nullptr; + } else { + d = value.d->asReturnedValue(); + QV4::PersistentValueStorage::free(value.d); + value.d = nullptr; + } +} + static bool js_equal(const QString &string, const QV4::Value &value) { if (String *s = value.stringValue()) diff --git a/src/qml/jsapi/qjsvalue.h b/src/qml/jsapi/qjsvalue.h index 393f1c960e..3f8a16a149 100644 --- a/src/qml/jsapi/qjsvalue.h +++ b/src/qml/jsapi/qjsvalue.h @@ -61,6 +61,9 @@ namespace QV4 { struct Value; } +class QJSPrimitiveValue; +class QJSManagedValue; + class Q_QML_EXPORT QJSValue { public: @@ -109,6 +112,7 @@ public: QJSValue &operator=(const QJSValue &other); explicit QJSValue(QJSPrimitiveValue &&value); + explicit QJSValue(QJSManagedValue &&value); bool isBool() const; bool isNumber() const; diff --git a/src/qml/jsapi/qjsvalue_p.h b/src/qml/jsapi/qjsvalue_p.h index 9e371118cc..cea48fd9e2 100644 --- a/src/qml/jsapi/qjsvalue_p.h +++ b/src/qml/jsapi/qjsvalue_p.h @@ -83,6 +83,12 @@ class Q_AUTOTEST_EXPORT QJSValuePrivate return (m & IsString) ? nullptr : reinterpret_cast<QV4::Value *>(m); } + static QV4::Value *managedValue(QV4::Value &v) + { + quintptr m = quintptr(v.m()); + return (m & IsString) ? nullptr : reinterpret_cast<QV4::Value *>(m); + } + static const QString *qstring(const QV4::Value &v) { const quintptr m = quintptr(v.m()); @@ -108,14 +114,13 @@ class Q_AUTOTEST_EXPORT QJSValuePrivate return QV4::Value::fromHeapObject(reinterpret_cast<QV4::Heap::Base *>(m)).asReturnedValue(); } -protected: - // Only for the test. You're not supposed to subclass QJSValuePrivate otherwise. +public: + static void setRawValue(QJSValue *jsval, QV4::Value *m) { jsval->d = encodeRawValue(quintptr(m)); } -public: static QJSValue fromReturnedValue(QV4::ReturnedValue d) { QJSValue result; @@ -134,6 +139,19 @@ public: return nullptr; } + // This is a move operation and transfers ownership. + static QV4::Value *takeManagedValue(QJSValue *jsval) + { + QV4::Value v = QV4::Value::fromReturnedValue(jsval->d); + if (!v.isManaged()) + return nullptr; + if (QV4::Value *value = managedValue(v)) { + setValue(jsval, QV4::Encode::undefined()); + return value; + } + return nullptr; + } + static QV4::ReturnedValue asPrimitiveType(const QJSValue *jsval) { const QV4::Value v = QV4::Value::fromReturnedValue(jsval->d); @@ -170,6 +188,11 @@ public: jsval->d = v.isManaged() ? encode(v) : v.asReturnedValue(); } + static void adoptValue(QJSValue *jsval, QV4::Value *v) + { + jsval->d = v->isManaged() ? encodeRawValue(quintptr(v)) : v->asReturnedValue(); + } + // Moves any QString onto the V4 heap, changing the value to reflect that. static void manageStringOnV4Heap(QV4::ExecutionEngine *e, QJSValue *jsval) { diff --git a/src/qml/jsruntime/qv4value.cpp b/src/qml/jsruntime/qv4value.cpp index 3449603a8a..6913a49f9c 100644 --- a/src/qml/jsruntime/qv4value.cpp +++ b/src/qml/jsruntime/qv4value.cpp @@ -122,100 +122,125 @@ double Value::toNumberImpl(Value val) } } -QString Value::toQStringNoThrow() const +static QString primitiveToQString(const Value *value) { - switch (type()) { + switch (value->type()) { case Value::Empty_Type: Q_ASSERT(!"empty Value encountered"); Q_UNREACHABLE(); + return QString(); case Value::Undefined_Type: return QStringLiteral("undefined"); case Value::Null_Type: return QStringLiteral("null"); case Value::Boolean_Type: - if (booleanValue()) + if (value->booleanValue()) return QStringLiteral("true"); else return QStringLiteral("false"); case Value::Managed_Type: + Q_UNREACHABLE(); + return QString(); + case Value::Integer_Type: { + QString str; + RuntimeHelpers::numberToString(&str, (double)value->int_32(), 10); + return str; + } + case Value::Double_Type: { + QString str; + RuntimeHelpers::numberToString(&str, value->doubleValue(), 10); + return str; + } + } // switch + + Q_UNREACHABLE(); + return QString(); +} + + +QString Value::toQStringNoThrow() const +{ + if (isManaged()) { if (String *s = stringValue()) return s->toQString(); if (Symbol *s = symbolValue()) return s->descriptiveString(); - { - Q_ASSERT(isObject()); - Scope scope(objectValue()->engine()); - ScopedValue ex(scope); - bool caughtException = false; - ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); + + Q_ASSERT(isObject()); + Scope scope(objectValue()->engine()); + ScopedValue ex(scope); + bool caughtException = false; + ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); + if (scope.hasException()) { + ex = scope.engine->catchException(); + caughtException = true; + } else if (prim->isPrimitive()) { + return prim->toQStringNoThrow(); + } + + // Can't nest try/catch due to CXX ABI limitations for foreign exception nesting. + if (caughtException) { + ScopedValue prim(scope, RuntimeHelpers::toPrimitive(ex, STRING_HINT)); if (scope.hasException()) { ex = scope.engine->catchException(); - caughtException = true; } else if (prim->isPrimitive()) { - return prim->toQStringNoThrow(); - } - // Can't nest try/catch due to CXX ABI limitations for foreign exception nesting. - if (caughtException) { - ScopedValue prim(scope, RuntimeHelpers::toPrimitive(ex, STRING_HINT)); - if (scope.hasException()) { - ex = scope.engine->catchException(); - } else if (prim->isPrimitive()) { - return prim->toQStringNoThrow(); - } + return prim->toQStringNoThrow(); } - return QString(); } - case Value::Integer_Type: { - QString str; - RuntimeHelpers::numberToString(&str, (double)int_32(), 10); - return str; - } - default: { // double - QString str; - RuntimeHelpers::numberToString(&str, doubleValue(), 10); - return str; + + return QString(); } - } // switch + + return primitiveToQString(this); } QString Value::toQString() const { - switch (type()) { - case Value::Empty_Type: - Q_ASSERT(!"empty Value encountered"); - Q_UNREACHABLE(); - case Value::Undefined_Type: - return QStringLiteral("undefined"); - case Value::Null_Type: - return QStringLiteral("null"); - case Value::Boolean_Type: - if (booleanValue()) - return QStringLiteral("true"); - else - return QStringLiteral("false"); - case Value::Managed_Type: - if (String *s = stringValue()) { + if (isManaged()) { + if (String *s = stringValue()) return s->toQString(); - } else if (isSymbol()) { + + if (isSymbol()) { static_cast<const Managed *>(this)->engine()->throwTypeError(); return QString(); - } else { - Q_ASSERT(isObject()); - Scope scope(objectValue()->engine()); - ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); - return prim->toQString(); } - case Value::Integer_Type: { - QString str; - RuntimeHelpers::numberToString(&str, (double)int_32(), 10); - return str; + + Q_ASSERT(isObject()); + Scope scope(objectValue()->engine()); + ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); + return prim->toQString(); } - default: { // double - QString str; - RuntimeHelpers::numberToString(&str, doubleValue(), 10); - return str; + + return primitiveToQString(this); +} + +QString Value::toQString(bool *ok) const +{ + if (isManaged()) { + if (String *s = stringValue()) { + *ok = true; + return s->toQString(); + } + + if (isSymbol()) { + static_cast<const Managed *>(this)->engine()->throwTypeError(); + *ok = false; + return QString(); + } + + Q_ASSERT(isObject()); + Scope scope(objectValue()->engine()); + ScopedValue prim(scope, RuntimeHelpers::toPrimitive(*this, STRING_HINT)); + + if (scope.hasException()) { + *ok = false; + return QString(); + } + + return prim->toQString(ok); } - } // switch + + return primitiveToQString(this); } QV4::PropertyKey Value::toPropertyKey(ExecutionEngine *e) const diff --git a/src/qml/jsruntime/qv4value_p.h b/src/qml/jsruntime/qv4value_p.h index 44383689b4..6941c11f44 100644 --- a/src/qml/jsruntime/qv4value_p.h +++ b/src/qml/jsruntime/qv4value_p.h @@ -190,8 +190,11 @@ struct Q_QML_PRIVATE_EXPORT Value : public StaticValue inline double toNumber() const; static double toNumberImpl(Value v); double toNumberImpl() const { return toNumberImpl(*this); } + QString toQStringNoThrow() const; QString toQString() const; + QString toQString(bool *ok) const; + Heap::String *toString(ExecutionEngine *e) const { if (isString()) return reinterpret_cast<Heap::String *>(m()); diff --git a/tests/auto/qml/.prev_CMakeLists.txt b/tests/auto/qml/.prev_CMakeLists.txt index 40fd5a3795..93455c5ede 100644 --- a/tests/auto/qml/.prev_CMakeLists.txt +++ b/tests/auto/qml/.prev_CMakeLists.txt @@ -34,6 +34,7 @@ add_subdirectory(qqmlmetatype) if(TARGET Qt::Widgets) add_subdirectory(qjsengine) add_subdirectory(qjsvalue) + add_subdirectory(qjsmanagedvalue) endif() if(QT_FEATURE_process AND QT_FEATURE_qml_debug) add_subdirectory(debugger) diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt index ed3706d760..07d6c41b59 100644 --- a/tests/auto/qml/CMakeLists.txt +++ b/tests/auto/qml/CMakeLists.txt @@ -34,6 +34,7 @@ add_subdirectory(qqmlmetatype) if(TARGET Qt::Widgets) add_subdirectory(qjsengine) add_subdirectory(qjsvalue) + add_subdirectory(qjsmanagedvalue) endif() if(QT_FEATURE_process AND QT_FEATURE_qml_debug) add_subdirectory(debugger) diff --git a/tests/auto/qml/qjsmanagedvalue/.prev_CMakeLists.txt b/tests/auto/qml/qjsmanagedvalue/.prev_CMakeLists.txt new file mode 100644 index 0000000000..72bd0c6d8d --- /dev/null +++ b/tests/auto/qml/qjsmanagedvalue/.prev_CMakeLists.txt @@ -0,0 +1,19 @@ +# Generated from qjsvalue.pro. + +##################################################################### +## tst_qjsvalue Test: +##################################################################### + +qt_internal_add_test(tst_qjsvalue + SOURCES + tst_qjsvalue.cpp tst_qjsvalue.h + PUBLIC_LIBRARIES + Qt::Gui + Qt::GuiPrivate + Qt::Qml + Qt::QmlPrivate + Qt::Widgets +) + +## Scopes: +##################################################################### diff --git a/tests/auto/qml/qjsmanagedvalue/CMakeLists.txt b/tests/auto/qml/qjsmanagedvalue/CMakeLists.txt new file mode 100644 index 0000000000..3e466d8e2e --- /dev/null +++ b/tests/auto/qml/qjsmanagedvalue/CMakeLists.txt @@ -0,0 +1,16 @@ +# Generated from qjsmanagedvalue.pro. + +##################################################################### +## tst_qjsmanagedvalue Test: +##################################################################### + +qt_internal_add_test(tst_qjsmanagedvalue + SOURCES + tst_qjsmanagedvalue.cpp tst_qjsmanagedvalue.h + PUBLIC_LIBRARIES + Qt::Qml + Qt::QmlPrivate +) + +## Scopes: +##################################################################### diff --git a/tests/auto/qml/qjsmanagedvalue/qjsmanagedvalue.pro b/tests/auto/qml/qjsmanagedvalue/qjsmanagedvalue.pro new file mode 100644 index 0000000000..4b863a6ecc --- /dev/null +++ b/tests/auto/qml/qjsmanagedvalue/qjsmanagedvalue.pro @@ -0,0 +1,6 @@ +CONFIG += testcase +TARGET = tst_qjsmanagedvalue +macos:CONFIG -= app_bundle +QT += qml testlib qml-private +SOURCES += tst_qjsmanagedvalue.cpp +HEADERS += tst_qjsmanagedvalue.h diff --git a/tests/auto/qml/qjsmanagedvalue/tst_qjsmanagedvalue.cpp b/tests/auto/qml/qjsmanagedvalue/tst_qjsmanagedvalue.cpp new file mode 100644 index 0000000000..009850d2ab --- /dev/null +++ b/tests/auto/qml/qjsmanagedvalue/tst_qjsmanagedvalue.cpp @@ -0,0 +1,1746 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "tst_qjsmanagedvalue.h" + +#include <QtQml/private/qv4engine_p.h> +#include <QtQml/qjsmanagedvalue.h> + +#include <QtCore/qthread.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcomponent.h> +#include <QtCore/qbuffer.h> +#include <QtCore/qvariant.h> +#include <QtCore/qpoint.h> +#include <QtCore/qdatastream.h> +#include <QtCore/qsequentialiterable.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qwaitcondition.h> +#include <QtCore/qthread.h> + +#include <QtTest/qtest.h> + +#include <memory> + +void tst_QJSManagedValue::ctor_invalid() +{ + QJSManagedValue v; + QCOMPARE(v.type(), QJSManagedValue::Undefined); +} + +void tst_QJSManagedValue::ctor_undefinedWithEngine() +{ + QJSEngine eng; + QJSManagedValue v(eng.toManagedValue(QVariant())); + QCOMPARE(v.type(), QJSManagedValue::Undefined); +} + +void tst_QJSManagedValue::ctor_nullWithEngine() +{ + QJSEngine eng; + QJSManagedValue v(eng.evaluate(QStringLiteral("null")), &eng); + QCOMPARE(v.type(), QJSManagedValue::Object); + QVERIFY(v.isNull()); +} + +void tst_QJSManagedValue::ctor_boolWithEngine() +{ + QJSEngine eng; + QJSManagedValue v(eng.toManagedValue(false)); + QCOMPARE(v.type(), QJSManagedValue::Boolean); + QCOMPARE(v.toBoolean(), false); +} + +void tst_QJSManagedValue::ctor_intWithEngine() +{ + QJSEngine eng; + QJSManagedValue v(eng.toManagedValue(int(1))); + QCOMPARE(v.type(), QJSManagedValue::Number); + QCOMPARE(v.toNumber(), 1.0); +} + +void tst_QJSManagedValue::ctor_uintWithEngine() +{ + QJSEngine eng; + QJSManagedValue v(eng.toManagedValue(uint(0x43211234))); + QCOMPARE(v.type(), QJSManagedValue::Number); + QCOMPARE(v.toNumber(), 0x43211234); +} + +void tst_QJSManagedValue::ctor_floatWithEngine() +{ + QJSEngine eng; + QJSManagedValue v(eng.toManagedValue(12345678910.5)); + QCOMPARE(v.type(), QJSManagedValue::Number); + QCOMPARE(v.toNumber(), 12345678910.5); +} + + +void tst_QJSManagedValue::ctor_stringWithEngine() +{ + QJSEngine eng; + QJSManagedValue v(eng.toManagedValue(QStringLiteral("ciao"))); + QCOMPARE(v.type(), QJSManagedValue::String); + QCOMPARE(v.toString(), QStringLiteral("ciao")); +} + +void tst_QJSManagedValue::ctor_copyAndAssignWithEngine() +{ + QJSEngine eng; + // copy constructor, operator= + + QJSManagedValue v(eng.toManagedValue(1.0)); + QJSManagedValue v2(v.toJSValue(), &eng); + QCOMPARE(v2.strictlyEquals(v), true); + + QJSManagedValue v3(v.toJSValue(), &eng); + QCOMPARE(v3.strictlyEquals(v), true); + QCOMPARE(v3.strictlyEquals(v2), true); + + QJSManagedValue v4(eng.toManagedValue(2.0)); + QCOMPARE(v4.strictlyEquals(v), false); + v3 = QJSManagedValue(v4.toJSValue(), &eng); + QCOMPARE(v3.strictlyEquals(v), false); + QCOMPARE(v3.strictlyEquals(v4), true); + + v2 = QJSManagedValue(QJSPrimitiveValue(), &eng); + QCOMPARE(v2.strictlyEquals(v), false); + QCOMPARE(v.toNumber(), 1.0); + + QJSManagedValue v5(v.toJSValue(), &eng); + QCOMPARE(v5.strictlyEquals(v), true); + v = QJSManagedValue(QJSPrimitiveValue(), &eng); + QCOMPARE(v5.strictlyEquals(v), false); + QCOMPARE(v5.toNumber(), 1.0); +} + +void tst_QJSManagedValue::toString() +{ + QJSEngine eng; + + { + QJSManagedValue undefined(eng.toManagedValue(QVariant())); + QCOMPARE(undefined.toString(), QStringLiteral("undefined")); + QCOMPARE(qjsvalue_cast<QString>(undefined), QString()); + } + + { + QJSManagedValue null(eng.evaluate(QStringLiteral("null")), &eng); + QCOMPARE(null.toString(), QStringLiteral("null")); + QCOMPARE(qjsvalue_cast<QString>(null), QString()); + } + + { + QJSManagedValue falskt(eng.toManagedValue(false)); + QCOMPARE(falskt.toString(), QStringLiteral("false")); + QCOMPARE(qjsvalue_cast<QString>(falskt), QStringLiteral("false")); + + QJSManagedValue sant(eng.toManagedValue(true)); + QCOMPARE(sant.toString(), QStringLiteral("true")); + QCOMPARE(qjsvalue_cast<QString>(sant), QStringLiteral("true")); + } + { + QJSManagedValue number(eng.toManagedValue(123)); + QCOMPARE(number.toString(), QStringLiteral("123")); + QCOMPARE(qjsvalue_cast<QString>(number), QStringLiteral("123")); + } + { + QJSManagedValue number(eng.toManagedValue(6.37e-8)); + QCOMPARE(number.toString(), QStringLiteral("6.37e-8")); + } + { + QJSManagedValue number(eng.toManagedValue(-6.37e-8)); + QCOMPARE(number.toString(), QStringLiteral("-6.37e-8")); + + QJSManagedValue str(eng.toManagedValue(QStringLiteral("ciao"))); + QCOMPARE(str.toString(), QStringLiteral("ciao")); + QCOMPARE(qjsvalue_cast<QString>(str), QStringLiteral("ciao")); + } + + { + QJSManagedValue object(eng.newObject(), &eng); + QCOMPARE(object.toString(), QStringLiteral("[object Object]")); + QCOMPARE(qjsvalue_cast<QString>(object), QStringLiteral("[object Object]")); + } + + // toString() that throws exception + { + QVERIFY(!eng.hasError()); + QJSManagedValue objectObject(eng.evaluate(QStringLiteral( + "(function(){" + " o = { };" + " o.toString = function() { throw new Error('toString'); };" + " return o;" + "})()")), &eng); + QCOMPARE(objectObject.type(), QJSManagedValue::Object); + QVERIFY(!objectObject.isNull()); + QVERIFY(!eng.hasError()); + + QCOMPARE(objectObject.toString(), QStringLiteral("undefined")); + + QVERIFY(eng.hasError()); + QJSManagedValue errorObject(eng.catchError(), &eng); + QCOMPARE(errorObject.type(), QJSManagedValue::Object); + QVERIFY(!errorObject.isNull()); + QCOMPARE(errorObject.toString(), QStringLiteral("Error: toString")); + QVERIFY(!eng.hasError()); + } + { + QVERIFY(!eng.hasError()); + QJSManagedValue objectObject(eng.evaluate(QStringLiteral( + "(function(){" + " var f = function() {};" + " f.prototype = Date;" + " return new f;" + "})()")), &eng); + QCOMPARE(objectObject.type(), QJSManagedValue::Object); + QVERIFY(!objectObject.isNull()); + QVERIFY(!eng.hasError()); + QCOMPARE(objectObject.toString(), QStringLiteral("undefined")); + QVERIFY(eng.hasError()); + QJSManagedValue errorObject(eng.catchError(), &eng); + QCOMPARE(errorObject.type(), QJSManagedValue::Object); + QVERIFY(!errorObject.isNull()); + QCOMPARE(errorObject.toString(), QStringLiteral("TypeError: Type error")); + QVERIFY(!eng.hasError()); + } + + QJSManagedValue inv(QJSPrimitiveValue(), &eng); + QCOMPARE(inv.toString(), QStringLiteral("undefined")); + + // variant should use internal valueOf(), then fall back to QVariant::toString(), + // then fall back to "QVariant(typename)" + QJSManagedValue variant(eng.toManagedValue(QPoint(10, 20))); + QCOMPARE(variant.type(), QJSManagedValue::Object); + QCOMPARE(variant.toString(), QStringLiteral("QPoint(10, 20)")); + variant = eng.toManagedValue(QUrl()); + QCOMPARE(variant.type(), QJSManagedValue::Object); + QVERIFY(variant.toString().isEmpty()); + + { + QJSManagedValue o(eng.newObject(), &eng); + o.setProperty(QStringLiteral("test"), QJSValue(42)); + QCOMPARE(o.toString(), QStringLiteral("[object Object]")); + } + + { + QJSManagedValue o(eng.newArray(), &eng); + o.setProperty(0, 1); + o.setProperty(1, 2); + o.setProperty(2, 3); + QCOMPARE(o.toString(), QStringLiteral("1,2,3")); + } + + { + QByteArray hello = QByteArrayLiteral("Hello World"); + QJSManagedValue jsValue(eng.toManagedValue(hello)); + QCOMPARE(jsValue.toString(), QString::fromUtf8(hello)); + } +} + +void tst_QJSManagedValue::toNumber() +{ + QJSEngine eng; + + QJSManagedValue undefined(eng.toManagedValue(QVariant())); + QCOMPARE(qIsNaN(undefined.toNumber()), true); + QCOMPARE(qIsNaN(qjsvalue_cast<qreal>(undefined)), true); + + QJSManagedValue null(eng.evaluate(QStringLiteral("null")), &eng); + QCOMPARE(null.toNumber(), 0.0); + QCOMPARE(qjsvalue_cast<qreal>(null), 0.0); + + { + QJSManagedValue falskt(eng.toManagedValue(false)); + QCOMPARE(falskt.toNumber(), 0.0); + QCOMPARE(qjsvalue_cast<qreal>(falskt), 0.0); + + QJSManagedValue sant(eng.toManagedValue(true)); + QCOMPARE(sant.toNumber(), 1.0); + QCOMPARE(qjsvalue_cast<qreal>(sant), 1.0); + + QJSManagedValue number(eng.toManagedValue(123.0)); + QCOMPARE(number.toNumber(), 123.0); + QCOMPARE(qjsvalue_cast<qreal>(number), 123.0); + + QJSManagedValue str(eng.toManagedValue(QStringLiteral("ciao"))); + QCOMPARE(qIsNaN(str.toNumber()), true); + QCOMPARE(qIsNaN(qjsvalue_cast<qreal>(str)), true); + + QJSManagedValue str2(eng.toManagedValue(QStringLiteral("123"))); + QCOMPARE(str2.toNumber(), 123.0); + QCOMPARE(qjsvalue_cast<qreal>(str2), 123.0); + } + + QJSManagedValue object(eng.newObject(), &eng); + QCOMPARE(qIsNaN(object.toNumber()), true); + QCOMPARE(qIsNaN(qjsvalue_cast<qreal>(object)), true); + + QJSManagedValue inv(QJSPrimitiveValue(), &eng); + QVERIFY(qIsNaN(inv.toNumber())); + QVERIFY(qIsNaN(qjsvalue_cast<qreal>(inv))); + + // V2 constructors + { + QJSManagedValue falskt(QJSPrimitiveValue(false), &eng); + QCOMPARE(falskt.toNumber(), 0.0); + QCOMPARE(qjsvalue_cast<qreal>(falskt), 0.0); + + QJSManagedValue sant(QJSPrimitiveValue(true), &eng); + QCOMPARE(sant.toNumber(), 1.0); + QCOMPARE(qjsvalue_cast<qreal>(sant), 1.0); + + QJSManagedValue number(QJSPrimitiveValue(123.0), &eng); + QCOMPARE(number.toNumber(), 123.0); + QCOMPARE(qjsvalue_cast<qreal>(number), 123.0); + + QJSManagedValue number2(QJSPrimitiveValue(int(0x43211234)), &eng); + QCOMPARE(number2.toNumber(), 1126240820.0); + + QJSManagedValue str(QStringLiteral("ciao"), &eng); + QCOMPARE(qIsNaN(str.toNumber()), true); + QCOMPARE(qIsNaN(qjsvalue_cast<qreal>(str)), true); + + QJSManagedValue str2(QStringLiteral("123"), &eng); + QCOMPARE(str2.toNumber(), 123.0); + QCOMPARE(qjsvalue_cast<qreal>(str2), 123.0); + } +} + +void tst_QJSManagedValue::toBoolean() +{ + QJSEngine eng; + + QJSManagedValue undefined(eng.toManagedValue(QVariant())); + QCOMPARE(undefined.type(), QJSManagedValue::Undefined); + QCOMPARE(undefined.toBoolean(), false); + QCOMPARE(qjsvalue_cast<bool>(undefined), false); + + QJSManagedValue null(eng.evaluate(QStringLiteral("null")), &eng); + QCOMPARE(null.type(), QJSManagedValue::Object); + QVERIFY(null.isNull()); + QCOMPARE(null.toBoolean(), false); + QCOMPARE(qjsvalue_cast<bool>(null), false); + + { + QJSManagedValue falskt = eng.toManagedValue(false); + QCOMPARE(falskt.type(), QJSManagedValue::Boolean); + QCOMPARE(falskt.toBoolean(), false); + QCOMPARE(qjsvalue_cast<bool>(falskt), false); + + QJSManagedValue sant(eng.toManagedValue(true)); + QCOMPARE(sant.type(), QJSManagedValue::Boolean); + QCOMPARE(sant.toBoolean(), true); + QCOMPARE(qjsvalue_cast<bool>(sant), true); + + QJSManagedValue number(eng.toManagedValue(0.0)); + QCOMPARE(number.toBoolean(), false); + QCOMPARE(qjsvalue_cast<bool>(number), false); + + QJSManagedValue number2(eng.toManagedValue(qQNaN())); + QCOMPARE(number2.toBoolean(), false); + QCOMPARE(qjsvalue_cast<bool>(number2), false); + + QJSManagedValue number3(eng.toManagedValue(123.0)); + QCOMPARE(number3.toBoolean(), true); + QCOMPARE(qjsvalue_cast<bool>(number3), true); + + QJSManagedValue number4(eng.toManagedValue(-456.0)); + QCOMPARE(number4.toBoolean(), true); + QCOMPARE(qjsvalue_cast<bool>(number4), true); + + QJSManagedValue str(eng.toManagedValue(QStringLiteral(""))); + QCOMPARE(str.toBoolean(), false); + QCOMPARE(qjsvalue_cast<bool>(str), false); + + QJSManagedValue str2(eng.toManagedValue(QStringLiteral("123"))); + QCOMPARE(str2.toBoolean(), true); + QCOMPARE(qjsvalue_cast<bool>(str2), true); + } + + QJSManagedValue object(eng.newObject(), &eng); + QCOMPARE(object.toBoolean(), true); + QCOMPARE(qjsvalue_cast<bool>(object), true); + + QJSManagedValue inv(QJSPrimitiveValue(), &eng); + QCOMPARE(inv.toBoolean(), false); + QCOMPARE(qjsvalue_cast<bool>(inv), false); + + // V2 constructors + { + QJSManagedValue falskt(QJSPrimitiveValue(false), &eng); + QCOMPARE(falskt.toBoolean(), false); + QCOMPARE(qjsvalue_cast<bool>(falskt), false); + + QJSManagedValue sant(QJSPrimitiveValue(true), &eng); + QCOMPARE(sant.toBoolean(), true); + QCOMPARE(qjsvalue_cast<bool>(sant), true); + + QJSManagedValue number(QJSPrimitiveValue(0.0), &eng); + QCOMPARE(number.toBoolean(), false); + QCOMPARE(qjsvalue_cast<bool>(number), false); + + QJSManagedValue number2(QJSPrimitiveValue(qQNaN()), &eng); + QCOMPARE(number2.toBoolean(), false); + QCOMPARE(qjsvalue_cast<bool>(number2), false); + + QJSManagedValue number3(QJSPrimitiveValue(123.0), &eng); + QCOMPARE(number3.toBoolean(), true); + QCOMPARE(qjsvalue_cast<bool>(number3), true); + + QJSManagedValue number4(QJSPrimitiveValue(-456.0), &eng); + QCOMPARE(number4.toBoolean(), true); + QCOMPARE(qjsvalue_cast<bool>(number4), true); + + QJSManagedValue number5(QJSPrimitiveValue(0x43211234), &eng); + QCOMPARE(number5.toBoolean(), true); + + QJSManagedValue str(QJSPrimitiveValue(QStringLiteral("")), &eng); + QCOMPARE(str.toBoolean(), false); + QCOMPARE(qjsvalue_cast<bool>(str), false); + + QJSManagedValue str2(QJSPrimitiveValue(QStringLiteral("123")), &eng); + QCOMPARE(str2.toBoolean(), true); + QCOMPARE(qjsvalue_cast<bool>(str2), true); + } +} + +void tst_QJSManagedValue::toVariant() +{ + QJSEngine eng; + + { + QJSManagedValue undefined(eng.toManagedValue(QVariant())); + QCOMPARE(undefined.toVariant(), QVariant()); + QCOMPARE(qjsvalue_cast<QVariant>(undefined), QVariant()); + } + + { + QJSManagedValue null(eng.evaluate(QStringLiteral("null")), &eng); + QCOMPARE(null.toVariant(), QVariant::fromValue(nullptr)); + QCOMPARE(qjsvalue_cast<QVariant>(null), QVariant::fromValue(nullptr)); + } + + { + QJSManagedValue number(eng.toManagedValue(123.0)); + QCOMPARE(number.toVariant(), QVariant(123.0)); + QCOMPARE(qjsvalue_cast<QVariant>(number), QVariant(123.0)); + + QJSManagedValue intNumber(eng.toManagedValue(qint32(123))); + QCOMPARE(intNumber.toVariant().typeId(), QVariant((qint32)123).typeId()); + QCOMPARE((qjsvalue_cast<QVariant>(intNumber)).typeId(), + QVariant(qint32(123)).typeId()); + + QJSManagedValue falskt(eng.toManagedValue(false)); + QCOMPARE(falskt.toVariant(), QVariant(false)); + QCOMPARE(qjsvalue_cast<QVariant>(falskt), QVariant(false)); + + QJSManagedValue sant(eng.toManagedValue(true)); + QCOMPARE(sant.toVariant(), QVariant(true)); + QCOMPARE(qjsvalue_cast<QVariant>(sant), QVariant(true)); + + QJSManagedValue str(eng.toManagedValue(QStringLiteral("ciao"))); + QCOMPARE(str.toVariant(), QVariant(QStringLiteral("ciao"))); + QCOMPARE(qjsvalue_cast<QVariant>(str), QVariant(QStringLiteral("ciao"))); + } + + { + QJSManagedValue object(eng.newObject(), &eng); + QVariant retained = object.toVariant(); + QCOMPARE(retained.metaType(), QMetaType::fromType<QJSValue>()); + QVERIFY(QJSManagedValue(retained.value<QJSValue>(), &eng).strictlyEquals(object)); + } + + { + QObject temp; + QJSManagedValue qobject(eng.newQObject(&temp), &eng); + QVariant var = qobject.toVariant(); + QCOMPARE(var.typeId(), int(QMetaType::QObjectStar)); + QCOMPARE(qvariant_cast<QObject*>(var), (QObject *)&temp); + QCOMPARE(qobject.toVariant(), var); + } + + { + QDateTime dateTime = QDate(1980, 10, 4).startOfDay(); + QJSManagedValue dateObject(eng.toManagedValue(dateTime)); + QVariant var = dateObject.toVariant(); + QCOMPARE(var, QVariant(dateTime)); + QCOMPARE(dateObject.toVariant(), var); + } + + { + QRegularExpression rx = QRegularExpression(QStringLiteral("[0-9a-z]+")); + QJSManagedValue rxObject(eng.toManagedValue(rx)); + QVERIFY(rxObject.isRegularExpression()); + QVariant var = rxObject.toVariant(); + QCOMPARE(var, QVariant(rx)); + QCOMPARE(rxObject.toVariant(), var); + } + + { + QJSManagedValue inv; + QCOMPARE(inv.toVariant(), QVariant()); + QCOMPARE(inv.toVariant(), QVariant()); + QCOMPARE(qjsvalue_cast<QVariant>(inv), QVariant()); + } + + // V2 constructors + { + QJSManagedValue number(QJSPrimitiveValue(123.0), &eng); + QCOMPARE(number.toVariant(), QVariant(123.0)); + QCOMPARE(number.toVariant(), QVariant(123.0)); + QCOMPARE(qjsvalue_cast<QVariant>(number), QVariant(123.0)); + + QJSManagedValue falskt(QJSPrimitiveValue(false), &eng); + QCOMPARE(falskt.toVariant(), QVariant(false)); + QCOMPARE(falskt.toVariant(), QVariant(false)); + QCOMPARE(qjsvalue_cast<QVariant>(falskt), QVariant(false)); + + QJSManagedValue sant(QJSPrimitiveValue(true), &eng); + QCOMPARE(sant.toVariant(), QVariant(true)); + QCOMPARE(sant.toVariant(), QVariant(true)); + QCOMPARE(qjsvalue_cast<QVariant>(sant), QVariant(true)); + + QJSManagedValue str(QStringLiteral("ciao"), &eng); + QCOMPARE(str.toVariant(), QVariant(QStringLiteral("ciao"))); + QCOMPARE(str.toVariant(), QVariant(QStringLiteral("ciao"))); + QCOMPARE(qjsvalue_cast<QVariant>(str), QVariant(QStringLiteral("ciao"))); + + QJSManagedValue undef(QJSPrimitiveUndefined(), &eng); + QCOMPARE(undef.toVariant(), QVariant()); + QCOMPARE(undef.toVariant(), QVariant()); + QCOMPARE(qjsvalue_cast<QVariant>(undef), QVariant()); + + QJSManagedValue nil(QJSPrimitiveNull(), &eng); + QCOMPARE(nil.toVariant(), QVariant::fromValue(nullptr)); + QCOMPARE(nil.toVariant(), QVariant::fromValue(nullptr)); + QCOMPARE(qjsvalue_cast<QVariant>(nil), QVariant::fromValue(nullptr)); + } + + // object + { + QVariantMap mapIn; + mapIn[QStringLiteral("a")] = 123; + mapIn[QStringLiteral("b")] = QStringLiteral("hello"); + QJSManagedValue object(eng.toManagedValue(mapIn)); + QCOMPARE(object.type(), QJSManagedValue::Object); + + QVariant retained = object.toVariant(); + QCOMPARE(retained.metaType(), QMetaType::fromType<QJSValue>()); + QVERIFY(QJSManagedValue(retained.value<QJSValue>(), &eng).strictlyEquals(object)); + + QVariantMap mapOut = retained.toMap(); + QCOMPARE(mapOut.size(), mapIn.size()); + for (auto it = mapOut.begin(), end = mapOut.end(); it != end; ++it) + QCOMPARE(*it, mapIn[it.key()]); + + // round-trip conversion + QJSManagedValue object2(eng.toManagedValue(retained)); + QCOMPARE(object2.type(), QJSManagedValue::Object); + QVERIFY(object2.property(QStringLiteral("a")).strictlyEquals(object.property(QStringLiteral("a")))); + QVERIFY(object2.property(QStringLiteral("b")).strictlyEquals(object.property(QStringLiteral("b")))); + } + + + // array + { + auto handler = qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &, const QString &) { + if (type == QtMsgType::QtWarningMsg) + QFAIL("Converting QJSManagedValue to QVariant should not cause error messages"); + }); + auto guard = qScopeGuard([&]() { qInstallMessageHandler(handler); }); + + QVariantList listIn; + listIn << 123 << QStringLiteral("hello"); + QJSManagedValue array(eng.toManagedValue(listIn)); + QVERIFY(array.isArray()); + QCOMPARE(array.property(QStringLiteral("length")).toInt(), 2); + + QVariant retained = array.toVariant(); + QCOMPARE(retained.metaType(), QMetaType::fromType<QJSValue>()); + QVERIFY(QJSManagedValue(retained.value<QJSValue>(), &eng).strictlyEquals(array)); + + QVariantList listOut = retained.toList(); + QCOMPARE(listOut.size(), listIn.size()); + for (int i = 0; i < listIn.size(); ++i) + QCOMPARE(listOut.at(i), listIn.at(i)); + // round-trip conversion + QJSManagedValue array2(eng.toManagedValue(retained)); + QVERIFY(array2.isArray()); + QCOMPARE(array2.property(QStringLiteral("length")).toInt(), array.property(QStringLiteral("length")).toInt()); + for (int i = 0; i < array.property(QStringLiteral("length")).toInt(); ++i) + QVERIFY(array2.property(i).strictlyEquals(array.property(i))); + } + + // function + { + QJSManagedValue func(eng.evaluate(QStringLiteral("(function() { return 5 + 5 })")), &eng); + QVERIFY(func.isCallable()); + QCOMPARE(func.call().toInt(), 10); + + QVariant funcVar = func.toVariant(); + QVERIFY(funcVar.isValid()); + QCOMPARE(funcVar.metaType(), QMetaType::fromType<QJSValue>()); + + QJSManagedValue func2(eng.toManagedValue(funcVar)); + QVERIFY(func2.isCallable()); + QCOMPARE(func2.call().toInt(), 10); + } +} + +void tst_QJSManagedValue::hasProperty_basic() +{ + QJSEngine eng; + QJSManagedValue obj(eng.newObject(), &eng); + QVERIFY(obj.hasProperty(QStringLiteral("hasOwnProperty"))); // inherited from Object.prototype + QVERIFY(!obj.hasOwnProperty(QStringLiteral("hasOwnProperty"))); + + QVERIFY(!obj.hasProperty(QStringLiteral("foo"))); + QVERIFY(!obj.hasOwnProperty(QStringLiteral("foo"))); + obj.setProperty(QStringLiteral("foo"), 123); + QVERIFY(obj.hasProperty(QStringLiteral("foo"))); + QVERIFY(obj.hasOwnProperty(QStringLiteral("foo"))); + + QVERIFY(!obj.hasProperty(QStringLiteral("bar"))); + QVERIFY(!obj.hasOwnProperty(QStringLiteral("bar"))); +} + +void tst_QJSManagedValue::hasProperty_globalObject() +{ + QJSEngine eng; + QJSManagedValue global(eng.globalObject(), &eng); + QVERIFY(global.hasProperty(QStringLiteral("Math"))); + QVERIFY(global.hasOwnProperty(QStringLiteral("Math"))); + QVERIFY(!global.hasProperty(QStringLiteral("NoSuchStandardProperty"))); + QVERIFY(!global.hasOwnProperty(QStringLiteral("NoSuchStandardProperty"))); + + QVERIFY(!global.hasProperty(QStringLiteral("foo"))); + QVERIFY(!global.hasOwnProperty(QStringLiteral("foo"))); + global.setProperty(QStringLiteral("foo"), 123); + QVERIFY(global.hasProperty(QStringLiteral("foo"))); + QVERIFY(global.hasOwnProperty(QStringLiteral("foo"))); +} + +void tst_QJSManagedValue::hasProperty_changePrototype() +{ + QJSEngine eng; + QJSManagedValue obj(eng.newObject(), &eng); + QJSManagedValue proto(eng.newObject(), &eng); + obj.setPrototype(proto); + + QVERIFY(!obj.hasProperty(QStringLiteral("foo"))); + QVERIFY(!obj.hasOwnProperty(QStringLiteral("foo"))); + proto.setProperty(QStringLiteral("foo"), 123); + QVERIFY(obj.hasProperty(QStringLiteral("foo"))); + QVERIFY(!obj.hasOwnProperty(QStringLiteral("foo"))); + + obj.setProperty(QStringLiteral("foo"), 456); // override prototype property + QVERIFY(obj.hasProperty(QStringLiteral("foo"))); + QVERIFY(obj.hasOwnProperty(QStringLiteral("foo"))); +} + +void tst_QJSManagedValue::hasProperty_QTBUG56830_data() +{ + QTest::addColumn<QString>("key"); + QTest::addColumn<QString>("lookup"); + + QTest::newRow("bugreport-1") << QStringLiteral("240000000000") << QStringLiteral("3776798720"); + QTest::newRow("bugreport-2") << QStringLiteral("240000000001") << QStringLiteral("3776798721"); + QTest::newRow("biggest-ok-before-bug") << QStringLiteral("238609294221") << QStringLiteral("2386092941"); + QTest::newRow("smallest-bugged") << QStringLiteral("238609294222") << QStringLiteral("2386092942"); + QTest::newRow("biggest-bugged") << QStringLiteral("249108103166") << QStringLiteral("12884901886"); + QTest::newRow("smallest-ok-after-bug") << QStringLiteral("249108103167") << QStringLiteral("12884901887"); +} + +void tst_QJSManagedValue::hasProperty_QTBUG56830() +{ + QFETCH(QString, key); + QFETCH(QString, lookup); + + QJSEngine eng; + QJSManagedValue value(QJSPrimitiveValue(42), &eng); + + QJSManagedValue obj(eng.newObject(), &eng); + obj.setProperty(key, QJSValue(std::move(value))); + QVERIFY(obj.hasProperty(key)); + QVERIFY(!obj.hasProperty(lookup)); +} + +void tst_QJSManagedValue::deleteProperty_basic() +{ + QJSEngine eng; + QJSManagedValue obj(eng.newObject(), &eng); + // deleteProperty() behavior matches JS delete operator + obj.deleteProperty(QStringLiteral("foo")); + + obj.setProperty(QStringLiteral("foo"), 123); + obj.deleteProperty(QStringLiteral("foo")); + QVERIFY(!obj.hasOwnProperty(QStringLiteral("foo"))); +} + +void tst_QJSManagedValue::deleteProperty_globalObject() +{ + QJSEngine eng; + QJSManagedValue global(eng.globalObject(), &eng); + // deleteProperty() behavior matches JS delete operator + global.deleteProperty(QStringLiteral("foo")); + + global.setProperty(QStringLiteral("foo"), 123); + global.deleteProperty(QStringLiteral("foo")); + QVERIFY(!global.hasProperty(QStringLiteral("foo"))); + + global.deleteProperty(QStringLiteral("Math")); + QVERIFY(!global.hasProperty(QStringLiteral("Math"))); + + global.deleteProperty(QStringLiteral("NaN")); // read-only + QVERIFY(global.hasProperty(QStringLiteral("NaN"))); +} + +void tst_QJSManagedValue::deleteProperty_inPrototype() +{ + QJSEngine eng; + QJSManagedValue obj(eng.newObject(), &eng); + QJSManagedValue proto(eng.newObject(), &eng); + obj.setPrototype(proto); + + proto.setProperty(QStringLiteral("foo"), 123); + QVERIFY(obj.hasProperty(QStringLiteral("foo"))); + // deleteProperty() behavior matches JS delete operator + obj.deleteProperty(QStringLiteral("foo")); + QVERIFY(obj.hasProperty(QStringLiteral("foo"))); +} + +void tst_QJSManagedValue::getSetProperty_propertyRemoval() +{ + QJSEngine eng; + QJSManagedValue object(eng.newObject(), &eng); + QJSManagedValue str(eng.toManagedValue(QStringLiteral("bar"))); + QJSManagedValue num(eng.toManagedValue(123.0)); + + object.setProperty(QStringLiteral("foo"), num.toJSValue()); + QVERIFY(QJSManagedValue(object.property(QStringLiteral("foo")), &eng).strictlyEquals(num)); + object.setProperty(QStringLiteral("bar"), str.toJSValue()); + QVERIFY(QJSManagedValue(object.property(QStringLiteral("bar")), &eng).strictlyEquals(str)); + object.deleteProperty(QStringLiteral("foo")); + QVERIFY(!object.hasOwnProperty(QStringLiteral("foo"))); + QVERIFY(QJSManagedValue(object.property(QStringLiteral("bar")), &eng).strictlyEquals(str)); + object.setProperty(QStringLiteral("foo"), num.toJSValue()); + QVERIFY(QJSManagedValue(object.property(QStringLiteral("foo")), &eng).strictlyEquals(num)); + QVERIFY(QJSManagedValue(object.property(QStringLiteral("bar")), &eng).strictlyEquals(str)); + object.deleteProperty(QStringLiteral("bar")); + QVERIFY(!object.hasOwnProperty(QStringLiteral("bar"))); + QVERIFY(QJSManagedValue(object.property(QStringLiteral("foo")), &eng).strictlyEquals(num)); + object.deleteProperty(QStringLiteral("foo")); + QVERIFY(!object.hasOwnProperty(QStringLiteral("foo"))); + + eng.globalObject().setProperty(QStringLiteral("object3"), object.toJSValue()); + QVERIFY(QJSManagedValue(eng.evaluate(QStringLiteral("object3.hasOwnProperty('foo')")), &eng) + .strictlyEquals(eng.toManagedValue(false))); + object.setProperty(QStringLiteral("foo"), num.toJSValue()); + QVERIFY(QJSManagedValue(eng.evaluate(QStringLiteral("object3.hasOwnProperty('foo')")), &eng) + .strictlyEquals(eng.toManagedValue(true))); + QVERIFY(QJSManagedValue(eng.globalObject(), &eng).deleteProperty(QStringLiteral("object3"))); + QVERIFY(QJSManagedValue(eng.evaluate(QStringLiteral("this.hasOwnProperty('object3')")), &eng) + .strictlyEquals(eng.toManagedValue(false))); +} + +void tst_QJSManagedValue::getSetProperty_resolveMode() +{ + // test ResolveMode + QJSEngine eng; + QJSManagedValue object(eng.newObject(), &eng); + QJSManagedValue prototype(eng.newObject(), &eng); + object.setPrototype(prototype); + QJSManagedValue num2(eng.toManagedValue(456.0)); + prototype.setProperty(QStringLiteral("propertyInPrototype"), num2.toJSValue()); + // default is ResolvePrototype + QVERIFY(QJSManagedValue(object.property(QStringLiteral("propertyInPrototype")), &eng) + .strictlyEquals(num2)); +} + +void tst_QJSManagedValue::getSetProperty_twoEngines() +{ + QJSEngine engine; + QJSManagedValue object(engine.newObject(), &engine); + + QJSEngine otherEngine; + QJSManagedValue otherNum(otherEngine.toManagedValue(123)); + object.setProperty(QStringLiteral("oof"), QJSValue(std::move(otherNum))); + QVERIFY(object.hasOwnProperty(QStringLiteral("oof"))); // primitive values don't have an engine + QVERIFY(object.property(QStringLiteral("oof")).isNumber()); + + QJSManagedValue otherString(otherEngine.toManagedValue(QStringLiteral("nope"))); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setProperty() failed: Value was created in different engine."); + object.setProperty(QStringLiteral("eek"), QJSValue(std::move(otherString))); + QVERIFY(!object.hasOwnProperty(QStringLiteral("eek"))); // strings are managed + QVERIFY(object.property(QStringLiteral("eek")).isUndefined()); +} + +void tst_QJSManagedValue::getSetProperty_gettersAndSettersThrowErrorJS() +{ + // getter/setter that throws an error (from js function) + QJSEngine eng; + QJSManagedValue str(eng.toManagedValue(QStringLiteral("bar"))); + + eng.evaluate(QStringLiteral( + "o = new Object; " + "o.__defineGetter__('foo', function() { throw new Error('get foo') }); " + "o.__defineSetter__('foo', function() { throw new Error('set foo') }); ")); + QJSManagedValue object(eng.evaluate(QStringLiteral("o")), &eng); + QCOMPARE(object.type(), QJSManagedValue::Object); + QVERIFY(!eng.hasError()); + QJSManagedValue ret(object.property(QStringLiteral("foo")), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Undefined); + QVERIFY(eng.hasError()); + QJSManagedValue error(eng.catchError(), &eng); + QCOMPARE(error.toString(), QStringLiteral("Error: get foo")); + QVERIFY(!eng.hasError()); + object.setProperty(QStringLiteral("foo"), QJSValue(std::move(str))); + QVERIFY(eng.hasError()); + QCOMPARE(eng.catchError().toString(), QStringLiteral("Error: set foo")); + QVERIFY(!eng.hasError()); +} + +void tst_QJSManagedValue::getSetProperty_array() +{ + QJSEngine eng; + QJSManagedValue str(eng.toManagedValue(QStringLiteral("bar"))); + QJSManagedValue num(eng.toManagedValue(123.0)); + QJSManagedValue array(eng.newArray(), &eng); + + QVERIFY(array.isArray()); + array.setProperty(0, num.toJSValue()); + QCOMPARE(array.property(0).toNumber(), num.toNumber()); + QCOMPARE(array.property(QStringLiteral("0")).toNumber(), num.toNumber()); + QCOMPARE(array.property(QStringLiteral("length")).toUInt(), quint32(1)); + array.setProperty(1, str.toJSValue()); + QCOMPARE(array.property(1).toString(), str.toString()); + QCOMPARE(array.property(QStringLiteral("1")).toString(), str.toString()); + QCOMPARE(array.property(QStringLiteral("length")).toUInt(), quint32(2)); + array.setProperty(QStringLiteral("length"), eng.toScriptValue(1)); + QCOMPARE(array.property(QStringLiteral("length")).toUInt(), quint32(1)); + QVERIFY(array.property(1).isUndefined()); +} + +void tst_QJSManagedValue::getSetProperty() +{ + QJSEngine eng; + + QJSManagedValue object(eng.newObject(), &eng); + + QJSManagedValue str(eng.toManagedValue(QStringLiteral("bar"))); + object.setProperty(QStringLiteral("foo"), str.toJSValue()); + QCOMPARE(object.property(QStringLiteral("foo")).toString(), str.toString()); + + QJSManagedValue num(eng.toManagedValue(123.0)); + object.setProperty(QStringLiteral("baz"), num.toJSValue()); + QCOMPARE(object.property(QStringLiteral("baz")).toNumber(), num.toNumber()); + + QJSManagedValue strstr(QJSPrimitiveValue("bar"), &eng); + object.setProperty(QStringLiteral("foo"), strstr.toJSValue()); + QCOMPARE(object.property(QStringLiteral("foo")).toString(), strstr.toString()); + + QJSManagedValue numnum(QJSPrimitiveValue(123.0), &eng); + object.setProperty(QStringLiteral("baz"), numnum.toJSValue()); + QCOMPARE(object.property(QStringLiteral("baz")).toNumber(), numnum.toNumber()); + + QJSManagedValue inv; + inv.setProperty(QStringLiteral("foo"), num.toJSValue()); + QCOMPARE(inv.property(QStringLiteral("foo")).isUndefined(), true); + + eng.globalObject().setProperty(QStringLiteral("object"), object.toJSValue()); + + // Setting index property on non-Array + object.setProperty(13, num.toJSValue()); + QVERIFY(QJSManagedValue(object.property(13), &eng).equals(num)); +} + +void tst_QJSManagedValue::getSetPrototype_cyclicPrototype() +{ + QJSEngine eng; + QJSManagedValue prototype(eng.newObject(), &eng); + QJSManagedValue object(eng.newObject(), &eng); + object.setPrototype(prototype); + + QJSManagedValue previousPrototype = prototype.prototype(); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Prototype cycle detected."); + prototype.setPrototype(prototype); + QCOMPARE(prototype.prototype().strictlyEquals(previousPrototype), true); + + object.setPrototype(prototype); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Prototype cycle detected."); + prototype.setPrototype(object); + QCOMPARE(prototype.prototype().strictlyEquals(previousPrototype), true); +} + +void tst_QJSManagedValue::getSetPrototype_evalCyclicPrototype() +{ + QJSEngine eng; + QJSManagedValue ret(eng.evaluate(QStringLiteral("o = { }; p = { }; o.__proto__ = p; p.__proto__ = o")), &eng); + QEXPECT_FAIL("", "QJSEngine::evaluate() catches exceptions.", Abort); + QCOMPARE(ret.type(), QJSManagedValue::Undefined); + QVERIFY(eng.hasError()); + QJSManagedValue error(eng.catchError(), &eng); + QCOMPARE(error.toString(), QStringLiteral("TypeError: Could not change prototype.")); + QVERIFY(!eng.hasError()); +} + +void tst_QJSManagedValue::getSetPrototype_eval() +{ + QJSEngine eng; + QJSManagedValue ret(eng.evaluate(QStringLiteral("p = { }; p.__proto__ = { }")), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Object); + QVERIFY(!eng.hasError()); +} + +void tst_QJSManagedValue::getSetPrototype_invalidPrototype() +{ + QJSEngine eng; + QJSManagedValue inv; + QJSManagedValue object(eng.newObject(), &eng); + QJSManagedValue proto = object.prototype(); + QVERIFY(object.prototype().strictlyEquals(proto)); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Can only set a prototype on an object (excluding null)."); + inv.setPrototype(object); + QCOMPARE(inv.prototype().type(), QJSManagedValue::Undefined); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Can only set objects (including null) as prototypes."); + object.setPrototype(inv); + QVERIFY(object.prototype().strictlyEquals(proto)); +} + +void tst_QJSManagedValue::getSetPrototype_twoEngines() +{ + QJSEngine eng; + QJSManagedValue prototype(eng.newObject(), &eng); + QJSManagedValue object(eng.newObject(), &eng); + object.setPrototype(prototype); + QJSEngine otherEngine; + QJSManagedValue newPrototype(otherEngine.newObject(), &otherEngine); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Prototype was created in differen engine."); + object.setPrototype(newPrototype); + QCOMPARE(object.prototype().strictlyEquals(prototype), true); + +} + +void tst_QJSManagedValue::getSetPrototype_null() +{ + QJSEngine eng; + QJSManagedValue object(eng.newObject(), &eng); + object.setPrototype(QJSManagedValue(QJSPrimitiveNull(), &eng)); + QVERIFY(object.prototype().isNull()); + + QJSManagedValue newProto(eng.newObject(), &eng); + object.setPrototype(newProto); + QVERIFY(object.prototype().equals(newProto)); + + object.setPrototype(QJSManagedValue(eng.evaluate(QStringLiteral("null")), &eng)); + QVERIFY(object.prototype().isNull()); +} + +void tst_QJSManagedValue::getSetPrototype_notObjectOrNull() +{ + QJSEngine eng; + QJSManagedValue object(eng.newObject(), &eng); + QJSManagedValue originalProto = object.prototype(); + + // bool + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Can only set objects (including null) as prototypes."); + object.setPrototype({QJSPrimitiveValue(true), &eng}); + QVERIFY(object.prototype().equals(originalProto)); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Can only set objects (including null) as prototypes."); + object.setPrototype(eng.toManagedValue(true)); + QVERIFY(object.prototype().equals(originalProto)); + + // number + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Can only set objects (including null) as prototypes."); + object.setPrototype({QJSPrimitiveValue(123), &eng}); + QVERIFY(object.prototype().equals(originalProto)); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Can only set objects (including null) as prototypes."); + object.setPrototype(eng.toManagedValue(123)); + QVERIFY(object.prototype().equals(originalProto)); + + // string + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Can only set objects (including null) as prototypes."); + object.setPrototype({QStringLiteral("foo"), &eng}); + QVERIFY(object.prototype().equals(originalProto)); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Can only set objects (including null) as prototypes."); + object.setPrototype(eng.toManagedValue(QStringLiteral("foo"))); + QVERIFY(object.prototype().equals(originalProto)); + + // undefined + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Can only set objects (including null) as prototypes."); + object.setPrototype(QJSManagedValue(QJSPrimitiveUndefined(), &eng)); + QVERIFY(object.prototype().equals(originalProto)); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::setPrototype() failed: Can only set objects (including null) as prototypes."); + object.setPrototype(eng.toManagedValue(QVariant())); + QVERIFY(object.prototype().equals(originalProto)); +} + +void tst_QJSManagedValue::getSetPrototype() +{ + QJSEngine eng; + QJSManagedValue prototype(eng.newObject(), &eng); + QJSManagedValue object(eng.newObject(), &eng); + object.setPrototype(prototype); + QVERIFY(object.prototype().strictlyEquals(prototype)); +} + +void tst_QJSManagedValue::call_function() +{ + QJSEngine eng; + QJSManagedValue fun(eng.evaluate(QStringLiteral("(function() { return 1; })")), &eng); + QVERIFY(fun.isCallable()); + QJSManagedValue result(fun.call(), &eng); + QCOMPARE(result.type(), QJSManagedValue::Number); + QCOMPARE(result.toInteger(), 1); +} + +void tst_QJSManagedValue::call_object() +{ + QJSEngine eng; + QJSManagedValue object(eng.evaluate(QStringLiteral("Object")), &eng); + QCOMPARE(object.isCallable(), true); + QJSManagedValue result(object.callWithInstance(object.toJSValue()), &eng); + QCOMPARE(result.type(), QJSManagedValue::Object); + QVERIFY(!result.isNull()); +} + +void tst_QJSManagedValue::call_newObjects() +{ + QJSEngine eng; + // test that call() doesn't construct new objects + QJSManagedValue number(eng.evaluate(QStringLiteral("Number")), &eng); + QJSManagedValue object(eng.evaluate(QStringLiteral("Object")), &eng); + QCOMPARE(object.isCallable(), true); + QJSValueList args; + args << eng.toScriptValue(123); + QJSManagedValue result(number.callWithInstance(object.toJSValue(), args), &eng); + QVERIFY(result.strictlyEquals(QJSManagedValue(args.at(0), &eng))); +} + +void tst_QJSManagedValue::call_this() +{ + QJSEngine eng; + // test that correct "this" object is used + QJSManagedValue fun(eng.evaluate(QStringLiteral("(function() { return this; })")), &eng); + QCOMPARE(fun.isCallable(), true); + + QJSManagedValue numberObject(eng.evaluate(QStringLiteral("new Number(123)")), &eng); + QJSManagedValue result(fun.callWithInstance(QJSValue(std::move(numberObject))), &eng); + QCOMPARE(result.type(), QJSManagedValue::Object); + QCOMPARE(result.toNumber(), 123.0); +} + +void tst_QJSManagedValue::call_arguments() +{ + QJSEngine eng; + // test that correct arguments are passed + + QJSManagedValue fun(eng.evaluate(QStringLiteral("(function() { return arguments[0]; })")), &eng); + QCOMPARE(fun.isCallable(), true); + { + QJSManagedValue result(fun.callWithInstance(eng.toScriptValue(QVariant())), &eng); + QCOMPARE(result.type(), QJSManagedValue::Undefined); + } + { + QJSValueList args; + args << eng.toScriptValue(123.0); + QJSManagedValue result(fun.callWithInstance(eng.toScriptValue(QVariant()), args), &eng); + QCOMPARE(result.type(), QJSManagedValue::Number); + QCOMPARE(result.toNumber(), 123.0); + } + // V2 constructors + { + QJSValueList args; + args << QJSValue(123.0); + QJSManagedValue result(fun.callWithInstance(eng.toScriptValue(QVariant()), args), &eng); + QCOMPARE(result.type(), QJSManagedValue::Number); + QCOMPARE(result.toNumber(), 123.0); + } +} + +void tst_QJSManagedValue::call() +{ + QJSEngine eng; + { + QJSManagedValue fun(eng.evaluate(QStringLiteral("(function() { return arguments[1]; })")), &eng); + QCOMPARE(fun.isCallable(), true); + + { + QJSValueList args; + args << eng.toScriptValue(123.0) << eng.toScriptValue(456.0); + QJSManagedValue result(fun.callWithInstance(eng.toScriptValue(QVariant()), args), &eng); + QCOMPARE(result.type(), QJSManagedValue::Number); + QCOMPARE(result.toNumber(), 456.0); + } + } + { + QJSManagedValue fun(eng.evaluate(QStringLiteral("(function() { throw new Error('foo'); })")), &eng); + QCOMPARE(fun.isCallable(), true); + QVERIFY(!eng.hasError()); + + { + QJSManagedValue result(fun.call(), &eng); + QCOMPARE(result.type(), QJSManagedValue::Undefined); + QJSManagedValue error(eng.catchError(), &eng); + } + } +} + +void tst_QJSManagedValue::call_twoEngines() +{ + QJSEngine eng; + QJSManagedValue object(eng.evaluate(QStringLiteral("Object")), &eng); + QJSEngine otherEngine; + QJSManagedValue fun(otherEngine.evaluate(QStringLiteral("(function() { return 1; })")), &otherEngine); + QVERIFY(fun.isCallable()); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::callWithInstance() failed: Instance was created in different engine."); + QVERIFY(fun.callWithInstance(QJSValue(std::move(object))).isUndefined()); + + // Primitive value doesn't need an engine + QVERIFY(!fun.call(QJSValueList() << eng.toScriptValue(123)).isUndefined()); + + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::call() failed: Argument was created in different engine."); + QVERIFY(fun.call(QJSValueList() << eng.toScriptValue(QStringLiteral("string"))) + .isUndefined()); + { + QJSManagedValue fun(eng.evaluate(QStringLiteral("Object")), &eng); + QVERIFY(fun.isCallable()); + QJSEngine eng2; + QJSManagedValue objectInDifferentEngine(eng2.newObject(), &eng2); + QJSValueList args; + args << QJSValue(std::move(objectInDifferentEngine)); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::call() failed: Argument was created in different engine."); + fun.call(args); + } +} + +void tst_QJSManagedValue::call_nonFunction_data() +{ + newEngine(); + QTest::addColumn<QJSValue>("value"); + + QTest::newRow("invalid") << QJSValue(); + QTest::newRow("bool") << QJSValue(false); + QTest::newRow("int") << QJSValue(123); + QTest::newRow("string") << QJSValue(QStringLiteral("ciao")); + QTest::newRow("undefined") << QJSValue(QJSValue::UndefinedValue); + QTest::newRow("null") << QJSValue(QJSValue::NullValue); + + QTest::newRow("bool bound") << engine->toScriptValue(false); + QTest::newRow("int bound") << engine->toScriptValue(123); + QTest::newRow("string bound") << engine->toScriptValue(QStringLiteral("ciao")); + QTest::newRow("undefined bound") << engine->toScriptValue(QVariant()); + QTest::newRow("null bound") << engine->evaluate(QStringLiteral("null")); +} + +void tst_QJSManagedValue::call_nonFunction() +{ + // calling things that are not functions + QFETCH(QJSValue, value); + QVERIFY(QJSManagedValue(std::move(value), engine.data()).call().isUndefined()); +} + +void tst_QJSManagedValue::construct_nonFunction_data() +{ + call_nonFunction_data(); +} + +void tst_QJSManagedValue::construct_nonFunction() +{ + QFETCH(QJSValue, value); + QVERIFY(QJSManagedValue(std::move(value), engine.data()).callAsConstructor().isUndefined()); +} + +void tst_QJSManagedValue::construct_simple() +{ + QJSEngine eng; + QJSManagedValue fun(eng.evaluate(QStringLiteral("(function () { this.foo = 123; })")), &eng); + QVERIFY(fun.isCallable()); + QJSManagedValue ret(fun.callAsConstructor(), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Object); + QVERIFY(!ret.isNull()); + QVERIFY(ret.prototype().strictlyEquals( + QJSManagedValue(fun.property(QStringLiteral("prototype")), &eng))); + QCOMPARE(ret.property(QStringLiteral("foo")).toInt(), 123); +} + +void tst_QJSManagedValue::construct_newObjectJS() +{ + QJSEngine eng; + // returning a different object overrides the default-constructed one + QJSManagedValue fun(eng.evaluate(QStringLiteral("(function () { return { bar: 456 }; })")), &eng); + QVERIFY(fun.isCallable()); + QJSManagedValue ret(fun.callAsConstructor(), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Object); + QVERIFY(!ret.isNull()); + QVERIFY(!ret.prototype().strictlyEquals( + QJSManagedValue(fun.property(QStringLiteral("prototype")), &eng))); + QCOMPARE(ret.property(QStringLiteral("bar")).toInt(), 456); +} + +void tst_QJSManagedValue::construct_arg() +{ + QJSEngine eng; + QJSManagedValue Number(eng.evaluate(QStringLiteral("Number")), &eng); + QCOMPARE(Number.isCallable(), true); + QJSValueList args; + args << eng.toScriptValue(123); + QJSManagedValue ret(Number.callAsConstructor(args), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Object); + QVERIFY(!ret.isNull()); + QCOMPARE(ret.toNumber(), args.at(0).toNumber()); +} + +void tst_QJSManagedValue::construct_proto() +{ + QJSEngine eng; + // test that internal prototype is set correctly + QJSManagedValue fun(eng.evaluate(QStringLiteral("(function() { return this.__proto__; })")), &eng); + QCOMPARE(fun.isCallable(), true); + QCOMPARE(fun.property(QStringLiteral("prototype")).isObject(), true); + QJSManagedValue ret(fun.callAsConstructor(), &eng); + QVERIFY(QJSManagedValue(fun.property(QStringLiteral("prototype")), &eng).strictlyEquals(ret)); +} + +void tst_QJSManagedValue::construct_returnInt() +{ + QJSEngine eng; + // test that we return the new object even if a non-object value is returned from the function + QJSManagedValue fun(eng.evaluate(QStringLiteral("(function() { return 123; })")), &eng); + QCOMPARE(fun.isCallable(), true); + QJSManagedValue ret(fun.callAsConstructor(), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Object); + QVERIFY(!ret.isNull()); +} + +void tst_QJSManagedValue::construct_throw() +{ + QJSEngine eng; + QJSManagedValue fun(eng.evaluate(QStringLiteral("(function() { throw new Error('foo'); })")), &eng); + QCOMPARE(fun.isCallable(), true); + QVERIFY(!eng.hasError()); + QJSManagedValue ret(fun.callAsConstructor(), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Undefined); + QVERIFY(eng.hasError()); + QJSManagedValue error(eng.catchError(), &eng); + QVERIFY(!eng.hasError()); +} + +void tst_QJSManagedValue::construct_twoEngines() +{ + QJSEngine engine; + QJSEngine otherEngine; + QJSManagedValue ctor(engine.evaluate(QStringLiteral("(function (a, b) { this.foo = 123; })")), &engine); + + QJSManagedValue arg(otherEngine.toManagedValue(124567)); + QVERIFY(!ctor.callAsConstructor(QJSValueList() << arg.toJSValue()).isUndefined()); + + QJSManagedValue arg2(otherEngine.toManagedValue(QStringLiteral("string"))); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::callAsConstructor() failed: Argument was created in different engine."); + QVERIFY(ctor.callAsConstructor(QJSValueList() << arg2.toJSValue()).isUndefined()); + QTest::ignoreMessage(QtWarningMsg, "QJSManagedValue::callAsConstructor() failed: Argument was created in different engine."); + + QVERIFY(ctor.callAsConstructor(QJSValueList() << arg.toJSValue() << otherEngine.newObject()).isUndefined()); +} + +void tst_QJSManagedValue::construct_constructorThrowsPrimitive() +{ + QJSEngine eng; + QJSManagedValue fun(eng.evaluate(QStringLiteral("(function() { throw 123; })")), &eng); + QVERIFY(fun.isCallable()); + // construct(QJSValueList) + { + QJSManagedValue ret(fun.callAsConstructor(), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Undefined); + QVERIFY(eng.hasError()); + QJSManagedValue error(eng.catchError(), &eng); + QCOMPARE(error.toNumber(), 123.0); + QVERIFY(!eng.hasError()); + } +} + +void tst_QJSManagedValue::equals() +{ + QJSEngine eng; + QObject temp; + + QVERIFY(QJSManagedValue().equals(QJSManagedValue())); + + QJSManagedValue num(eng.toManagedValue(123)); + QVERIFY(num.equals(eng.toManagedValue(123))); + QVERIFY(!num.equals(eng.toManagedValue(321))); + QVERIFY(num.equals(eng.toManagedValue(QStringLiteral("123")))); + QVERIFY(!num.equals(eng.toManagedValue(QStringLiteral("321")))); + QVERIFY(num.equals({eng.evaluate(QStringLiteral("new Number(123)")), &eng})); + QVERIFY(!num.equals({eng.evaluate(QStringLiteral("new Number(321)")), &eng})); + QVERIFY(num.equals({eng.evaluate(QStringLiteral("new String('123')")), &eng})); + QVERIFY(!num.equals({eng.evaluate(QStringLiteral("new String('321')")), &eng})); + QVERIFY(QJSManagedValue(eng.evaluate(QStringLiteral("new Number(123)")), &eng).equals(num)); + QVERIFY(!num.equals(QJSManagedValue())); + + QJSManagedValue str(eng.toManagedValue(QStringLiteral("123"))); + QVERIFY(str.equals(eng.toManagedValue(QStringLiteral("123")))); + QVERIFY(!str.equals(eng.toManagedValue(QStringLiteral("321")))); + QVERIFY(str.equals(eng.toManagedValue(123))); + QVERIFY(!str.equals(eng.toManagedValue(321))); + QVERIFY(str.equals({eng.evaluate(QStringLiteral("new String('123')")), &eng})); + QVERIFY(!str.equals({eng.evaluate(QStringLiteral("new String('321')")), &eng})); + QVERIFY(str.equals({eng.evaluate(QStringLiteral("new Number(123)")), &eng})); + QVERIFY(!str.equals({eng.evaluate(QStringLiteral("new Number(321)")), &eng})); + QVERIFY(QJSManagedValue(eng.evaluate(QStringLiteral("new String('123')")), &eng).equals(str)); + QCOMPARE(str.equals(QJSManagedValue()), false); + + QJSManagedValue num2(QJSPrimitiveValue(123), &eng); + QVERIFY(num2.equals(QJSManagedValue(QJSPrimitiveValue(123), &eng))); + QVERIFY(!num2.equals(QJSManagedValue(QJSPrimitiveValue(321), &eng))); + QVERIFY(num2.equals(QJSManagedValue(QStringLiteral("123"), &eng))); + QVERIFY(!num2.equals(QJSManagedValue(QStringLiteral("321"), &eng))); + QVERIFY(!num2.equals(QJSManagedValue())); + + QJSManagedValue str2(QStringLiteral("123"), &eng); + QVERIFY(str2.equals(QJSManagedValue(QStringLiteral("123"), &eng))); + QVERIFY(!str2.equals(QJSManagedValue(QStringLiteral("321"), &eng))); + QVERIFY(str2.equals(QJSManagedValue(QJSPrimitiveValue(123), &eng))); + QVERIFY(!str2.equals(QJSManagedValue(QJSPrimitiveValue(321), &eng))); + QVERIFY(!str2.equals(QJSManagedValue())); + + QJSManagedValue date1(eng.toManagedValue(QDate(2000, 1, 1).startOfDay())); + QJSManagedValue date2(eng.toManagedValue(QDate(1999, 1, 1).startOfDay())); + QCOMPARE(date1.equals(date2), false); + QCOMPARE(date1.equals(date1), true); + QCOMPARE(date2.equals(date2), true); + + QJSManagedValue undefined(eng.toManagedValue(QVariant())); + QJSManagedValue null(eng.evaluate(QStringLiteral("null")), &eng); + QCOMPARE(undefined.equals(undefined), true); + QCOMPARE(null.equals(null), true); + QCOMPARE(undefined.equals(null), true); + QCOMPARE(null.equals(undefined), true); + QVERIFY(undefined.equals(QJSManagedValue())); + QVERIFY(null.equals(QJSManagedValue())); + QVERIFY(!null.equals(num)); + QVERIFY(!undefined.equals(num)); + + QJSManagedValue sant(eng.toManagedValue(true)); + QVERIFY(sant.equals(eng.toManagedValue(1))); + QVERIFY(sant.equals(eng.toManagedValue(QStringLiteral("1")))); + QVERIFY(sant.equals(sant)); + QVERIFY(sant.equals({eng.evaluate(QStringLiteral("new Number(1)")), &eng})); + QVERIFY(sant.equals({eng.evaluate(QStringLiteral("new String('1')")), &eng})); + QVERIFY(sant.equals({eng.evaluate(QStringLiteral("new Boolean(true)")), &eng})); + QVERIFY(QJSManagedValue(eng.evaluate(QStringLiteral("new Boolean(true)")), &eng).equals(sant)); + QVERIFY(!sant.equals(eng.toManagedValue(0))); + QVERIFY(!sant.equals(undefined)); + QVERIFY(!sant.equals(null)); + + QJSManagedValue falskt(eng.toManagedValue(false)); + QVERIFY(falskt.equals(eng.toManagedValue(0))); + QVERIFY(falskt.equals(eng.toManagedValue(QStringLiteral("0")))); + QVERIFY(falskt.equals(falskt)); + QVERIFY(falskt.equals({eng.evaluate(QStringLiteral("new Number(0)")), &eng})); + QVERIFY(falskt.equals({eng.evaluate(QStringLiteral("new String('0')")), &eng})); + QVERIFY(falskt.equals({eng.evaluate(QStringLiteral("new Boolean(false)")), &eng})); + QVERIFY(QJSManagedValue(eng.evaluate(QStringLiteral("new Boolean(false)")), &eng).equals(falskt)); + QVERIFY(!falskt.equals(sant)); + QVERIFY(!falskt.equals(undefined)); + QVERIFY(!falskt.equals(null)); + + QJSManagedValue obj1(eng.newObject(), &eng); + QJSManagedValue obj2(eng.newObject(), &eng); + QCOMPARE(obj1.equals(obj2), false); + QCOMPARE(obj2.equals(obj1), false); + QCOMPARE(obj1.equals(obj1), true); + QCOMPARE(obj2.equals(obj2), true); + + QJSManagedValue qobj1(eng.newQObject(&temp), &eng); + QJSManagedValue qobj2(eng.newQObject(&temp), &eng); + QJSManagedValue qobj3(eng.newQObject(nullptr), &eng); + + // FIXME: No ScriptOwnership: QJSManagedValue qobj4(eng.newQObject(new QObject(), QScriptEngine::ScriptOwnership), &eng); + QJSManagedValue qobj4(eng.newQObject(new QObject()), &eng); + + QVERIFY(qobj1.equals(qobj2)); // compares the QObject pointers + QVERIFY(!qobj2.equals(qobj4)); // compares the QObject pointers + QVERIFY(!qobj2.equals(obj2)); // compares the QObject pointers + + QJSManagedValue compareFun(eng.evaluate(QStringLiteral("(function(a, b) { return a == b; })")), &eng); + QVERIFY(compareFun.isCallable()); + { + QJSManagedValue ret(compareFun.call(QJSValueList() << qobj1.toJSValue() << qobj2.toJSValue()), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Boolean); + ret = QJSManagedValue(compareFun.call(QJSValueList() << qobj1.toJSValue() << qobj3.toJSValue()), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Boolean); + QVERIFY(!ret.toBoolean()); + ret = QJSManagedValue(compareFun.call(QJSValueList() << qobj1.toJSValue() << qobj4.toJSValue()), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Boolean); + QVERIFY(!ret.toBoolean()); + ret = QJSManagedValue(compareFun.call(QJSValueList() << qobj1.toJSValue() << obj1.toJSValue()), &eng); + QCOMPARE(ret.type(), QJSManagedValue::Boolean); + QVERIFY(!ret.toBoolean()); + } + + { + QJSManagedValue var1(eng.toManagedValue(QVariant::fromValue(QPoint(1, 2)))); + QJSManagedValue var2(eng.toManagedValue(QVariant::fromValue(QPoint(1, 2)))); + QVERIFY(var1.equals(var2)); + } + { + QJSManagedValue var1(eng.toManagedValue(QVariant::fromValue(QPoint(1, 2)))); + QJSManagedValue var2(eng.toManagedValue(QVariant::fromValue(QPoint(3, 4)))); + QVERIFY(!var1.equals(var2)); + } +} + +void tst_QJSManagedValue::strictlyEquals() +{ + QJSEngine eng; + QObject temp; + + QVERIFY(QJSManagedValue().strictlyEquals(QJSManagedValue())); + + QJSManagedValue num(eng.toManagedValue(123)); + QVERIFY(num.strictlyEquals(eng.toManagedValue(123))); + QVERIFY(!num.strictlyEquals(eng.toManagedValue(321))); + QVERIFY(!num.strictlyEquals(eng.toManagedValue(QStringLiteral("123")))); + QVERIFY(!num.strictlyEquals(eng.toManagedValue(QStringLiteral("321")))); + QVERIFY(!num.strictlyEquals({eng.evaluate(QStringLiteral("new Number(123)")), &eng})); + QVERIFY(!num.strictlyEquals({eng.evaluate(QStringLiteral("new Number(321)")), &eng})); + QVERIFY(!num.strictlyEquals({eng.evaluate(QStringLiteral("new String('123')")), &eng})); + QVERIFY(!num.strictlyEquals({eng.evaluate(QStringLiteral("new String('321')")), &eng})); + QVERIFY(!QJSManagedValue(eng.evaluate(QStringLiteral("new Number(123)")), &eng).strictlyEquals(num)); + QVERIFY(!num.strictlyEquals(QJSManagedValue())); + QVERIFY(!QJSManagedValue().strictlyEquals(num)); + + QJSManagedValue str(eng.toManagedValue(QStringLiteral("123"))); + QVERIFY(str.strictlyEquals(eng.toManagedValue(QStringLiteral("123")))); + QVERIFY(!str.strictlyEquals(eng.toManagedValue(QStringLiteral("321")))); + QVERIFY(!str.strictlyEquals(eng.toManagedValue(123))); + QVERIFY(!str.strictlyEquals(eng.toManagedValue(321))); + QVERIFY(!str.strictlyEquals({eng.evaluate(QStringLiteral("new String('123')")), &eng})); + QVERIFY(!str.strictlyEquals({eng.evaluate(QStringLiteral("new String('321')")), &eng})); + QVERIFY(!str.strictlyEquals({eng.evaluate(QStringLiteral("new Number(123)")), &eng})); + QVERIFY(!str.strictlyEquals({eng.evaluate(QStringLiteral("new Number(321)")), &eng})); + QVERIFY(!QJSManagedValue(eng.evaluate(QStringLiteral("new String('123')")), &eng).strictlyEquals(str)); + QVERIFY(!str.strictlyEquals(QJSManagedValue())); + + QJSManagedValue num2(QJSPrimitiveValue(123), &eng); + QVERIFY(num2.strictlyEquals({QJSPrimitiveValue(123), &eng})); + QVERIFY(!num2.strictlyEquals({QJSPrimitiveValue(321), &eng})); + QVERIFY(!num2.strictlyEquals({QStringLiteral("123"), &eng})); + QVERIFY(!num2.strictlyEquals({QStringLiteral("321"), &eng})); + QVERIFY(!num2.strictlyEquals(QJSManagedValue())); + + QJSManagedValue str2(QStringLiteral("123"), &eng); + QVERIFY(str2.strictlyEquals({QStringLiteral("123"), &eng})); + QVERIFY(!str2.strictlyEquals({QStringLiteral("321"), &eng})); + QVERIFY(!str2.strictlyEquals({QJSPrimitiveValue(123), &eng})); + QVERIFY(!str2.strictlyEquals({QJSPrimitiveValue(321), &eng})); + QVERIFY(!str2.strictlyEquals(QJSManagedValue())); + + QJSManagedValue date1(eng.toManagedValue(QDate(2000, 1, 1).startOfDay())); + QJSManagedValue date2(eng.toManagedValue(QDate(1999, 1, 1).startOfDay())); + QCOMPARE(date1.strictlyEquals(date2), false); + QCOMPARE(date1.strictlyEquals(date1), true); + QCOMPARE(date2.strictlyEquals(date2), true); + QVERIFY(!date1.strictlyEquals(QJSManagedValue())); + + QJSManagedValue undefined(eng.toManagedValue(QVariant())); + QJSManagedValue null(eng.evaluate(QStringLiteral("null")), &eng); + QCOMPARE(undefined.strictlyEquals(undefined), true); + QCOMPARE(null.strictlyEquals(null), true); + QCOMPARE(undefined.strictlyEquals(null), false); + QCOMPARE(null.strictlyEquals(undefined), false); + QVERIFY(!null.strictlyEquals(QJSManagedValue())); + + QJSManagedValue sant(eng.toManagedValue(true)); + QVERIFY(!sant.strictlyEquals(eng.toManagedValue(1))); + QVERIFY(!sant.strictlyEquals(eng.toManagedValue(QStringLiteral("1")))); + QVERIFY(sant.strictlyEquals(sant)); + QVERIFY(!sant.strictlyEquals({eng.evaluate(QStringLiteral("new Number(1)")), &eng})); + QVERIFY(!sant.strictlyEquals({eng.evaluate(QStringLiteral("new String('1')")), &eng})); + QVERIFY(!sant.strictlyEquals({eng.evaluate(QStringLiteral("new Boolean(true)")), &eng})); + QVERIFY(!QJSManagedValue(eng.evaluate(QStringLiteral("new Boolean(true)")), &eng).strictlyEquals(sant)); + QVERIFY(!sant.strictlyEquals(eng.toManagedValue(0))); + QVERIFY(!sant.strictlyEquals(undefined)); + QVERIFY(!sant.strictlyEquals(null)); + QVERIFY(!sant.strictlyEquals(QJSManagedValue())); + + QJSManagedValue falskt(eng.toManagedValue(false)); + QVERIFY(!falskt.strictlyEquals(eng.toManagedValue(0))); + QVERIFY(!falskt.strictlyEquals(eng.toManagedValue(QStringLiteral("0")))); + QVERIFY(falskt.strictlyEquals(falskt)); + QVERIFY(!falskt.strictlyEquals({eng.evaluate(QStringLiteral("new Number(0)")), &eng})); + QVERIFY(!falskt.strictlyEquals({eng.evaluate(QStringLiteral("new String('0')")), &eng})); + QVERIFY(!falskt.strictlyEquals({eng.evaluate(QStringLiteral("new Boolean(false)")), &eng})); + QVERIFY(!QJSManagedValue(eng.evaluate(QStringLiteral("new Boolean(false)")), &eng).strictlyEquals(falskt)); + QVERIFY(!falskt.strictlyEquals(sant)); + QVERIFY(!falskt.strictlyEquals(undefined)); + QVERIFY(!falskt.strictlyEquals(null)); + QVERIFY(!falskt.strictlyEquals(QJSManagedValue())); + + QVERIFY(!QJSManagedValue(QJSPrimitiveValue(false), &eng).strictlyEquals({QJSPrimitiveValue(123), &eng})); + QVERIFY(!QJSManagedValue(QJSPrimitiveUndefined(), &eng).strictlyEquals({QJSPrimitiveValue(123), &eng})); + QVERIFY(!QJSManagedValue(QJSPrimitiveNull(), &eng).strictlyEquals({QJSPrimitiveValue(123), &eng})); + QVERIFY(!QJSManagedValue(QJSPrimitiveValue(false), &eng).strictlyEquals({QStringLiteral("ciao"), &eng})); + QVERIFY(!QJSManagedValue(QJSPrimitiveUndefined(), &eng).strictlyEquals({QStringLiteral("ciao"), &eng})); + QVERIFY(!QJSManagedValue(QJSPrimitiveNull(), &eng).strictlyEquals({QStringLiteral("ciao"), &eng})); + QVERIFY(eng.toManagedValue(QStringLiteral("ciao")).strictlyEquals(QJSManagedValue(QStringLiteral("ciao"), &eng))); + QVERIFY(QJSManagedValue(QStringLiteral("ciao"), &eng).strictlyEquals(eng.toManagedValue(QStringLiteral("ciao")))); + QVERIFY(!QJSManagedValue(QStringLiteral("ciao"), &eng).strictlyEquals({QJSPrimitiveValue(123), &eng})); + QVERIFY(!QJSManagedValue(QStringLiteral("ciao"), &eng).strictlyEquals(eng.toManagedValue(123))); + QVERIFY(!QJSManagedValue(QJSPrimitiveValue(123), &eng).strictlyEquals({QStringLiteral("ciao"), &eng})); + QVERIFY(!QJSManagedValue(QJSPrimitiveValue(123), &eng).strictlyEquals(eng.toManagedValue(QStringLiteral("ciao")))); + QVERIFY(!eng.toManagedValue(123).strictlyEquals(QJSManagedValue(QStringLiteral("ciao"), &eng))); + + QJSManagedValue obj1(eng.newObject(), &eng); + QJSManagedValue obj2(eng.newObject(), &eng); + QCOMPARE(obj1.strictlyEquals(obj2), false); + QCOMPARE(obj2.strictlyEquals(obj1), false); + QCOMPARE(obj1.strictlyEquals(obj1), true); + QCOMPARE(obj2.strictlyEquals(obj2), true); + QVERIFY(!obj1.strictlyEquals(QJSManagedValue())); + + QJSManagedValue qobj1(eng.newQObject(&temp), &eng); + QJSManagedValue qobj2(eng.newQObject(&temp), &eng); + QVERIFY(qobj1.strictlyEquals(qobj2)); + + { + QJSManagedValue var1(eng.toManagedValue(QVariant(QStringList() << QStringLiteral("a")))); + QJSManagedValue var2(eng.toManagedValue(QVariant(QStringList() << QStringLiteral("a")))); + QVERIFY(var1.isArray()); + QVERIFY(var2.isArray()); + QVERIFY(!var1.strictlyEquals(var2)); + } + { + QJSManagedValue var1(eng.toManagedValue(QVariant(QStringList() << QStringLiteral("a")))); + QJSManagedValue var2(eng.toManagedValue(QVariant(QStringList() << QStringLiteral("b")))); + QVERIFY(!var1.strictlyEquals(var2)); + } + { + QJSManagedValue var1(eng.toManagedValue(QVariant::fromValue(QPoint(1, 2)))); + QJSManagedValue var2(eng.toManagedValue(QVariant::fromValue(QPoint(1, 2)))); + QVERIFY(var1.strictlyEquals(var2)); + } + { + QJSManagedValue var1(eng.toManagedValue(QVariant::fromValue(QPoint(1, 2)))); + QJSManagedValue var2(eng.toManagedValue(QVariant::fromValue(QPoint(3, 4)))); + QVERIFY(!var1.strictlyEquals(var2)); + } + + { + // Import QtQml to trigger the registration of QStringList, which makes it a sequence + // type, rather than a generic JS array. + QQmlEngine qmlEngine; + QQmlComponent c(&qmlEngine); + c.setData("import QtQml\nQtObject {}", QUrl()); + QScopedPointer<QObject> obj(c.create()); + QVERIFY(!obj.isNull()); + + QJSManagedValue var1(qmlEngine.toManagedValue(QVariant(QStringList() << QStringLiteral("a")))); + QJSManagedValue var2(qmlEngine.toManagedValue(QVariant(QStringList() << QStringLiteral("a")))); + QVERIFY(!var1.isArray()); + QVERIFY(!var2.isArray()); + QVERIFY(!var1.strictlyEquals(var2)); + } +} + +void tst_QJSManagedValue::castToPointer() +{ + QJSEngine eng; + { + QRect c(123, 210, 231, 10); + QJSManagedValue v(eng.toManagedValue(&c)); + QRect *cp = qjsvalue_cast<QRect*>(v); + QVERIFY(cp != nullptr); + QCOMPARE(*cp, c); + + QPoint *bp = qjsvalue_cast<QPoint*>(v); + QVERIFY(!bp); + + QJSManagedValue v2(eng.toManagedValue(QVariant::fromValue(cp))); + QCOMPARE(qjsvalue_cast<QRect*>(v2), cp); + } +} + +void tst_QJSManagedValue::engineDeleted() +{ + QJSEngine *eng = new QJSEngine; + QObject *temp = new QObject(); // Owned by JS engine, as newQObject() sets JS ownership explicitly + QJSManagedValue v1(eng->toManagedValue(123)); + QCOMPARE(v1.type(), QJSManagedValue::Number); + QJSManagedValue v2(eng->toManagedValue(QStringLiteral("ciao"))); + QCOMPARE(v2.type(), QJSManagedValue::String); + QJSManagedValue v3(eng->newObject(), eng); + QCOMPARE(v3.type(), QJSManagedValue::Object); + QVERIFY(!v3.isNull()); + QJSManagedValue v4(eng->newQObject(temp), eng); + QCOMPARE(v4.type(), QJSManagedValue::Object); + QVERIFY(!v4.isNull()); + QJSManagedValue v5(QStringLiteral("Hello"), eng); + QCOMPARE(v2.type(), QJSManagedValue::String); + + delete eng; + + QCOMPARE(v1.type(), QJSManagedValue::Undefined); + QCOMPARE(v2.type(), QJSManagedValue::Undefined); + QCOMPARE(v3.type(), QJSManagedValue::Undefined); + QCOMPARE(v4.type(), QJSManagedValue::Undefined); + QCOMPARE(v5.type(), QJSManagedValue::Undefined); + + QVERIFY(v3.property(QStringLiteral("foo")).isUndefined()); +} + +void tst_QJSManagedValue::valueOfWithClosure() +{ + QJSEngine eng; + // valueOf() + { + QJSManagedValue obj(eng.evaluate(QStringLiteral("o = {}; (function(foo) { o.valueOf = function() { return foo; } })(123); o")), &eng); + QCOMPARE(obj.type(), QJSManagedValue::Object); + QVERIFY(!obj.isNull()); + QCOMPARE(obj.toNumber(), 123); + } + // toString() + { + QJSManagedValue obj(eng.evaluate(QStringLiteral("o = {}; (function(foo) { o.toString = function() { return foo; } })('ciao'); o")), &eng); + QCOMPARE(obj.type(), QJSManagedValue::Object); + QVERIFY(!obj.isNull()); + QCOMPARE(obj.toString(), QStringLiteral("ciao")); + } +} + +static int instanceCount = 0; + +struct MyType +{ + MyType(int n = 0, const char *t=nullptr): number(n), text(t) + { + ++instanceCount; + } + MyType(const MyType &other) + : number(other.number), text(other.text) + { + ++instanceCount; + } + ~MyType() + { + --instanceCount; + } + int number; + const char *text; +}; + +Q_DECLARE_METATYPE(MyType) +Q_DECLARE_METATYPE(MyType*) + +void tst_QJSManagedValue::jsvalueArrayToSequenceType() +{ + QCOMPARE(instanceCount, 0); + { + QJSEngine eng {}; + QJSManagedValue testObject(eng.newObject(), &eng); + testObject.setProperty(QStringLiteral("test"), 42); + testObject.setProperty(QStringLiteral("mytypeobject"), eng.toScriptValue(QVariant::fromValue(MyType {42, "hello"}))); + auto array = eng.newArray(4); + array.setProperty(0, QStringLiteral("Hello World")); + array.setProperty(1, 42); + array.setProperty(2, QJSValue()); + array.setProperty(3, QJSValue(std::move(testObject))); + auto asVariant = QVariant::fromValue(array); + QVERIFY(asVariant.canConvert<QVariantList>()); + auto asIterable = asVariant.value<QSequentialIterable>(); + for (auto it = asIterable.begin(); it != asIterable.end(); ++it) { + Q_UNUSED(*it); + } + int i = 0; + for (QVariant myVariant: asIterable) { + QCOMPARE(myVariant.isValid(), i != 2); + ++i; + } + QVERIFY(asIterable.at(2).value<QVariant>().isNull()); + QCOMPARE(asIterable.at(3).value<QVariantMap>().find(QStringLiteral("mytypeobject"))->value<MyType>().number, 42); + QCOMPARE(asIterable.at(0).value<QVariant>().toString(), QStringLiteral("Hello World")); + auto it1 = asIterable.begin(); + auto it2 = asIterable.begin(); + QCOMPARE((*it1).value<QVariant>().toString(), (*it2).value<QVariant>().toString()); + QCOMPARE((*it1).value<QVariant>().toString(), QStringLiteral("Hello World")); + ++it2; + QCOMPARE((*it1).value<QVariant>().toString(), QStringLiteral("Hello World")); + QCOMPARE((*it2).value<QVariant>().toInt(), 42); + } + // tests need to be done after engine has been destroyed, else it will hold a reference until + // the gc decides to collect it + QCOMPARE(instanceCount, 0); +} + +static QLoggingCategory::CategoryFilter oldFilter; +void logFilter(QLoggingCategory *category) +{ + if (qstrcmp(category->categoryName(), "qt.qml.managedvalue") == 0) + category->setEnabled(QtDebugMsg, true); + else if (oldFilter) + oldFilter(category); +} + +void tst_QJSManagedValue::stringAndUrl() +{ + QJSEngine engine; + const QString string = QStringLiteral("http://example.com/something.html"); + const QUrl url(string); + + const QJSManagedValue urlValue(engine.toManagedValue(url)); + QCOMPARE(urlValue.toString(), string); + QCOMPARE(engine.fromManagedValue<QUrl>(urlValue), url); + + const QJSManagedValue stringValue(engine.toManagedValue(string)); + QCOMPARE(stringValue.toString(), string); + QCOMPARE(engine.fromManagedValue<QUrl>(stringValue), url); + + const QJSManagedValue immediateStringValue(string, &engine); + QCOMPARE(immediateStringValue.toString(), string); + QCOMPARE(engine.fromManagedValue<QUrl>(immediateStringValue), url); +} + +void tst_QJSManagedValue::jsFunctionInVariant() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::ConsoleExtension); + QJSManagedValue console(engine.globalObject().property(QStringLiteral("console")), &engine); + QCOMPARE(console.type(), QJSManagedValue::Object); + QVERIFY(!console.isNull()); + QJSManagedValue log(console.property(QStringLiteral("log")), &engine); + QVERIFY(log.isCallable()); + + { + QTest::ignoreMessage(QtDebugMsg, "direct call"); + log.callWithInstance(QJSValue(std::move(console)), {"direct call"}); + } +} + +QTEST_MAIN(tst_QJSManagedValue) diff --git a/tests/auto/qml/qjsmanagedvalue/tst_qjsmanagedvalue.h b/tests/auto/qml/qjsmanagedvalue/tst_qjsmanagedvalue.h new file mode 100644 index 0000000000..da2a8f76a7 --- /dev/null +++ b/tests/auto/qml/qjsmanagedvalue/tst_qjsmanagedvalue.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TST_QJSMANAGEDVALUE_H +#define TST_QJSMANAGEDVALUE_H + +#include <QtCore/qobject.h> +#include <QtQml/qjsengine.h> + +class tst_QJSManagedValue : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void ctor_invalid(); + void ctor_undefinedWithEngine(); + void ctor_nullWithEngine(); + void ctor_boolWithEngine(); + void ctor_intWithEngine(); + void ctor_uintWithEngine(); + void ctor_floatWithEngine(); + void ctor_stringWithEngine(); + void ctor_copyAndAssignWithEngine(); + + void toString(); + void toNumber(); + void toBoolean(); + void toVariant(); + + void equals(); + void strictlyEquals(); + + void hasProperty_basic(); + void hasProperty_globalObject(); + void hasProperty_changePrototype(); + void hasProperty_QTBUG56830_data(); + void hasProperty_QTBUG56830(); + + void deleteProperty_basic(); + void deleteProperty_globalObject(); + void deleteProperty_inPrototype(); + + void getSetPrototype_cyclicPrototype(); + void getSetPrototype_evalCyclicPrototype(); + void getSetPrototype_eval(); + void getSetPrototype_invalidPrototype(); + void getSetPrototype_twoEngines(); + void getSetPrototype_null(); + void getSetPrototype_notObjectOrNull(); + void getSetPrototype(); + void getSetProperty_propertyRemoval(); + void getSetProperty_resolveMode(); + void getSetProperty_twoEngines(); + void getSetProperty_gettersAndSettersThrowErrorJS(); + void getSetProperty_array(); + void getSetProperty(); + + void call_function(); + void call_object(); + void call_newObjects(); + void call_this(); + void call_arguments(); + void call(); + void call_twoEngines(); + void call_nonFunction_data(); + void call_nonFunction(); + void construct_nonFunction_data(); + void construct_nonFunction(); + void construct_simple(); + void construct_newObjectJS(); + void construct_arg(); + void construct_proto(); + void construct_returnInt(); + void construct_throw(); + void construct_twoEngines(); + void construct_constructorThrowsPrimitive(); + void castToPointer(); + void engineDeleted(); + void valueOfWithClosure(); + + void jsvalueArrayToSequenceType(); + + void stringAndUrl(); + void jsFunctionInVariant(); + +private: + void newEngine() { engine.reset(new QJSEngine()); } + QScopedPointer<QJSEngine> engine; +}; + +#endif // TST_QJSMANAGEDVALUE diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro index abbc786ee9..56a3309701 100644 --- a/tests/auto/qml/qml.pro +++ b/tests/auto/qml/qml.pro @@ -82,6 +82,7 @@ qtHaveModule(widgets) { SUBDIRS += \ qjsengine \ qjsvalue \ + qjsmanagedvalue \ # qwidgetsinqml } |