// Copyright (C) 2020 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE /*! * \class QJSManagedValue * \inmodule QtQml * \since 6.1 * * \inmodule QtQml * * \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-262}. * * \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 * \value Function The \c function 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) { if (!d) return nullptr; QV4::ExecutionEngine *v4 = QV4::PersistentValueStorage::getEngine(d); Q_ASSERT(v4); return v4; } /*! * 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()) *result.d = object->getPrototypeOf(); else if (auto managed = d->as()) *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() : 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())) { 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; if (d->isFunctionObject()) return Function; 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. */ /*! * \fn QJSManagedValue::isFunction() const * * Returns \c true if the type of this QJSManagedValue is \c function, * \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(); } /*! * Returns \c true if this value represents a JavaScript Array * object, or \c false otherwise. */ bool QJSManagedValue::isArray() const { return d && d->as(); } /*! * Returns \c true if this value represents a JavaScript Url * object, or \c false otherwise. */ bool QJSManagedValue::isUrl() const { return d && d->as(); } /*! * 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(); } /*! * 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(); } /*! * 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(); } /*! * Returns \c true if this value represents a JavaScript Date object, or * \c false otherwise. */ bool QJSManagedValue::isDate() const { return d && d->as(); } /*! * Returns \c true if this value represents a JavaScript Error object, or * \c false otherwise. */ bool QJSManagedValue::isError() const { return d && d->as(); } /*! * \internal * * Returns \c true if this value represents a JavaScript meta type, or \c false * otherwise. */ bool QJSManagedValue::isJsMetaType() const { return d && d->as(); } /*! * 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(), 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 (d->as()) return QV4::ExecutionEngine::toVariant(*d, QMetaType{}, 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() : 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() : 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() : 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() : 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() : 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::Scope scope(obj->engine()); QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); return obj->hasProperty(key); } 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::Scope scope(obj->engine()); QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); return obj->getOwnProperty(key) != 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) return QJSValue(); if (d->isNullOrUndefined()) { QV4::ExecutionEngine *e = v4Engine(d); e->throwTypeError(QStringLiteral("Cannot read property '%1' of null").arg(name)); return QJSValue(); } if (QV4::String *string = d->as()) { if (name == QStringLiteral("length")) return QJSValue(string->d()->length()); } if (QV4::Object *obj = d->as()) { QV4::Scope scope(obj->engine()); QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); return QJSValuePrivate::fromReturnedValue(obj->get(key)); } 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 (d->isNullOrUndefined()) { v4Engine(d)->throwTypeError( QStringLiteral("Value is null and could not be converted to an object")); } if (QV4::Object *obj = d->as()) { QV4::Scope scope(obj->engine()); QV4::ExecutionEngine *v4 = QJSValuePrivate::engine(&value); if (Q_UNLIKELY(v4 && v4 != scope.engine)) { qWarning("QJSManagedValue::setProperty() failed: " "Value was created in different engine."); return; } QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); obj->put(key, QJSValuePrivate::convertToReturnedValue(scope.engine, value)); } } /*! * Deletes the property \a name from this QJSManagedValue. Returns \c true if * the deletion succeeded, or \c false otherwise. */ bool QJSManagedValue::deleteProperty(const QString &name) { if (!d) return false; if (QV4::Object *obj = d->as()) { QV4::Scope scope(obj->engine()); QV4::ScopedPropertyKey key(scope, scope.engine->identifierTable->asPropertyKey(name)); return obj->deleteProperty(key); } 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::String *string = d->as()) return arrayIndex < quint32(string->d()->length()); if (QV4::Object *obj = d->as()) { bool hasProperty = false; if (arrayIndex == std::numeric_limits::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::String *string = d->as()) return arrayIndex < quint32(string->d()->length()); if (QV4::Object *obj = d->as()) { if (arrayIndex == std::numeric_limits::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::String *string = d->as()) { const QString qString = string->toQString(); if (arrayIndex < quint32(qString.size())) return qString.sliced(arrayIndex, 1); return QJSValue(); } if (QV4::Object *obj = d->as()) { if (arrayIndex == std::numeric_limits::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::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 \c false otherwise. */ bool QJSManagedValue::deleteProperty(quint32 arrayIndex) { if (!d) return false; if (QV4::Object *obj = d->as()) return obj->deleteProperty(QV4::PropertyKey::fromArrayIndex(arrayIndex)); return false; } static const QV4::FunctionObject *functionObjectForCall(QV4::Value *d) { if (Q_UNLIKELY(!d)) { qWarning("QJSManagedValue: Calling a default-constructed or moved-from managed value" "should throw an exception, but there is no engine to receive it."); return nullptr; } if (const QV4::FunctionObject *f = d->as()) return f; v4Engine(d)->throwTypeError(QStringLiteral("Value is not a function")); return nullptr; } /*! * 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 = functionObjectForCall(d); if (!f) return QJSValue(); QV4::ExecutionEngine *engine = f->engine(); QV4::Scope scope(engine); QV4::JSCallArguments jsCallData(scope, arguments.size()); *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 = functionObjectForCall(d); 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::JSCallArguments jsCallData(scope, arguments.size()); *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 = functionObjectForCall(d); if (!f) return QJSValue(); QV4::ExecutionEngine *engine = f->engine(); QV4::Scope scope(engine); QV4::JSCallArguments jsCallData(scope, arguments.size()); 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)); } /*! * \internal * * Retrieves the JavaScript meta type of this value. The JavaScript meta type * represents the layout of members in an object. Instantiating a meta type is * faster than re-constructing the same object using a sequence of setProperty() * calls on a new object. * * \sa members(), instantiate() */ QJSManagedValue QJSManagedValue::jsMetaType() const { if (!d) return QJSManagedValue(); QJSManagedValue result(v4Engine(d)); if (QV4::Managed *m = d->as()) *result.d = m->internalClass(); return result; } /*! * \internal * * If this value is a JavaScript meta type, retrieves the names of its members * The ordering of the names corresponds to the ordering of the values to be * passed to instantiate(). * * If the value is not a meta type, an empty list is returned. * * \sa isMetaType(), metaType(), instantiate() */ QStringList QJSManagedValue::jsMetaMembers() const { if (!d) return {}; if (QV4::InternalClass *c = d->as()) { const auto heapClass = c->d(); const int size = heapClass->size; QStringList result; result.reserve(size); QV4::Scope scope(c->engine()); for (int i = 0; i < size; ++i) { QV4::ScopedValue key(scope, heapClass->keyAt(i)); result.append(key->toQString()); } return result; } return {}; } /*! * \internal * * If this value is a JavaScript meta type, instantiates it using the * \a values, and returns the result. Otherwise returns undefined. * * The values are expected in the same order as the keys in the return value of * members(), and that is the order in which properties were added to the object * this meta type originally belongs to. * * \sa members(), metaType(), isMetaType(). */ QJSManagedValue QJSManagedValue::jsMetaInstantiate(const QJSValueList &values) const { if (!d) return {}; if (QV4::InternalClass *c = d->as()) { QV4::ExecutionEngine *engine = c->engine(); QJSManagedValue result(engine); *result.d = c->engine()->newObject(c->d()); QV4::Object *o = result.d->as(); for (uint i = 0, end = qMin(qsizetype(c->d()->size), values.size()); i < end; ++i) { const QJSValue &arg = values[i]; if (Q_UNLIKELY(!QJSValuePrivate::checkEngine(engine, arg))) { qWarning("QJSManagedValue::instantiate() failed: " "Argument was created in different engine."); return QJSManagedValue(); } o->setProperty(i, QJSValuePrivate::convertToReturnedValue(engine, arg)); } return result; } return {}; } QJSManagedValue::QJSManagedValue(QV4::ExecutionEngine *engine) : d(engine->memoryManager->m_persistentValues->allocate()) { } QT_END_NAMESPACE