diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2020-10-29 19:34:24 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2020-11-09 18:10:38 +0100 |
commit | cbe1869fbe7048f34513b201a1d9111a0a64257a (patch) | |
tree | 5c36cdf60a064f661551c8cf6a11a9716db4aa3f /src/qml/jsruntime | |
parent | d7008c79d4ec023527ebfc118ad47f40075f244d (diff) |
QML: Rewrite Qt object in actual C++
Quite obviously, the Qt object is a singleton, extended with a
namespace, backed by a member of the JavaScript global object.
Defining all the methods as JavaScript functions is unnecessary and
duplicates the general type transformation code. Also, it makes it
hard to use those same methods from a C++ context as we cannot
properly set up the arguments outside the JS engine.
Rewriting the Qt object reveals some deficiencies in the old
implementation that we need to fix now:
1. The enums of the Qt type were listed as properties of the Qt object,
which means you could iterate them with a for..in loop in in JavaScript.
This is just wrong. Enums are not properties. This functionality
is deleted and the test adapted to check for each enum value separately.
The commit message for the change that introduced the iterability
already mentioned that the author had failed to find any occurrence of
this in the real world.
2. Parsing time objects from strings was done by parsing the string as a
date/time and then picking the time from that. We still support that for
now, but output a (categorized) warning. Parsing the time directly is
preferred where possible.
3. Previously you could create (invalid) dates and times from various
kinds of QML types, like int and color. This does not work anymore as we
now validate the types before calling the functions.
4. Passing more arguments to a function than the function accepted was
unconditionally ignored before. Now, a Q_CLASSINFO on the surrounding
class can specify that the arguments should be checked, in which case a
JavaScript error is thrown if too many arguments are passed. In order
for this to work correctly we also have to ignore JS undefined values as
trailing arguments for overload resolution. This way, if a method
matching the defined arguments exists, it will be preferred over a
method that matches the full argument count, but possibly cannot accept
undefined as parameter.
Consequently a number of error messages change, which is reflected in
the qqmlqt test.
[ChangeLog][QtQMl][Important Behavior Changes] You can not iterate the
enumerations of the Qt object in JavaScript anymore. This does not work
with any other enumeration type either. You can of course still access
them by name, for example as Qt.LeftButton or similar.
[ChangeLog][QtQMl][Important Behavior Changes] The time formatting
functions of the Qt object in QML now allow you to pass an actual time
string, rather than a date/time string as argument. Passing a date/time
string results in a warning now.
[ChangeLog][QtQml][Important Behavior Changes] Functions in the Qt
object for formatting date and time will now throw a JavaScript error
when presented with a value of an incompatible type, such as int or
color.
[ChangeLog][QtQml][Important Behavior Changes] The Qt.resolvedUrl()
function now returns a URL rather than a string. This follows the
documentation.
[ChangeLog][QtQml][Important Behavior Changes] The GlobalColor enum of
the Qt namespace is not exposed to QML anymore. It did not make any
sense before as the enum values could not be used as colors.
Change-Id: I7fc2f24377eb2fde8f63a1ffac5548d652de7b12
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/qml/jsruntime')
-rw-r--r-- | src/qml/jsruntime/qv4engine.cpp | 14 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4engine_p.h | 1 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4include.cpp | 50 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4include_p.h | 9 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4qobjectwrapper.cpp | 59 |
5 files changed, 91 insertions, 42 deletions
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 923d458feb..fe34207ddc 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -2048,10 +2048,7 @@ void ExecutionEngine::initQmlGlobalObject() void ExecutionEngine::initializeGlobal() { - QV4::Scope scope(this); - - QV4::ScopedObject qt(scope, memoryManager->allocate<QV4::QtObject>(qmlEngine())); - globalObject->defineDefaultProperty(QStringLiteral("Qt"), qt); + createQtObject(); QV4::GlobalExtensions::init(globalObject, QJSEngine::AllExtensions); @@ -2078,6 +2075,15 @@ void ExecutionEngine::initializeGlobal() } } +void ExecutionEngine::createQtObject() +{ + QV4::Scope scope(this); + QtObject *qtObject = new QtObject(this); + QJSEngine::setObjectOwnership(qtObject, QJSEngine::JavaScriptOwnership); + QV4::ScopedObject qt(scope, QV4::QObjectWrapper::wrap(this, qtObject)); + globalObject->defineDefaultProperty(QStringLiteral("Qt"), qt); +} + const QSet<QString> &ExecutionEngine::illegalNames() const { return m_illegalNames; diff --git a/src/qml/jsruntime/qv4engine_p.h b/src/qml/jsruntime/qv4engine_p.h index 92bd3a0627..bf24663920 100644 --- a/src/qml/jsruntime/qv4engine_p.h +++ b/src/qml/jsruntime/qv4engine_p.h @@ -689,6 +689,7 @@ public: QV4::ReturnedValue global(); void initQmlGlobalObject(); void initializeGlobal(); + void createQtObject(); void freezeObject(const QV4::Value &value); diff --git a/src/qml/jsruntime/qv4include.cpp b/src/qml/jsruntime/qv4include.cpp index 17512cf4ff..c185894ab3 100644 --- a/src/qml/jsruntime/qv4include.cpp +++ b/src/qml/jsruntime/qv4include.cpp @@ -59,20 +59,21 @@ QT_BEGIN_NAMESPACE QV4Include::QV4Include(const QUrl &url, QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &callback) - : v4(engine), m_url(url) + : QObject(engine->jsEngine()) + , v4(engine), m_url(url) #if QT_CONFIG(qml_network) , m_redirectCount(0), m_network(nullptr) , m_reply(nullptr) #endif { if (qmlContext) - m_qmlContext.set(engine, *qmlContext); + m_qmlContext.set(v4, *qmlContext); if (callback.as<QV4::FunctionObject>()) - m_callbackFunction.set(engine, callback); + m_callbackFunction.set(v4, callback); m_resultObject.set(v4, resultValue(v4)); #if QT_CONFIG(qml_network) - if (QQmlEngine *qmlEngine = engine->qmlEngine()) { + if (QQmlEngine *qmlEngine = v4->qmlEngine()) { m_network = qmlEngine->networkAccessManager(); QNetworkRequest request; @@ -202,37 +203,40 @@ void QV4Include::finished() /* Documented in qv4engine.cpp */ -QV4::ReturnedValue QV4Include::method_include(const QV4::FunctionObject *b, const QV4::Value *, const QV4::Value *argv, int argc) +QJSValue QV4Include::method_include(QV4::ExecutionEngine *engine, const QUrl &url, + const QJSValue &callbackFunction) { - QV4::Scope scope(b); - if (!argc) - RETURN_UNDEFINED(); + QQmlRefPointer<QQmlContextData> context = engine->callingQmlContext(); - QQmlRefPointer<QQmlContextData> context = scope.engine->callingQmlContext(); - - if ((!context || !context->isJSContext()) && scope.engine->qmlEngine()) - RETURN_RESULT(scope.engine->throwError(QString::fromUtf8("Qt.include(): Can only be called from JavaScript files"))); + if ((!context || !context->isJSContext()) && engine->qmlEngine()) { + return QJSValuePrivate::fromReturnedValue( + engine->throwError( + QString::fromUtf8( + "Qt.include(): Can only be called from JavaScript files"))); + } - QV4::ScopedValue callbackFunction(scope, QV4::Value::undefinedValue()); - if (argc >= 2 && argv[1].as<QV4::FunctionObject>()) - callbackFunction = argv[1]; - QUrl url(scope.engine->resolvedUrl(argv[0].toQStringNoThrow())); - if (const QQmlEngine *qmlEngine = scope.engine->qmlEngine()) - url = qmlEngine->interceptUrl(url, QQmlAbstractUrlInterceptor::JavaScriptFile); + QV4::Scope scope(engine); + QV4::ScopedValue scopedCallbackFunction(scope, QV4::Value::undefinedValue()); + if (auto function = QJSValuePrivate::asManagedType<QV4::FunctionObject>(&callbackFunction)) + scopedCallbackFunction = *function; - QString localFile = QQmlFile::urlToLocalFileOrQrc(url); + const QQmlEngine *qmlEngine = engine->qmlEngine(); + const QUrl intercepted = qmlEngine + ? qmlEngine->interceptUrl(url, QQmlAbstractUrlInterceptor::JavaScriptFile) + : url; + QString localFile = QQmlFile::urlToLocalFileOrQrc(intercepted); QV4::ScopedValue result(scope); QV4::Scoped<QV4::QmlContext> qmlcontext(scope, scope.engine->qmlContext()); if (localFile.isEmpty()) { #if QT_CONFIG(qml_network) - QV4Include *i = new QV4Include(url, scope.engine, qmlcontext, callbackFunction); + QV4Include *i = new QV4Include(url, engine, qmlcontext, scopedCallbackFunction); result = i->result(); #else result = resultValue(scope.engine, NetworkError); - callback(callbackFunction, result); + callback(scopedCallbackFunction, result); #endif } else { QScopedPointer<QV4::Script> script; @@ -255,10 +259,10 @@ QV4::ReturnedValue QV4Include::method_include(const QV4::FunctionObject *b, cons result = resultValue(scope.engine, NetworkError, error); } - callback(callbackFunction, result); + callback(scopedCallbackFunction, result); } - return result->asReturnedValue(); + return QJSValuePrivate::fromReturnedValue(result->asReturnedValue()); } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4include_p.h b/src/qml/jsruntime/qv4include_p.h index 9d0a17a5cc..d375abf20a 100644 --- a/src/qml/jsruntime/qv4include_p.h +++ b/src/qml/jsruntime/qv4include_p.h @@ -61,7 +61,8 @@ QT_BEGIN_NAMESPACE -class QQmlEngine; +class QJSEngine; +class QJSValue; #if QT_CONFIG(qml_network) class QNetworkAccessManager; #endif @@ -77,13 +78,15 @@ public: Exception = 3 }; - static QV4::ReturnedValue method_include(const QV4::FunctionObject *, const QV4::Value *thisObject, const QV4::Value *argv, int argc); + static QJSValue method_include(QV4::ExecutionEngine *engine, const QUrl &url, + const QJSValue &callbackFunction); private Q_SLOTS: void finished(); private: - QV4Include(const QUrl &url, QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, const QV4::Value &callback); + QV4Include(const QUrl &url, QV4::ExecutionEngine *engine, QV4::QmlContext *qmlContext, + const QV4::Value &callback); ~QV4Include(); QV4::ReturnedValue result(); diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index cabe48dbef..1bf3a01f03 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -1554,6 +1554,16 @@ static const QQmlPropertyData * RelatedMethod(const QQmlObjectOrGadget &object, } } +static int numDefinedArguments(QV4::CallData *callArgs) +{ + int numDefinedArguments = callArgs->argc(); + while (numDefinedArguments > 0 + && callArgs->args[numDefinedArguments - 1].type() == QV4::StaticValue::Undefined_Type) { + --numDefinedArguments; + } + return numDefinedArguments; +} + static QV4::ReturnedValue CallPrecise(const QQmlObjectOrGadget &object, const QQmlPropertyData &data, QV4::ExecutionEngine *engine, QV4::CallData *callArgs, QMetaObject::Call callType = QMetaObject::InvokeMetaMethod) @@ -1567,6 +1577,35 @@ static QV4::ReturnedValue CallPrecise(const QQmlObjectOrGadget &object, const QQ + QLatin1String(unknownTypeError)); } + auto handleTooManyArguments = [&](int expectedArguments) { + const QMetaObject *metaObject = object.metaObject(); + const int indexOfClassInfo = metaObject->indexOfClassInfo("QML.StrictArguments"); + if (indexOfClassInfo != -1 + && QString::fromUtf8(metaObject->classInfo(indexOfClassInfo).value()) + == QStringLiteral("true")) { + engine->throwError(QStringLiteral("Too many arguments")); + return false; + } + + const auto stackTrace = engine->stackTrace(); + if (stackTrace.isEmpty()) { + qWarning().nospace().noquote() + << "When matching arguments for " + << object.className() << "::" << data.name(object.metaObject()) << "():"; + } else { + const StackFrame frame = engine->stackTrace().first(); + qWarning().noquote() << frame.function + QLatin1Char('@') + frame.source + + (frame.line > 0 ? (QLatin1Char(':') + QString::number(frame.line)) + : QString()); + } + + qWarning().noquote() << QStringLiteral("Too many arguments, ignoring %1") + .arg(callArgs->argc() - expectedArguments); + return true; + }; + + const int definedArgumentCount = numDefinedArguments(callArgs); + if (data.hasArguments()) { int *args = nullptr; @@ -1588,24 +1627,19 @@ static QV4::ReturnedValue CallPrecise(const QQmlObjectOrGadget &object, const QQ return engine->throwError(error); } - if (args[0] < callArgs->argc()) { - Q_ASSERT(!engine->stackTrace().isEmpty()); + if (args[0] < definedArgumentCount) { + if (!handleTooManyArguments(args[0])) + return Encode::undefined(); - const StackFrame frame = engine->stackTrace().first(); - qWarning().noquote() << frame.function + QLatin1Char('@') + frame.source - + (frame.line > 0 ? (QLatin1Char(':') + QString::number(frame.line)) - : QString()); - - qWarning().noquote() << QStringLiteral("Too many arguments, ignoring %1") - .arg(callArgs->argc() - args[0]); } return CallMethod(object, data.coreIndex(), returnType, args[0], args + 1, engine, callArgs, callType); } else { + if (definedArgumentCount > 0 && !handleTooManyArguments(0)) + return Encode::undefined(); return CallMethod(object, data.coreIndex(), returnType, 0, nullptr, engine, callArgs, callType); - } } @@ -1626,7 +1660,8 @@ static QV4::ReturnedValue CallOverloaded(const QQmlObjectOrGadget &object, const QV4::ExecutionEngine *engine, QV4::CallData *callArgs, const QQmlPropertyCache *propertyCache, QMetaObject::Call callType = QMetaObject::InvokeMetaMethod) { - int argumentCount = callArgs->argc(); + const int argumentCount = callArgs->argc(); + const int definedArgumentCount = numDefinedArguments(callArgs); QQmlPropertyData best; int bestParameterScore = INT_MAX; @@ -1654,7 +1689,7 @@ static QV4::ReturnedValue CallOverloaded(const QQmlObjectOrGadget &object, const if (methodArgumentCount > argumentCount) continue; // We don't have sufficient arguments to call this method - int methodParameterScore = argumentCount - methodArgumentCount; + int methodParameterScore = definedArgumentCount - methodArgumentCount; if (methodParameterScore > bestParameterScore) continue; // We already have a better option |