aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2020-12-11 13:35:53 +0100
committerUlf Hermann <ulf.hermann@qt.io>2020-12-18 17:26:51 +0100
commit6f181768a3147bbfa9a33cf2c05453365693f5b9 (patch)
tree3ff80238b5784032c86cfc1a70088e17b62a7127
parentd5ac54da624dbaebc865c8243a5e1c33d5e1c7ba (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.txt1
-rw-r--r--src/qml/CMakeLists.txt1
-rw-r--r--src/qml/jsapi/jsapi.pri2
-rw-r--r--src/qml/jsapi/qjsengine.cpp12
-rw-r--r--src/qml/jsapi/qjsengine.h38
-rw-r--r--src/qml/jsapi/qjsmanagedvalue.cpp1028
-rw-r--r--src/qml/jsapi/qjsmanagedvalue.h158
-rw-r--r--src/qml/jsapi/qjsprimitivevalue.h1
-rw-r--r--src/qml/jsapi/qjsvalue.cpp15
-rw-r--r--src/qml/jsapi/qjsvalue.h4
-rw-r--r--src/qml/jsapi/qjsvalue_p.h29
-rw-r--r--src/qml/jsruntime/qv4value.cpp147
-rw-r--r--src/qml/jsruntime/qv4value_p.h3
-rw-r--r--tests/auto/qml/.prev_CMakeLists.txt1
-rw-r--r--tests/auto/qml/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qjsmanagedvalue/.prev_CMakeLists.txt19
-rw-r--r--tests/auto/qml/qjsmanagedvalue/CMakeLists.txt16
-rw-r--r--tests/auto/qml/qjsmanagedvalue/qjsmanagedvalue.pro6
-rw-r--r--tests/auto/qml/qjsmanagedvalue/tst_qjsmanagedvalue.cpp1746
-rw-r--r--tests/auto/qml/qjsmanagedvalue/tst_qjsmanagedvalue.h116
-rw-r--r--tests/auto/qml/qml.pro1
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
}