diff options
Diffstat (limited to 'src/qml/jsapi/qjsengine.cpp')
-rw-r--r-- | src/qml/jsapi/qjsengine.cpp | 765 |
1 files changed, 527 insertions, 238 deletions
diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index 065fbc1c0a..8346ef5d84 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qjsengine.h" #include "qjsengine_p.h" @@ -48,11 +12,14 @@ #include "private/qv4globalobject_p.h" #include "private/qv4script_p.h" #include "private/qv4runtime_p.h" +#include <private/qv4dateobject_p.h> #include <private/qqmlbuiltinfunctions_p.h> #include <private/qqmldebugconnector_p.h> #include <private/qv4qobjectwrapper_p.h> +#include <private/qv4qmetaobjectwrapper_p.h> #include <private/qv4stackframe_p.h> #include <private/qv4module_p.h> +#include <private/qv4symbol_p.h> #include <QtCore/qdatetime.h> #include <QtCore/qmetaobject.h> @@ -71,11 +38,6 @@ #include <private/qqmlglobal_p.h> #include <qqmlengine.h> -#undef Q_D -#undef Q_Q -#define Q_D(blah) -#define Q_Q(blah) - Q_DECLARE_METATYPE(QList<int>) /*! @@ -154,6 +116,46 @@ Q_DECLARE_METATYPE(QList<int>) } \endcode + Modules don't have to be files. They can be values registered with + QJSEngine::registerModule(): + + \code + import version from "version"; + + export function getVersion() + { + return version; + } + \endcode + + \code + QJSValue version(610); + myEngine.registerModule("version", version); + QJSValue module = myEngine.importModule("./myprint.mjs"); + QJSValue getVersion = module.property("getVersion"); + QJSValue result = getVersion.call(); + \endcode + + Named exports are supported, but because they are treated as members of an + object, the default export must be an ECMAScript object. Most of the newXYZ + functions in QJSValue will return an object. + + \code + QJSValue name("Qt6"); + QJSValue obj = myEngine.newObject(); + obj.setProperty("name", name); + myEngine.registerModule("info", obj); + \endcode + + \code + import { name } from "info"; + + export function getName() + { + return name; + } + \endcode + \section1 Engine Configuration The globalObject() function returns the \b {Global Object} @@ -242,7 +244,7 @@ Q_DECLARE_METATYPE(QList<int>) \section1 Extensions QJSEngine provides a compliant ECMAScript implementation. By default, - familiar utilities like logging are not available, but they can can be + familiar utilities like logging are not available, but they can be installed via the \l installExtensions() function. \sa QJSValue, {Making Applications Scriptable}, @@ -257,7 +259,7 @@ Q_DECLARE_METATYPE(QList<int>) \l installExtensions(). \value TranslationExtension Indicates that translation functions (\c qsTr(), - for example) should be installed. + for example) should be installed. This also installs the Qt.uiLanguage property. \value ConsoleExtension Indicates that console functions (\c console.log(), for example) should be installed. @@ -344,11 +346,8 @@ QJSEngine::QJSEngine() */ QJSEngine::QJSEngine(QObject *parent) - : QObject(*new QJSEnginePrivate, parent) - , m_v4Engine(new QV4::ExecutionEngine(this)) + : QJSEngine(*new QJSEnginePrivate, parent) { - checkForApplicationInstance(); - QJSEnginePrivate::addToDebugServer(this); } @@ -366,11 +365,12 @@ QJSEngine::QJSEngine(QJSEnginePrivate &dd, QObject *parent) Destroys this QJSEngine. Garbage is not collected from the persistent JS heap during QJSEngine - destruction. If you need all memory freed, call collectGarbage manually + destruction. If you need all memory freed, call collectGarbage() manually right before destroying the QJSEngine. */ QJSEngine::~QJSEngine() { + m_v4Engine->inShutdown = true; QJSEnginePrivate::removeFromDebugServer(this); delete m_v4Engine; } @@ -390,46 +390,16 @@ QJSEngine::~QJSEngine() when the QJSEngine decides that it's wise to do so (i.e. when a certain number of new objects have been created). However, you can call this function to explicitly request that garbage collection should be performed as soon as possible. -*/ -void QJSEngine::collectGarbage() -{ - m_v4Engine->memoryManager->runGC(); -} - -#if QT_DEPRECATED_SINCE(5, 6) - -/*! - \since 5.4 - \obsolete - - Installs translator functions on the given \a object, or on the Global - Object if no object is specified. - - The relation between script translator functions and C++ translator - functions is described in the following table: - - \table - \header \li Script Function \li Corresponding C++ Function - \row \li qsTr() \li QObject::tr() - \row \li QT_TR_NOOP() \li QT_TR_NOOP() - \row \li qsTranslate() \li QCoreApplication::translate() - \row \li QT_TRANSLATE_NOOP() \li QT_TRANSLATE_NOOP() - \row \li qsTrId() \li qtTrId() - \row \li QT_TRID_NOOP() \li QT_TRID_NOOP() - \endtable - It also adds an arg() method to the string prototype. - \sa {Internationalization with Qt} -*/ -void QJSEngine::installTranslatorFunctions(const QJSValue &object) + \sa {Garbage Collection} + \sa {Qt::}{gc()} + */ +void QJSEngine::collectGarbage() { - installExtensions(TranslationExtension, object); + m_v4Engine->memoryManager->runGC(); } -#endif // QT_DEPRECATED_SINCE(5, 6) - - /*! \since 5.6 @@ -456,10 +426,7 @@ void QJSEngine::installExtensions(QJSEngine::Extensions extensions, const QJSVal } QV4::Scope scope(m_v4Engine); - QV4::ScopedObject obj(scope); - QV4::Value *val = QJSValuePrivate::getValue(&object); - if (val) - obj = val; + QV4::ScopedObject obj(scope, QJSValuePrivate::asReturnedValue(&object)); if (!obj) obj = scope.engine->globalObject; @@ -479,7 +446,7 @@ void QJSEngine::installExtensions(QJSEngine::Extensions extensions, const QJSVal */ void QJSEngine::setInterrupted(bool interrupted) { - m_v4Engine->isInterrupted = interrupted; + m_v4Engine->isInterrupted.storeRelaxed(interrupted); } /*! @@ -490,7 +457,7 @@ void QJSEngine::setInterrupted(bool interrupted) */ bool QJSEngine::isInterrupted() const { - return m_v4Engine->isInterrupted.loadAcquire(); + return m_v4Engine->isInterrupted.loadRelaxed(); } static QUrl urlForFileName(const QString &fileName) @@ -510,6 +477,9 @@ static QUrl urlForFileName(const QString &fileName) The script code will be evaluated in the context of the global object. + \note If you need to evaluate inside a QML context, use \l QQmlExpression + instead. + The evaluation of \a program can cause an \l{Script Exceptions}{exception} in the engine; in this case the return value will be the exception that was thrown (typically an \c{Error} object; see @@ -527,12 +497,23 @@ static QUrl urlForFileName(const QString &fileName) the file name is accessible through the "fileName" property if it is provided with this function. + \a exceptionStackTrace is used to report whether an uncaught exception was + thrown. If you pass a non-null pointer to a QStringList to it, it will set + it to list of "stackframe messages" if the script threw an unhandled + exception, or an empty list otherwise. A stackframe message has the format + function name:line number:column:file name + \note In some cases, e.g. for native functions, function name and file name + can be empty and line number and column can be -1. + \note If an exception was thrown and the exception value is not an Error instance (i.e., QJSValue::isError() returns \c false), the - exception value will still be returned, but there is currently no - API for detecting that an exception did occur in this case. + exception value will still be returned. Use \c exceptionStackTrace->isEmpty() + to distinguish whether the value was a normal or an exceptional return + value. + + \sa QQmlExpression::evaluate */ -QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) +QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, int lineNumber, QStringList *exceptionStackTrace) { QV4::ExecutionEngine *v4 = m_v4Engine; QV4::Scope scope(v4); @@ -546,16 +527,27 @@ QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, in script.strictMode = v4->globalCode->isStrict(); script.inheritContext = true; script.parse(); - if (!scope.engine->hasException) + if (!scope.hasException()) result = script.run(); - if (scope.engine->hasException) - result = v4->catchException(); - if (v4->isInterrupted.loadAcquire()) + if (exceptionStackTrace) + exceptionStackTrace->clear(); + if (scope.hasException()) { + QV4::StackTrace trace; + result = v4->catchException(&trace); + if (exceptionStackTrace) { + for (auto &&frame: trace) + exceptionStackTrace->push_back(QLatin1StringView("%1:%2:%3:%4").arg( + frame.function, + QString::number(qAbs(frame.line)), + QString::number(frame.column), + frame.source) + ); + } + } + if (v4->isInterrupted.loadRelaxed()) result = v4->newErrorObject(QStringLiteral("Interrupted")); - QJSValue retval(v4, result->asReturnedValue()); - - return retval; + return QJSValuePrivate::fromReturnedValue(result->asReturnedValue()); } /*! @@ -576,26 +568,68 @@ QJSValue QJSEngine::evaluate(const QString& program, const QString& fileName, in \note If an exception is thrown during the loading of the module, the return value will be the exception (typically an \c{Error} object; see QJSValue::isError()). + \sa registerModule() + \since 5.12 */ QJSValue QJSEngine::importModule(const QString &fileName) { const QUrl url = urlForFileName(QFileInfo(fileName).canonicalFilePath()); - auto moduleUnit = m_v4Engine->loadModule(url); + const auto module = m_v4Engine->loadModule(url); if (m_v4Engine->hasException) - return QJSValue(m_v4Engine, m_v4Engine->catchException()); + return QJSValuePrivate::fromReturnedValue(m_v4Engine->catchException()); + + QV4::Scope scope(m_v4Engine); + if (const auto compiled = module.compiled) { + QV4::Scoped<QV4::Module> moduleNamespace(scope, compiled->instantiate()); + if (m_v4Engine->hasException) + return QJSValuePrivate::fromReturnedValue(m_v4Engine->catchException()); + compiled->evaluate(); + if (!m_v4Engine->isInterrupted.loadRelaxed()) + return QJSValuePrivate::fromReturnedValue(moduleNamespace->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue( + m_v4Engine->newErrorObject(QStringLiteral("Interrupted"))->asReturnedValue()); + } + + // If there is neither a native nor a compiled module, we should have seen an exception + Q_ASSERT(module.native); + + return QJSValuePrivate::fromReturnedValue(module.native->asReturnedValue()); +} + +/*! + Registers a QJSValue to serve as a module. After this function is called, + all modules that import \a moduleName will import the value of \a value + instead of loading \a moduleName from the filesystem. + Any valid QJSValue can be registered, but named exports (i.e. + \c {import { name } from "info"} are treated as members of an object, so + the default export must be created with one of the newXYZ methods of + QJSEngine. + + Because this allows modules that do not exist on the filesystem to be imported, + scripting applications can use this to provide built-in modules, similar to + Node.js. + + Returns \c true on success, \c false otherwise. + + \note The QJSValue \a value is not called or read until it is used by another module. + This means that there is no code to evaluate, so no errors will be seen until + another module throws an exception while trying to load this module. + + \warning Attempting to access a named export from a QJSValue that is not an + object will trigger a \l{Script Exceptions}{exception}. + + \sa importModule() + */ +bool QJSEngine::registerModule(const QString &moduleName, const QJSValue &value) +{ QV4::Scope scope(m_v4Engine); - QV4::Scoped<QV4::Module> moduleNamespace(scope, moduleUnit->instantiate(m_v4Engine)); + QV4::ScopedValue v4Value(scope, QJSValuePrivate::asReturnedValue(&value)); + m_v4Engine->registerNativeModule(QUrl(moduleName), v4Value); if (m_v4Engine->hasException) - return QJSValue(m_v4Engine, m_v4Engine->catchException()); - moduleUnit->evaluate(); - if (!m_v4Engine->isInterrupted.loadAcquire()) - return QJSValue(m_v4Engine, moduleNamespace->asReturnedValue()); - - return QJSValue( - m_v4Engine, - m_v4Engine->newErrorObject(QStringLiteral("Interrupted"))->asReturnedValue()); + return false; + return true; } /*! @@ -610,7 +644,23 @@ QJSValue QJSEngine::newObject() { QV4::Scope scope(m_v4Engine); QV4::ScopedValue v(scope, m_v4Engine->newObject()); - return QJSValue(m_v4Engine, v->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); +} + +/*! + \since 6.2 + + Creates a JavaScript object of class Symbol, with value \a name. + + The prototype of the created object will be the Symbol prototype object. + + \sa newObject() +*/ +QJSValue QJSEngine::newSymbol(const QString &name) +{ + QV4::Scope scope(m_v4Engine); + QV4::ScopedValue v(scope, QV4::Symbol::create(m_v4Engine, u'@' + name)); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); } /*! @@ -652,7 +702,7 @@ QJSValue QJSEngine::newErrorObject(QJSValue::ErrorType errorType, const QString case QJSValue::NoError: return QJSValue::UndefinedValue; } - return QJSValue(m_v4Engine, error->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(error->asReturnedValue()); } /*! @@ -667,7 +717,7 @@ QJSValue QJSEngine::newArray(uint length) if (length < 0x1000) array->arrayReserve(length); array->setArrayLengthUnchecked(length); - return QJSValue(m_v4Engine, array.asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(array.asReturnedValue()); } /*! @@ -692,7 +742,6 @@ QJSValue QJSEngine::newArray(uint length) */ QJSValue QJSEngine::newQObject(QObject *object) { - Q_D(QJSEngine); QV4::ExecutionEngine *v4 = m_v4Engine; QV4::Scope scope(v4); if (object) { @@ -701,7 +750,7 @@ QJSValue QJSEngine::newQObject(QObject *object) QQmlEngine::setObjectOwnership(object, QQmlEngine::JavaScriptOwnership); } QV4::ScopedValue v(scope, QV4::QObjectWrapper::wrap(v4, object)); - return QJSValue(v4, v->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); } /*! @@ -719,11 +768,10 @@ QJSValue QJSEngine::newQObject(QObject *object) */ QJSValue QJSEngine::newQMetaObject(const QMetaObject* metaObject) { - Q_D(QJSEngine); QV4::ExecutionEngine *v4 = m_v4Engine; QV4::Scope scope(v4); QV4::ScopedValue v(scope, QV4::QMetaObjectWrapper::create(v4, metaObject)); - return QJSValue(v4, v->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); } /*! \fn template <typename T> QJSValue QJSEngine::newQMetaObject() @@ -750,152 +798,252 @@ QJSValue QJSEngine::globalObject() const { QV4::Scope scope(m_v4Engine); QV4::ScopedValue v(scope, m_v4Engine->globalObject); - return QJSValue(m_v4Engine, v->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); +} + +QJSPrimitiveValue QJSEngine::createPrimitive(QMetaType type, const void *ptr) +{ + QV4::Scope scope(m_v4Engine); + QV4::ScopedValue v(scope, m_v4Engine->metaTypeToJS(type, ptr)); + return QV4::ExecutionEngine::createPrimitive(v); +} + +QJSManagedValue QJSEngine::createManaged(QMetaType type, const void *ptr) +{ + QJSManagedValue result(m_v4Engine); + *result.d = m_v4Engine->metaTypeToJS(type, ptr); + return result; } /*! * \internal * used by QJSEngine::toScriptValue */ -QJSValue QJSEngine::create(int type, const void *ptr) +QJSValue QJSEngine::create(QMetaType type, const void *ptr) { QV4::Scope scope(m_v4Engine); QV4::ScopedValue v(scope, scope.engine->metaTypeToJS(type, ptr)); - return QJSValue(m_v4Engine, v->asReturnedValue()); + return QJSValuePrivate::fromReturnedValue(v->asReturnedValue()); +} + +bool QJSEngine::convertPrimitive(const QJSPrimitiveValue &value, QMetaType type, void *ptr) +{ + switch (value.type()) { + case QJSPrimitiveValue::Undefined: + return QV4::ExecutionEngine::metaTypeFromJS(QV4::Value::undefinedValue(), type, ptr); + case QJSPrimitiveValue::Null: + return QV4::ExecutionEngine::metaTypeFromJS(QV4::Value::nullValue(), type, ptr); + case QJSPrimitiveValue::Boolean: + return QV4::ExecutionEngine::metaTypeFromJS(QV4::Value::fromBoolean(value.toBoolean()), type, ptr); + case QJSPrimitiveValue::Integer: + return QV4::ExecutionEngine::metaTypeFromJS(QV4::Value::fromInt32(value.toInteger()), type, ptr); + case QJSPrimitiveValue::Double: + return QV4::ExecutionEngine::metaTypeFromJS(QV4::Value::fromDouble(value.toDouble()), type, ptr); + case QJSPrimitiveValue::String: + return convertString(value.toString(), type, ptr); + } + + Q_UNREACHABLE_RETURN(false); +} + +bool QJSEngine::convertManaged(const QJSManagedValue &value, int type, void *ptr) +{ + return convertManaged(value, QMetaType(type), ptr); +} + +bool QJSEngine::convertManaged(const QJSManagedValue &value, QMetaType type, void *ptr) +{ + return QV4::ExecutionEngine::metaTypeFromJS(*value.d, type, ptr); +} + +bool QJSEngine::convertString(const QString &string, QMetaType metaType, void *ptr) +{ + // have a string based value without engine. Do conversion manually + if (metaType == QMetaType::fromType<bool>()) { + *reinterpret_cast<bool*>(ptr) = string.size() != 0; + return true; + } + if (metaType == QMetaType::fromType<QString>()) { + *reinterpret_cast<QString*>(ptr) = string; + return true; + } + if (metaType == QMetaType::fromType<QUrl>()) { + *reinterpret_cast<QUrl *>(ptr) = QUrl(string); + return true; + } + + double d = QV4::RuntimeHelpers::stringToNumber(string); + switch (metaType.id()) { + case QMetaType::Int: + *reinterpret_cast<int*>(ptr) = QV4::Value::toInt32(d); + return true; + case QMetaType::UInt: + *reinterpret_cast<uint*>(ptr) = QV4::Value::toUInt32(d); + return true; + case QMetaType::Long: + *reinterpret_cast<long*>(ptr) = QV4::Value::toInteger(d); + return true; + case QMetaType::ULong: + *reinterpret_cast<ulong*>(ptr) = QV4::Value::toInteger(d); + return true; + case QMetaType::LongLong: + *reinterpret_cast<qlonglong*>(ptr) = QV4::Value::toInteger(d); + return true; + case QMetaType::ULongLong: + *reinterpret_cast<qulonglong*>(ptr) = QV4::Value::toInteger(d); + return true; + case QMetaType::Double: + *reinterpret_cast<double*>(ptr) = d; + return true; + case QMetaType::Float: + *reinterpret_cast<float*>(ptr) = d; + return true; + case QMetaType::Short: + *reinterpret_cast<short*>(ptr) = QV4::Value::toInt32(d); + return true; + case QMetaType::UShort: + *reinterpret_cast<unsigned short*>(ptr) = QV4::Value::toUInt32(d); + return true; + case QMetaType::Char: + *reinterpret_cast<char*>(ptr) = QV4::Value::toInt32(d); + return true; + case QMetaType::UChar: + *reinterpret_cast<unsigned char*>(ptr) = QV4::Value::toUInt32(d); + return true; + case QMetaType::QChar: + *reinterpret_cast<QChar*>(ptr) = QChar(QV4::Value::toUInt32(d)); + return true; + case QMetaType::Char16: + *reinterpret_cast<char16_t *>(ptr) = QV4::Value::toUInt32(d); + return true; + default: + return false; + } } /*! \internal convert \a value to \a type, store the result in \a ptr */ -bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr) +bool QJSEngine::convertV2(const QJSValue &value, QMetaType metaType, void *ptr) { - QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(&value); - QV4::Value scratch; - QV4::Value *val = QJSValuePrivate::valueForData(&value, &scratch); - if (v4) { - QV4::Scope scope(v4); - QV4::ScopedValue v(scope, *val); - return scope.engine->metaTypeFromJS(v, type, ptr); - } + if (const QString *string = QJSValuePrivate::asQString(&value)) + return convertString(*string, metaType, ptr); - if (!val) { - QVariant *variant = QJSValuePrivate::getVariant(&value); - Q_ASSERT(variant); - - if (variant->userType() == QMetaType::QString) { - QString string = variant->toString(); - // have a string based value without engine. Do conversion manually - if (type == QMetaType::Bool) { - *reinterpret_cast<bool*>(ptr) = string.length() != 0; - return true; - } - if (type == QMetaType::QString) { - *reinterpret_cast<QString*>(ptr) = string; - return true; - } - double d = QV4::RuntimeHelpers::stringToNumber(string); - switch (type) { - case QMetaType::Int: - *reinterpret_cast<int*>(ptr) = QV4::Value::toInt32(d); - return true; - case QMetaType::UInt: - *reinterpret_cast<uint*>(ptr) = QV4::Value::toUInt32(d); - return true; - case QMetaType::LongLong: - *reinterpret_cast<qlonglong*>(ptr) = QV4::Value::toInteger(d); - return true; - case QMetaType::ULongLong: - *reinterpret_cast<qulonglong*>(ptr) = QV4::Value::toInteger(d); - return true; - case QMetaType::Double: - *reinterpret_cast<double*>(ptr) = d; - return true; - case QMetaType::Float: - *reinterpret_cast<float*>(ptr) = d; - return true; - case QMetaType::Short: - *reinterpret_cast<short*>(ptr) = QV4::Value::toInt32(d); - return true; - case QMetaType::UShort: - *reinterpret_cast<unsigned short*>(ptr) = QV4::Value::toUInt32(d); - return true; - case QMetaType::Char: - *reinterpret_cast<char*>(ptr) = QV4::Value::toInt32(d); - return true; - case QMetaType::UChar: - *reinterpret_cast<unsigned char*>(ptr) = QV4::Value::toUInt32(d); - return true; - case QMetaType::QChar: - *reinterpret_cast<QChar*>(ptr) = QV4::Value::toUInt32(d); - return true; - default: - return false; - } - } else { - return QMetaType::convert(&variant->data_ptr(), variant->userType(), ptr, type); - } - } + // Does not need scoping since QJSValue still holds on to the value. + return QV4::ExecutionEngine::metaTypeFromJS(QJSValuePrivate::asReturnedValue(&value), metaType, ptr); +} - Q_ASSERT(val); - - switch (type) { - case QMetaType::Bool: - *reinterpret_cast<bool*>(ptr) = val->toBoolean(); - return true; - case QMetaType::Int: - *reinterpret_cast<int*>(ptr) = val->toInt32(); - return true; - case QMetaType::UInt: - *reinterpret_cast<uint*>(ptr) = val->toUInt32(); - return true; - case QMetaType::LongLong: - *reinterpret_cast<qlonglong*>(ptr) = val->toInteger(); - return true; - case QMetaType::ULongLong: - *reinterpret_cast<qulonglong*>(ptr) = val->toInteger(); - return true; - case QMetaType::Double: - *reinterpret_cast<double*>(ptr) = val->toNumber(); - return true; - case QMetaType::QString: - *reinterpret_cast<QString*>(ptr) = val->toQStringNoThrow(); - return true; - case QMetaType::Float: - *reinterpret_cast<float*>(ptr) = val->toNumber(); - return true; - case QMetaType::Short: - *reinterpret_cast<short*>(ptr) = val->toInt32(); - return true; - case QMetaType::UShort: - *reinterpret_cast<unsigned short*>(ptr) = val->toUInt16(); - return true; - case QMetaType::Char: - *reinterpret_cast<char*>(ptr) = val->toInt32(); - return true; - case QMetaType::UChar: - *reinterpret_cast<unsigned char*>(ptr) = val->toUInt16(); - return true; - case QMetaType::QChar: - *reinterpret_cast<QChar*>(ptr) = val->toUInt16(); - return true; - default: - return false; - } +bool QJSEngine::convertVariant(const QVariant &value, QMetaType metaType, void *ptr) +{ + // TODO: We could probably avoid creating a QV4::Value in many cases, but we'd have to + // duplicate much of metaTypeFromJS and some methods of QV4::Value itself here. + QV4::Scope scope(handle()); + QV4::ScopedValue scoped(scope, scope.engine->fromVariant(value)); + return QV4::ExecutionEngine::metaTypeFromJS(scoped, metaType, ptr); +} + +bool QJSEngine::convertMetaType(QMetaType fromType, const void *from, QMetaType toType, void *to) +{ + // TODO: We could probably avoid creating a QV4::Value in many cases, but we'd have to + // duplicate much of metaTypeFromJS and some methods of QV4::Value itself here. + QV4::Scope scope(handle()); + QV4::ScopedValue scoped(scope, scope.engine->fromData(fromType, from)); + return QV4::ExecutionEngine::metaTypeFromJS(scoped, toType, to); +} + +QString QJSEngine::convertQObjectToString(QObject *object) +{ + return QV4::QObjectWrapper::objectToString( + handle(), object ? object->metaObject() : nullptr, object); +} + +QString QJSEngine::convertDateTimeToString(const QDateTime &dateTime) +{ + return QV4::DateObject::dateTimeToString(dateTime, handle()); +} + +double QJSEngine::convertDateTimeToNumber(const QDateTime &dateTime) +{ + return QV4::DateObject::dateTimeToNumber(dateTime); +} + +QDate QJSEngine::convertDateTimeToDate(const QDateTime &dateTime) +{ + return QV4::DateObject::dateTimeToDate(dateTime); } /*! \fn template <typename T> QJSValue QJSEngine::toScriptValue(const T &value) Creates a QJSValue with the given \a value. - \sa fromScriptValue() + \sa fromScriptValue(), coerceValue() +*/ + +/*! \fn template <typename T> QJSManagedValue QJSEngine::toManagedValue(const T &value) + + Creates a QJSManagedValue with the given \a value. + + \sa fromManagedValue(), coerceValue() +*/ + +/*! \fn template <typename T> QJSPrimitiveValue QJSEngine::toPrimitiveValue(const T &value) + + Creates a QJSPrimitiveValue with the given \a value. + + Since QJSPrimitiveValue can only hold int, bool, double, QString, and the + equivalents of JavaScript \c null and \c undefined, the value will be + coerced aggressively if you pass any other type. + + \sa fromPrimitiveValue(), coerceValue() */ /*! \fn template <typename T> T QJSEngine::fromScriptValue(const QJSValue &value) Returns the given \a value converted to the template type \c{T}. - \sa toScriptValue() + \sa toScriptValue(), coerceValue() +*/ + +/*! \fn template <typename T> T QJSEngine::fromManagedValue(const QJSManagedValue &value) + + Returns the given \a value converted to the template type \c{T}. + + \sa toManagedValue(), coerceValue() +*/ + +/*! \fn template <typename T> T QJSEngine::fromPrimitiveValue(const QJSPrimitiveValue &value) + + Returns the given \a value converted to the template type \c{T}. + + Since QJSPrimitiveValue can only hold int, bool, double, QString, and the + equivalents of JavaScript \c null and \c undefined, the value will be + coerced aggressively if you request any other type. + + \sa toPrimitiveValue(), coerceValue() +*/ + +/*! \fn template <typename T> T QJSEngine::fromVariant(const QVariant &value) + + Returns the given \a value converted to the template type \c{T}. + The conversion is done in JavaScript semantics. Those differ from + qvariant_cast's semantics. There are a number of implicit + conversions between JavaScript-equivalent types that are not + performed by qvariant_cast by default. + + \sa coerceValue(), fromScriptValue(), {QVariant::}{qvariant_cast()} +*/ + +/*! \fn template <typename From, typename To> T QJSEngine::coerceValue(const From &from) + + Returns the given \a from converted to the template type \c{To}. + The conversion is done in JavaScript semantics. Those differ from + qvariant_cast's semantics. There are a number of implicit + conversions between JavaScript-equivalent types that are not + performed by qvariant_cast by default. This method is a generalization of + all the other conversion methods in this class. + + \sa fromVariant(), {QVariant::}{qvariant_cast()}, fromScriptValue(), toScriptValue() */ /*! @@ -907,7 +1055,7 @@ bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr) JavaScript function through QJSEngine. When returning from C++, the engine will interrupt the normal flow of - execution and call the the next pre-registered exception handler with + execution and call the next pre-registered exception handler with an error object that contains the given \a message. The error object will point to the location of the top-most context on the JavaScript caller stack; specifically, it will have properties \c lineNumber, @@ -998,12 +1146,78 @@ void QJSEngine::throwError(QJSValue::ErrorType errorType, const QString &message { QV4::Scope scope(m_v4Engine); QJSValue error = newErrorObject(errorType, message); - QV4::ScopedObject e(scope, QJSValuePrivate::getValue(&error)); + QV4::ScopedObject e(scope, QJSValuePrivate::asReturnedValue(&error)); if (!e) return; m_v4Engine->throwError(e); } +/*! + \overload throwError() + + Throws a pre-constructed run-time \a error (exception). This way you can + use \l newErrorObject() to create the error and customize it as necessary. + + \since 6.1 + \sa {Script Exceptions}, newErrorObject() +*/ +void QJSEngine::throwError(const QJSValue &error) +{ + m_v4Engine->throwError(QJSValuePrivate::asReturnedValue(&error)); +} + +/*! + * Returns \c true if the last JavaScript execution resulted in an exception or + * if throwError() was called. Otherwise returns \c false. Mind that evaluate() + * catches any exceptions thrown in the evaluated code. + * + * \since Qt 6.1 + */ +bool QJSEngine::hasError() const +{ + return m_v4Engine->hasException; +} + +/*! + * If an exception is currently pending, catches it and returns it as a + * QJSValue. Otherwise returns undefined as QJSValue. After calling this method + * hasError() returns \c false. + * + * \since Qt 6.1 + */ +QJSValue QJSEngine::catchError() +{ + if (m_v4Engine->hasException) + return QJSValuePrivate::fromReturnedValue(m_v4Engine->catchException()); + else + return QJSValue(); +} + +/*! + \property QJSEngine::uiLanguage + \brief the language to be used for translating user interface strings + \since 5.15 + + This property holds the name of the language to be used for user interface + string translations. It is exposed for reading and writing as \c{Qt.uiLanguage} when + the QJSEngine::TranslationExtension is installed on the engine. It is always exposed + in instances of QQmlEngine. + + You can set the value freely and use it in bindings. It is recommended to set it + after installing translators in your application. By convention, an empty string + means no translation from the language used in the source code is intended to occur. +*/ +void QJSEngine::setUiLanguage(const QString &language) { + Q_D(QJSEngine); + d->uiLanguage = language; // property takes care of signal emission if necessary +} + +QString QJSEngine::uiLanguage() const +{ + Q_D(const QJSEngine); + return d->uiLanguage; +} + QJSEnginePrivate *QJSEnginePrivate::get(QV4::ExecutionEngine *e) { return e->jsEngine()->d_func(); @@ -1046,12 +1260,87 @@ void QJSEnginePrivate::removeFromDebugServer(QJSEngine *q) */ QJSEngine *qjsEngine(const QObject *object) { - QQmlData *data = QQmlData::get(object, false); + QQmlData *data = QQmlData::get(object); if (!data || data->jsWrapper.isNullOrUndefined()) return nullptr; return data->jsWrapper.engine()->jsEngine(); } + +/*! + \enum QJSEngine::ObjectOwnership + + ObjectOwnership controls whether or not the JavaScript memory manager automatically destroys the + QObject when the corresponding JavaScript object is garbage collected by the + engine. The two ownership options are: + + \value CppOwnership The object is owned by C++ code and the JavaScript memory manager will never + delete it. The JavaScript destroy() method cannot be used on these objects. This + option is similar to QScriptEngine::QtOwnership. + + \value JavaScriptOwnership The object is owned by JavaScript. When the object + is returned to the JavaScript memory manager as the return value of a method call, the JavaScript + memory manager will track it and delete it if there are no remaining JavaScript references to it + and it has no QObject::parent(). An object tracked by one QJSEngine will be deleted during that + QJSEngine's destructor. Thus, JavaScript references between objects with JavaScriptOwnership from + two different engines will not be valid if one of these engines is deleted. This option is similar + to QScriptEngine::ScriptOwnership. + + Generally an application doesn't need to set an object's ownership explicitly. The JavaScript + memory manager uses a heuristic to set the default ownership. By default, an object that is + created by the JavaScript memory manager has JavaScriptOwnership. The exception to this are the + root objects created by calling QQmlComponent::create() or QQmlComponent::beginCreate(), which + have CppOwnership by default. The ownership of these root-level objects is considered to have been + transferred to the C++ caller. + + Objects not-created by the JavaScript memory manager have CppOwnership by default. The exception + to this are objects returned from C++ method calls; their ownership will be set to + JavaScriptOwnership. This applies only to explicit invocations of Q_INVOKABLE methods or slots, + but not to property getter invocations. + + Calling setObjectOwnership() overrides the default ownership. + + \sa {Data Ownership} +*/ + +/*! + Sets the \a ownership of \a object. + + An object with \c JavaScriptOwnership is not garbage collected as long + as it still has a parent, even if there are no references to it. + + \sa QJSEngine::ObjectOwnership +*/ +void QJSEngine::setObjectOwnership(QObject *object, ObjectOwnership ownership) +{ + if (!object) + return; + + QQmlData *ddata = QQmlData::get(object, true); + if (!ddata) + return; + + ddata->indestructible = (ownership == CppOwnership)?true:false; + ddata->explicitIndestructibleSet = true; +} + +/*! + Returns the ownership of \a object. + + \sa QJSEngine::ObjectOwnership +*/ +QJSEngine::ObjectOwnership QJSEngine::objectOwnership(QObject *object) +{ + if (!object) + return CppOwnership; + + QQmlData *ddata = QQmlData::get(object, false); + if (!ddata) + return CppOwnership; + else + return ddata->indestructible?CppOwnership:JavaScriptOwnership; +} + QT_END_NAMESPACE #include "moc_qjsengine.cpp" |