diff options
Diffstat (limited to 'src/qml')
28 files changed, 376 insertions, 111 deletions
diff --git a/src/qml/common/qv4staticvalue_p.h b/src/qml/common/qv4staticvalue_p.h index 8160bbb748..0716f7ea20 100644 --- a/src/qml/common/qv4staticvalue_p.h +++ b/src/qml/common/qv4staticvalue_p.h @@ -51,6 +51,7 @@ // #include <QtCore/private/qnumeric_p.h> +#include <cstring> #ifdef QT_NO_DEBUG #define QV4_NEARLY_ALWAYS_INLINE Q_ALWAYS_INLINE diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index aab3f8e9d6..171dc641d3 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -908,7 +908,7 @@ bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node) bool IRBuilder::visit(QQmlJS::AST::UiSourceElement *node) { - if (QQmlJS::AST::FunctionDeclaration *funDecl = QQmlJS::AST::cast<QQmlJS::AST::FunctionDeclaration *>(node->sourceElement)) { + if (QQmlJS::AST::FunctionExpression *funDecl = node->sourceElement->asFunctionDefinition()) { CompiledFunctionOrExpression *foe = New<CompiledFunctionOrExpression>(); foe->node = funDecl; foe->parentNode = funDecl; @@ -1770,7 +1770,7 @@ QVector<int> JSCodeGen::generateJSCodeForFunctionsAndBindings(const QList<Compil for (const CompiledFunctionOrExpression &f : functions) { Q_ASSERT(f.node != document->program); Q_ASSERT(f.parentNode && f.parentNode != document->program); - QQmlJS::AST::FunctionDeclaration *function = QQmlJS::AST::cast<QQmlJS::AST::FunctionDeclaration*>(f.node); + auto function = f.node->asFunctionDefinition(); if (function) { scan.enterQmlFunction(function); @@ -1794,7 +1794,7 @@ QVector<int> JSCodeGen::generateJSCodeForFunctionsAndBindings(const QList<Compil QQmlJS::AST::Node *node = qmlFunction.node; Q_ASSERT(node != document->program); - QQmlJS::AST::FunctionDeclaration *function = QQmlJS::AST::cast<QQmlJS::AST::FunctionDeclaration*>(node); + QQmlJS::AST::FunctionExpression *function = node->asFunctionDefinition(); QString name; if (function) diff --git a/src/qml/compiler/qv4compilerscanfunctions_p.h b/src/qml/compiler/qv4compilerscanfunctions_p.h index f67db030a2..2de80eac44 100644 --- a/src/qml/compiler/qv4compilerscanfunctions_p.h +++ b/src/qml/compiler/qv4compilerscanfunctions_p.h @@ -88,7 +88,7 @@ public: const QString &name); void leaveEnvironment(); - void enterQmlFunction(QQmlJS::AST::FunctionDeclaration *ast) + void enterQmlFunction(QQmlJS::AST::FunctionExpression *ast) { enterFunction(ast, false); } protected: diff --git a/src/qml/debugger/qqmlconfigurabledebugservice_p.h b/src/qml/debugger/qqmlconfigurabledebugservice_p.h index 96ec46f475..38f41047c0 100644 --- a/src/qml/debugger/qqmlconfigurabledebugservice_p.h +++ b/src/qml/debugger/qqmlconfigurabledebugservice_p.h @@ -64,7 +64,7 @@ class QQmlConfigurableDebugService : public Base { protected: QQmlConfigurableDebugService(float version, QObject *parent = nullptr) : - Base(version, parent), m_configMutex(QMutex::Recursive) + Base(version, parent) { init(); } @@ -103,7 +103,7 @@ protected: emit Base::attachedToEngine(engine); } - QMutex m_configMutex; + QRecursiveMutex m_configMutex; QList<QJSEngine *> m_waitingEngines; bool m_waitingForConfiguration; }; diff --git a/src/qml/doc/src/cppintegration/definetypes.qdoc b/src/qml/doc/src/cppintegration/definetypes.qdoc index 41bc9fd140..2e8102bd65 100644 --- a/src/qml/doc/src/cppintegration/definetypes.qdoc +++ b/src/qml/doc/src/cppintegration/definetypes.qdoc @@ -471,7 +471,7 @@ will be accessible to the \e attachee: class MessageBoardAttachedType : public QObject { Q_OBJECT - Q_PROPERTY(bool expired READ expired WRITE expired NOTIFY expiredChanged) + Q_PROPERTY(bool expired READ expired WRITE setExpired NOTIFY expiredChanged) public: MessageBoardAttachedType(QObject *parent); bool expired() const; @@ -493,7 +493,7 @@ class MessageBoard : public QObject { Q_OBJECT public: - static MessageBoard *qmlAttachedProperties(QObject *object) + static MessageBoardAttachedType *qmlAttachedProperties(QObject *object) { return new MessageBoardAttachedType(object); } diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index ab54b5fd1d..9c106558fd 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -487,8 +487,7 @@ A QObject singleton type may be referenced via the type name with which it was registered, and this typename may be used as the target in a \l Connections type or otherwise used as any other type id would. - One exception to this is that a QObject singleton type property may not be aliased (because the - singleton type name does not identify an object within the same component as any other item). + One exception to this is that a QObject singleton type property may not be aliased. \b{NOTE:} A QObject singleton type instance returned from a singleton type provider is owned by the QML engine unless the object has explicit QQmlEngine::CppOwnership flag set. @@ -619,6 +618,100 @@ */ /*! + \fn int qmlRegisterSingletonInstance(const char *uri, int versionMajor, int + versionMinor, const char *typeName, QObject* cppObject) + \relates QQmlEngine + \since 5.14 + + This function is used to register a singleton object \a cppObject, with a + particular \a uri and \a typeName. Its version is a combination of \a + versionMajor and \a versionMinor. + + Installing a singleton type into a URI allows you to provide arbitrary + functionality (methods and properties) to QML code without requiring + individual instances of the type to be instantiated by the client. + + Use this function to register an object of the given type T as a singleton + type. + + A QObject singleton type may be referenced via the type name with which it + was registered; in turn this type name may be used as the target in a \l + Connections type, or like any other type ID. However, there's one + exception: a QObject singleton type property can't be aliased because the + singleton type name does not identify an object within the same component + as any other item. + + \note \a cppObject must outlive the QML engine in which it is used. + Moreover, \cppObject must have the same thread affinity as the engine. If + you want separate singleton instances for multiple engines, you need to use + \l {qmlRegisterSingletonType}. See \l{Threads and QObjects} for more + information about thread safety. + + Usage: + \code + // First, define your QObject which provides the functionality. + class SingletonTypeExample : public QObject + { + Q_OBJECT + Q_PROPERTY(int someProperty READ someProperty WRITE setSomeProperty NOTIFY somePropertyChanged) + + public: + explicit SingletonTypeExample(QObject* parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE int doSomething() + { + setSomeProperty(5); + return m_someProperty; + } + + int someProperty() const { return m_someProperty; } + void setSomeProperty(int val) { + if (m_someProperty != val) { + m_someProperty = val; + emit somePropertyChanged(val); + } + } + + signals: + void somePropertyChanged(int newValue); + + private: + int m_someProperty = 0; + }; + \endcode + + \code + // Second, create an instance of the object + + // allocate example before the engine to ensure that it outlives it + QScopedPointer<SingletonTypeExample> example(new SingletonTypeExample); + QQmlEngine engine; + + // Third, register the singleton type provider with QML by calling this + // function in an initialization function. + qmlRegisterSingletonInstance("Qt.example.qobjectSingleton", 1, 0, "MyApi", example.get()); + \endcode + + + In order to use the registered singleton type in QML, you must import the + URI with the corresponding version. + \qml + import QtQuick 2.0 + import Qt.example.qobjectSingleton 1.0 + Item { + id: root + property int someValue: MyApi.someProperty + + Component.onCompleted: { + console.log(MyApi.doSomething()) + } + } + \endqml + + \sa qmlRegisterSingletonType + */ + +/*! \fn int qmlRegisterType(const QUrl &url, const char *uri, int versionMajor, int versionMinor, const char *qmlName); \relates QQmlEngine diff --git a/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc b/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc index a5ad6af4a2..e607666886 100644 --- a/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc +++ b/src/qml/doc/src/qmllanguageref/syntax/propertybinding.qdoc @@ -128,6 +128,9 @@ loops - it could indicate that the binding is being used for more than describin property relationships. Complex bindings can reduce code performance, readability, and maintainability. It may be a good idea to redesign components that have complex bindings, or at least factor the binding out into a separate function. +As a general rule, users should not rely on the evaluation order of bindings. + +\sa {Positioning with Anchors} \target qml-javascript-assignment diff --git a/src/qml/jsapi/qjsengine_p.h b/src/qml/jsapi/qjsengine_p.h index a77d710cff..164a70d000 100644 --- a/src/qml/jsapi/qjsengine_p.h +++ b/src/qml/jsapi/qjsengine_p.h @@ -74,7 +74,7 @@ public: static const QJSEnginePrivate* get(const QJSEngine*e) { return e->d_func(); } static QJSEnginePrivate* get(QV4::ExecutionEngine *e); - QJSEnginePrivate() : mutex(QMutex::Recursive) {} + QJSEnginePrivate() = default; ~QJSEnginePrivate() override; static void addToDebugServer(QJSEngine *q); @@ -105,7 +105,7 @@ public: }; // Shared by QQmlEngine - mutable QMutex mutex; + mutable QRecursiveMutex mutex; // These methods may be called from the QML loader thread diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp index 1b26608bf3..bba88e5c9a 100644 --- a/src/qml/jsruntime/qv4dateobject.cpp +++ b/src/qml/jsruntime/qv4dateobject.cpp @@ -54,7 +54,7 @@ #include <wtf/MathExtras.h> -#if defined(Q_OS_LINUX) && QT_CONFIG(timezone) +#if defined(Q_OS_LINUX) && QT_CONFIG(timezone) && !defined(Q_OS_ANDROID) /* See QTBUG-56899. Although we don't (yet) have a proper way to reset the system zone, the code below, that uses QTimeZone::systemTimeZone(), works diff --git a/src/qml/jsruntime/qv4executableallocator.cpp b/src/qml/jsruntime/qv4executableallocator.cpp index c836d121e3..7ee6f39aa2 100644 --- a/src/qml/jsruntime/qv4executableallocator.cpp +++ b/src/qml/jsruntime/qv4executableallocator.cpp @@ -147,9 +147,7 @@ bool ExecutableAllocator::ChunkOfPages::contains(Allocation *alloc) const } ExecutableAllocator::ExecutableAllocator() - : mutex(QMutex::NonRecursive) -{ -} + = default; ExecutableAllocator::~ExecutableAllocator() { diff --git a/src/qml/jsruntime/qv4executableallocator_p.h b/src/qml/jsruntime/qv4executableallocator_p.h index 013c6d7120..f98f2c7d33 100644 --- a/src/qml/jsruntime/qv4executableallocator_p.h +++ b/src/qml/jsruntime/qv4executableallocator_p.h @@ -130,7 +130,7 @@ public: private: QMultiMap<size_t, Allocation*> freeAllocations; QMap<quintptr, ChunkOfPages*> chunks; - mutable QMutex mutex; + mutable QRecursiveMutex mutex; }; } diff --git a/src/qml/jsruntime/qv4executablecompilationunit.cpp b/src/qml/jsruntime/qv4executablecompilationunit.cpp index a9283cfa54..950e0b10ea 100644 --- a/src/qml/jsruntime/qv4executablecompilationunit.cpp +++ b/src/qml/jsruntime/qv4executablecompilationunit.cpp @@ -694,7 +694,7 @@ bool ExecutableCompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorSt } return CompiledData::SaveableUnitPointer(unitData()).saveToDisk<char>( - [this, &unitUrl, errorString](const char *data, quint32 size) { + [&unitUrl, errorString](const char *data, quint32 size) { return CompiledData::SaveableUnitPointer::writeDataToFile(localCacheFilePath(unitUrl), data, size, errorString); }); diff --git a/src/qml/jsruntime/qv4promiseobject.cpp b/src/qml/jsruntime/qv4promiseobject.cpp index b32e227b58..27075e96a0 100644 --- a/src/qml/jsruntime/qv4promiseobject.cpp +++ b/src/qml/jsruntime/qv4promiseobject.cpp @@ -86,6 +86,7 @@ namespace QV4 { namespace Promise { const int PROMISE_REACTION_EVENT = QEvent::registerEventType(); +const int PROMISE_RESOLVE_THENABLE_EVENT = QEvent::registerEventType(); struct ReactionEvent : public QEvent { @@ -99,6 +100,18 @@ struct ReactionEvent : public QEvent QV4::PersistentValue resolution; }; +struct ResolveThenableEvent : public QEvent +{ + ResolveThenableEvent(ExecutionEngine *e, const PromiseObject *promise_, const Object *thenable_, const FunctionObject *then_) + : QEvent(QEvent::Type(PROMISE_RESOLVE_THENABLE_EVENT)), promise(e, *promise_), thenable(e, *thenable_), then(e, *then_) + {} + + QV4::PersistentValue promise; + QV4::PersistentValue thenable; + QV4::PersistentValue then; +}; + + } // namespace Promise } // namespace QV4 QT_END_NAMESPACE @@ -115,6 +128,11 @@ void ReactionHandler::addReaction(ExecutionEngine *e, const Value *reaction, con QCoreApplication::postEvent(this, new ReactionEvent(e, reaction, value)); } +void ReactionHandler::addResolveThenable(ExecutionEngine *e, const PromiseObject *promise, const Object *thenable, const FunctionObject *then) +{ + QCoreApplication::postEvent(this, new ResolveThenableEvent(e, promise, thenable, then)); +} + void ReactionHandler::customEvent(QEvent *event) { if (event) @@ -122,6 +140,9 @@ void ReactionHandler::customEvent(QEvent *event) const int type = event->type(); if (type == PROMISE_REACTION_EVENT) executeReaction(static_cast<ReactionEvent*>(event)); + + if (type == PROMISE_RESOLVE_THENABLE_EVENT) + executeResolveThenable(static_cast<ResolveThenableEvent*>(event)); } } @@ -159,6 +180,7 @@ void ReactionHandler::executeReaction(ReactionEvent *event) } } + namespace { class FunctionBuilder { @@ -200,6 +222,26 @@ public: } + +void ReactionHandler::executeResolveThenable(ResolveThenableEvent *event) +{ + Scope scope(event->then.engine()); + JSCallData jsCallData(scope, 2); + PromiseObject *promise = event->promise.as<PromiseObject>(); + ScopedFunctionObject resolve {scope, FunctionBuilder::makeResolveFunction(scope.engine, promise->d())}; + ScopedFunctionObject reject {scope, FunctionBuilder::makeRejectFunction(scope.engine, promise->d())}; + jsCallData->args[0] = resolve; + jsCallData.args[1] = reject; + jsCallData->thisObject = event->thenable.as<QV4::Object>(); + event->then.as<const FunctionObject>()->call(jsCallData); + if (scope.engine->hasException) { + JSCallData rejectCallData(scope, 1); + rejectCallData->args[0] = scope.engine->catchException(); + Scoped<RejectWrapper> reject {scope, scope.engine->memoryManager->allocate<QV4::RejectWrapper>()}; + reject->call(rejectCallData); + } +} + void Heap::PromiseObject::setState(PromiseObject::State state) { this->state = state; @@ -363,23 +405,36 @@ void Heap::RejectWrapper::init() Heap::FunctionObject::init(); } +ReturnedValue PromiseCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) +{ + // 25.4.3.1 Promise ( executor ) + // 1. If NewTarget is undefined, throw a TypeError exception. + Scope scope(f); + THROW_TYPE_ERROR(); +} ReturnedValue PromiseCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) { + // 25.4.3.1 Promise ( executor ) + Scope scope(f); - if (argc != 1) + if (argc == 0) // If there are no arguments, argument 1 will be undefined ==> thus not callable ==> type error THROW_TYPE_ERROR(); ScopedFunctionObject executor(scope, argv[0].as<const FunctionObject>()); - if (!executor) - THROW_TYPE_ERROR(); + if (!executor) //2. If IsCallable(executor) is false + THROW_TYPE_ERROR(); // throw a TypeError exception Scoped<PromiseObject> a(scope, scope.engine->newPromiseObject()); if (scope.engine->hasException) return Encode::undefined(); - a->d()->state = Heap::PromiseObject::Pending; + a->d()->state = Heap::PromiseObject::Pending; //4. Set promise.[[PromiseState]] to "pending" + // 5. Set promise.[[PromiseFulfillReactions]] to a new empty List. + // 6. Set promise.[[PromiseRejectReactions]] to a new empty List. + // 7. Set promise.[[PromiseIsHandled]] to false. + // happens in constructor of a ScopedFunctionObject resolve(scope, FunctionBuilder::makeResolveFunction(scope.engine, a->d())); ScopedFunctionObject reject(scope, FunctionBuilder::makeRejectFunction(scope.engine, a->d())); @@ -387,13 +442,15 @@ ReturnedValue PromiseCtor::virtualCallAsConstructor(const FunctionObject *f, con JSCallData jsCallData(scope, 2); jsCallData->args[0] = resolve; jsCallData->args[1] = reject; - jsCallData->thisObject = a; + //jsCallData->thisObject = a; VERIFY corretness, but this should be undefined (see below) - executor->call(jsCallData); + executor->call(jsCallData); // 9. Let completion be Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »). if (scope.engine->hasException) { - a->d()->state = Heap::PromiseObject::Rejected; - a->d()->resolution.set(scope.engine, Value::fromReturnedValue(scope.engine->catchException())); + ScopedValue exception {scope, scope.engine->catchException()}; + JSCallData callData {scope, 1}; + callData.args[0] = exception; + reject->call(callData); } if (newTarget) @@ -402,42 +459,42 @@ ReturnedValue PromiseCtor::virtualCallAsConstructor(const FunctionObject *f, con return a->asReturnedValue(); } -ReturnedValue PromiseCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int) -{ - Scope scope(f); - THROW_TYPE_ERROR(); -} ReturnedValue PromiseCtor::method_resolve(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) { + // 25.4.4.5Promise.resolve ( x ) Scope scope(f); ExecutionEngine* e = scope.engine; - if (!thisObject || !thisObject->isObject()) + if (!thisObject || !thisObject->isObject()) // 2. If Type(C) is not Object, throw a TypeError exception THROW_TYPE_ERROR(); - ScopedValue argument(scope); + ScopedValue x(scope); if (argc < 1) { - argument = Encode::undefined(); + x = Encode::undefined(); } else { - argument = argv[0]; + x = argv[0]; } - if (isPromise(argument) && argument->isObject()) { + // 3. If IsPromise(x) is true, then + if (isPromise(x) && x->isObject()) { ScopedObject so(scope, thisObject); - ScopedObject constructor(scope, argument->objectValue()->get(e->id_constructor())); - if (so->d() == constructor->d()) - return argument->asReturnedValue(); + // Let xConstructor be ? Get(x, "constructor"). + ScopedObject constructor(scope, x->objectValue()->get(e->id_constructor())); + if (so->d() == constructor->d()) // If SameValue(xConstructor, C) is true, return x. + return x->asReturnedValue(); } + // Let promiseCapability be ? NewPromiseCapability(C). Scoped<PromiseCapability> capability(scope, e->memoryManager->allocate<QV4::PromiseCapability>()); ScopedObject newPromise(scope, e->newPromiseObject(thisObject->as<const FunctionObject>(), capability)); if (!newPromise || !isCallable(capability->d()->resolve) || !isCallable(capability->d()->reject)) THROW_TYPE_ERROR(); + // Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »). ScopedValue undefined(scope, Value::undefinedValue()); ScopedFunctionObject resolve(scope, capability->d()->resolve); - resolve->call(undefined, argument, 1); + resolve->call(undefined, x, 1); return newPromise.asReturnedValue(); } @@ -447,16 +504,18 @@ ReturnedValue PromiseCtor::method_reject(const FunctionObject *f, const Value *t Scope scope(f); ExecutionEngine *e = scope.engine; + // 2. If Type(C) is not Object, throw a TypeError exception. if (!thisObject || !thisObject->isObject()) THROW_TYPE_ERROR(); - ScopedValue argument(scope); + ScopedValue r(scope); if (argc < 1) { - argument = Encode::undefined(); + r = Encode::undefined(); } else { - argument = argv[0]; + r = argv[0]; } + // 3. Let promiseCapability be ? NewPromiseCapability(C). Scoped<PromiseCapability> capability(scope, e->memoryManager->allocate<QV4::PromiseCapability>()); ScopedObject newPromise(scope, e->newPromiseObject(thisObject->as<const FunctionObject>(), capability)); @@ -465,7 +524,8 @@ ReturnedValue PromiseCtor::method_reject(const FunctionObject *f, const Value *t ScopedValue undefined(scope, Value::undefinedValue()); ScopedFunctionObject reject(scope, capability->d()->reject.as<const FunctionObject>()); - reject->call(undefined, argument, 1); + // Perform ? Call(promiseCapability.[[Reject]], undefined, « r »). + reject->call(undefined, r, 1); return newPromise.asReturnedValue(); } @@ -475,6 +535,7 @@ ReturnedValue PromiseCtor::method_all(const FunctionObject *f, const Value *this Scope scope(f); ExecutionEngine* e = scope.engine; + // 2. If Type(C) is not Object, throw a TypeError exception. if (!thisObject || !thisObject->isObject()) THROW_TYPE_ERROR(); @@ -777,6 +838,7 @@ void PromisePrototype::init(ExecutionEngine *engine, Object *ctor) ReturnedValue PromisePrototype::method_then(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) { + // 25.4.5.3 Promise.prototype.then Scope scope(f); ExecutionEngine* e = scope.engine; @@ -804,6 +866,7 @@ ReturnedValue PromisePrototype::method_then(const FunctionObject *f, const Value if (!constructor || scope.hasException()) THROW_TYPE_ERROR(); + // 4. Let resultCapability be ? NewPromiseCapability(C). ScopedObject nextPromise(scope, e->newPromiseObject(constructor, capability)); capability->d()->promise.set(scope.engine, nextPromise); @@ -811,21 +874,25 @@ ReturnedValue PromisePrototype::method_then(const FunctionObject *f, const Value Scoped<PromiseReaction> rejectReaction(scope, Heap::PromiseReaction::createRejectReaction(scope.engine, capability, onRejected)); ScopedValue resolution(scope, promise->d()->resolution); - if (promise->d()->isPending()) { + if (promise->d()->isPending()) { // 7. If promise.[[PromiseState]] is "pending" { + // Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]]. ScopedArrayObject a(scope, promise->d()->fulfillReactions); ScopedValue newValue(scope, fulfillReaction->d()); a->push_back(newValue); } { + // Append rejectReaction as the last element of the List that is promise.[[PromiseRejectReactions]]. ScopedArrayObject a(scope, promise->d()->rejectReactions); ScopedValue newValue(scope, rejectReaction->d()); a->push_back(newValue); } - } else if (promise->d()->isFulfilled()) { - fulfillReaction->as<QV4::PromiseReaction>()->d()->triggerWithValue(e, resolution); - } else if (promise->d()->isRejected()) { + } else if (promise->d()->isFulfilled()) { // 8. Else if promise.[[PromiseState]] is "fulfilled", then + // Perform EnqueueJob("PromiseJobs", PromiseReactionJob, « fulfillReaction, value »). + fulfillReaction->as<QV4::PromiseReaction>()->d()->triggerWithValue(e, resolution); // Perform EnqueueJob("PromiseJobs", PromiseReactionJob, « fulfillReaction, value »). + } else if (promise->d()->isRejected()) { // 9. Else + // Perform EnqueueJob("PromiseJobs", PromiseReactionJob, « rejectReaction, reason »). rejectReaction->as<QV4::PromiseReaction>()->d()->triggerWithValue(e, resolution); } else { Q_ASSERT(false); @@ -889,7 +956,6 @@ ReturnedValue CapabilitiesExecutorWrapper::virtualCall(const FunctionObject *f, if (argc >= 2 && !argv[1].isUndefined()) capabilities->reject.set(scope.engine, argv[1]); - // TODO: return? return Encode::undefined(); } @@ -929,27 +995,60 @@ ReturnedValue ResolveElementWrapper::virtualCall(const FunctionObject *f, const ReturnedValue ResolveWrapper::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) { + // 25.4.1.3.2 (ecmase-262/8.0) + Q_UNUSED(thisObject); Scope scope(f); const ResolveWrapper *self = static_cast<const ResolveWrapper*>(f); Scoped<PromiseObject> promise(scope, self->d()->promise); - if (self->d()->alreadyResolved || !promise->d()->isPending()) + // 4. If alreadyRseolved.[[Value]] is true, return undefined + if (self->d()->alreadyResolved || !promise->d()->isPending()) // Why check for pending? return Encode::undefined(); - ScopedValue value(scope); + // 5. Set alreadyResolved.[[Value]] to true + self->d()->alreadyResolved = true; + + ScopedValue resolution(scope); if (argc == 1) { - value = argv[0]; + resolution = argv[0]; } else { - value = Encode::undefined(); + resolution = Encode::undefined(); } - self->d()->alreadyResolved = true; - promise->d()->setState(Heap::PromiseObject::Fulfilled); - promise->d()->resolution.set(scope.engine, value); - - promise->d()->triggerFullfillReactions(scope.engine); + if (!resolution->isObject()) { // 7 If Type(resolution) is not Object + // then Return FullFillPromise(promise, resolution) + // (FullFillPromise will return undefined, so we share the return with the other path which also returns undefined + promise->d()->setState(Heap::PromiseObject::Fulfilled); + promise->d()->resolution.set(scope.engine, resolution); + promise->d()->triggerFullfillReactions(scope.engine); + } else { + //PromiseObject *promise = resolution->as<PromiseObject>(); + auto resolutionObject = resolution->as<Object>(); + ScopedString thenName(scope, scope.engine->newIdentifier(QStringLiteral("then"))); + + // 8. Let then be Get(resolution, then) + ScopedFunctionObject thenAction { scope, resolutionObject->get(thenName)}; + // 9. If then is an abrupt completion, then + if (scope.engine->hasException) { + // Return RecjectPromise(promise, then.[[Value]] + ScopedValue thenValue {scope, scope.engine->catchException()}; + promise->d()->setState(Heap::PromiseObject::Rejected); + promise->d()->resolution.set(scope.engine, thenValue); + promise->d()->triggerRejectReactions(scope.engine); + } else { + // 10. Let thenAction be then.[[Value]] + if (!thenAction) { // 11. If IsCallable(thenAction) is false + promise->d()->setState(Heap::PromiseObject::Fulfilled); + promise->d()->resolution.set(scope.engine, resolution); + promise->d()->triggerFullfillReactions(scope.engine); + } else { + // 12. Perform EnqueueJob("PromiseJobs", PromiseResolveThenableJob, « promise, resolution, thenAction »). + scope.engine->getPromiseReactionHandler()->addResolveThenable(scope.engine, promise.getPointer(), resolutionObject, thenAction); + } + } + } return Encode::undefined(); } @@ -972,11 +1071,25 @@ ReturnedValue RejectWrapper::virtualCall(const FunctionObject *f, const Value *t value = Encode::undefined(); } - self->d()->alreadyResolved = true; - promise->d()->setState(Heap::PromiseObject::Rejected); - promise->d()->resolution.set(scope.engine, value); + if (!isPromise(value)) { + self->d()->alreadyResolved = true; + promise->d()->setState(Heap::PromiseObject::Rejected); + promise->d()->resolution.set(scope.engine, value); + + promise->d()->triggerRejectReactions(scope.engine); - promise->d()->triggerRejectReactions(scope.engine); + } else { + PromiseObject *promise = value->as<PromiseObject>(); + ScopedString thenName(scope, scope.engine->newIdentifier(QStringLiteral("catch"))); + + ScopedFunctionObject then(scope, promise->get(thenName)); + JSCallData jsCallData(scope, 2); + jsCallData->args[0] = *f; + jsCallData->args[1] = Encode::undefined(); + jsCallData->thisObject = value; + + then->call(jsCallData); + } return Encode::undefined(); } diff --git a/src/qml/jsruntime/qv4promiseobject_p.h b/src/qml/jsruntime/qv4promiseobject_p.h index bce59b19a7..8a3724e07d 100644 --- a/src/qml/jsruntime/qv4promiseobject_p.h +++ b/src/qml/jsruntime/qv4promiseobject_p.h @@ -62,6 +62,7 @@ struct PromiseCapability; namespace Promise { struct ReactionEvent; +struct ResolveThenableEvent; class ReactionHandler : public QObject { @@ -69,13 +70,15 @@ class ReactionHandler : public QObject public: ReactionHandler(QObject *parent = nullptr); - virtual ~ReactionHandler(); + virtual ~ReactionHandler() override; void addReaction(ExecutionEngine *e, const Value *reaction, const Value *value); + void addResolveThenable(ExecutionEngine *e, const PromiseObject *promise, const Object *thenable, const FunctionObject *then); protected: - void customEvent(QEvent *event); + void customEvent(QEvent *event) override; void executeReaction(ReactionEvent *event); + void executeResolveThenable(ResolveThenableEvent *event); }; } // Promise diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index 82b040232a..6c9760e472 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -1396,6 +1396,14 @@ UiObjectMember: FunctionDeclarationWithTypes; } break; ./ +UiObjectMember: GeneratorExpression; +/. + case $rule_number: { + auto node = new (pool) AST::UiSourceElement(sym(1).Node); + sym(1).Node = node; + } break; +./ + UiObjectMember: VariableStatement; /. case $rule_number: { diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index 1502298d14..39194068bf 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -3351,7 +3351,7 @@ public: SourceLocation firstSourceLocation() const override { - if (FunctionDeclaration *funDecl = cast<FunctionDeclaration *>(sourceElement)) + if (FunctionExpression *funDecl = sourceElement->asFunctionDefinition()) return funDecl->firstSourceLocation(); else if (VariableStatement *varStmt = cast<VariableStatement *>(sourceElement)) return varStmt->firstSourceLocation(); @@ -3361,7 +3361,7 @@ public: SourceLocation lastSourceLocation() const override { - if (FunctionDeclaration *funDecl = cast<FunctionDeclaration *>(sourceElement)) + if (FunctionExpression *funDecl = sourceElement->asFunctionDefinition()) return funDecl->lastSourceLocation(); else if (VariableStatement *varStmt = cast<VariableStatement *>(sourceElement)) return varStmt->lastSourceLocation(); diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index 613816e3f7..8a50b51b5d 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -76,6 +76,26 @@ int qmlTypeId(const char *uri, int versionMajor, int versionMinor, const char *q return QQmlMetaType::typeId(uri, versionMajor, versionMinor, qmlName); } +// From qqmlprivate.h +QObject *QQmlPrivate::RegisterSingletonFunctor::operator()(QQmlEngine *qeng, QJSEngine *) +{ + if (qeng->thread() != m_object->thread()) { + QQmlError error; + error.setDescription(QLatin1String("Registered object must live in the same thread as the engine it was registered with")); + QQmlEnginePrivate::get(qeng)->warning(qeng, error); + return nullptr; + } + if (alreadyCalled) { + QQmlError error; + error.setDescription(QLatin1String("Singleton registered by registerSingletonInstance must only be accessed from one engine")); + QQmlEnginePrivate::get(qeng)->warning(qeng, error); + return nullptr; + } + alreadyCalled = true; + qeng->setObjectOwnership(m_object, QQmlEngine::CppOwnership); + return m_object; +}; + /* This method is "over generalized" to allow us to (potentially) register more types of things in the future without adding exported symbols. diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h index 4f3bfb76b0..bfd1c88b28 100644 --- a/src/qml/qml/qqml.h +++ b/src/qml/qml/qqml.h @@ -667,6 +667,15 @@ inline int qmlRegisterSingletonType(const char *uri, int versionMajor, int versi return QQmlPrivate::qmlregister(QQmlPrivate::SingletonRegistration, &api); } +template<typename T> +inline auto qmlRegisterSingletonInstance(const char *uri, int versionMajor, int versionMinor, + const char *typeName, T *cppObject) -> typename std::enable_if<std::is_base_of<QObject, T>::value, int>::type +{ + QQmlPrivate::RegisterSingletonFunctor registrationFunctor; + registrationFunctor.m_object = cppObject; + return qmlRegisterSingletonType<T>(uri, versionMajor, versionMinor, typeName, registrationFunctor); +} + inline int qmlRegisterSingletonType(const QUrl &url, const char *uri, int versionMajor, int versionMinor, const char *qmlName) { if (url.isRelative()) { diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index 7ba5fc08a5..0580efe0d2 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -2454,17 +2454,20 @@ QJSValue QQmlEnginePrivate::singletonInstance<QJSValue>(const QQmlType &type) } else if (siinfo->qobjectCallback) { QObject *o = siinfo->qobjectCallback(q, q); if (!o) { - qFatal("qmlRegisterSingletonType(): \"%s\" is not available because the callback function returns a null pointer.", - qPrintable(QString::fromUtf8(type.typeName()))); + QQmlError error; + error.setMessageType(QtMsgType::QtCriticalMsg); + error.setDescription(QString::asprintf("qmlRegisterSingletonType(): \"%s\" is not available because the callback function returns a null pointer.", + qPrintable(QString::fromUtf8(type.typeName())))); + warning(error); + } else { + // if this object can use a property cache, create it now + QQmlData::ensurePropertyCache(q, o); } - // if this object can use a property cache, create it now - QQmlData::ensurePropertyCache(q, o); // even though the object is defined in C++, qmlContext(obj) and qmlEngine(obj) // should behave identically to QML singleton types. q->setContextForObject(o, new QQmlContext(q->rootContext(), q)); value = q->newQObject(o); singletonInstances.insert(type, value); - } else if (!siinfo->url.isEmpty()) { QQmlComponent component(q, siinfo->url, QQmlComponent::PreferSynchronous); QObject *o = component.beginCreate(q->rootContext()); diff --git a/src/qml/qml/qqmlerror.cpp b/src/qml/qml/qqmlerror.cpp index 739d5ce4cd..0b94ed3b49 100644 --- a/src/qml/qml/qqmlerror.cpp +++ b/src/qml/qml/qqmlerror.cpp @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qqmlerror.h" +#include "qqmlfile.h" #include "qqmlsourcecoordinate_p.h" #include <private/qqmljsdiagnosticmessage_p.h> @@ -307,8 +308,8 @@ QDebug operator<<(QDebug debug, const QQmlError &error) QUrl url = error.url(); - if (error.line() > 0 && url.scheme() == QLatin1String("file")) { - QString file = url.toLocalFile(); + if (error.line() > 0 && (url.scheme() == QLatin1String("file") || url.scheme() == QLatin1String("qrc"))) { + QString file = QQmlFile::urlToLocalFileOrQrc(url); QFile f(file); if (f.open(QIODevice::ReadOnly)) { QByteArray data = f.readAll(); diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index f4cdb45aff..7c9c0da01a 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -395,9 +395,18 @@ void findCompositeSingletons(const QQmlImportNamespace &set, QList<QQmlImports:: const QQmlDirComponents &components = import->qmlDirComponents; + const int importMajorVersion = import->majversion; + const int importMinorVersion = import->minversion; + auto shouldSkipSingleton = [importMajorVersion, importMinorVersion](int singletonMajorVersion, int singletonMinorVersion) -> bool { + return importMajorVersion != -1 && + (singletonMajorVersion > importMajorVersion || (singletonMajorVersion == importMajorVersion && singletonMinorVersion > importMinorVersion)); + }; + ConstIterator cend = components.constEnd(); for (ConstIterator cit = components.constBegin(); cit != cend; ++cit) { if (cit->singleton && excludeBaseUrl(import->url, cit->fileName, baseUrl.toString())) { + if (shouldSkipSingleton(cit->majorVersion, cit->minorVersion)) + continue; QQmlImports::CompositeSingletonReference ref; ref.typeName = cit->typeName; ref.prefix = set.prefix; @@ -408,7 +417,9 @@ void findCompositeSingletons(const QQmlImportNamespace &set, QList<QQmlImports:: } if (QQmlTypeModule *module = QQmlMetaType::typeModule(import->uri, import->majversion)) { - module->walkCompositeSingletons([&resultList, &set](const QQmlType &singleton) { + module->walkCompositeSingletons([&resultList, &set, &shouldSkipSingleton](const QQmlType &singleton) { + if (shouldSkipSingleton(singleton.majorVersion(), singleton.minorVersion())) + return; QQmlImports::CompositeSingletonReference ref; ref.typeName = singleton.elementName(); ref.prefix = set.prefix; @@ -1947,7 +1958,9 @@ void QQmlImportDatabase::setImportPathList(const QStringList &paths) if (qmlImportTrace()) qDebug().nospace() << "QQmlImportDatabase::setImportPathList: " << paths; - fileImportPath = paths; + fileImportPath.clear(); + for (auto it = paths.crbegin(); it != paths.crend(); ++it) + addImportPath(*it); // Our existing cached paths may have been invalidated clearDirCache(); diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index f21427ff69..2c641d3845 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -60,7 +60,7 @@ struct LockedData : private QQmlMetaTypeData }; Q_GLOBAL_STATIC(LockedData, metaTypeData) -Q_GLOBAL_STATIC_WITH_ARGS(QMutex, metaTypeDataLock, (QMutex::Recursive)) +Q_GLOBAL_STATIC(QRecursiveMutex, metaTypeDataLock) class QQmlMetaTypeDataPtr { @@ -804,7 +804,7 @@ QQmlType QQmlMetaType::typeForUrl(const QString &urlString, return QQmlType(); } -QMutex *QQmlMetaType::typeRegistrationLock() +QRecursiveMutex *QQmlMetaType::typeRegistrationLock() { return metaTypeDataLock(); } diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h index c2535a7fd5..6c2b0bb2a6 100644 --- a/src/qml/qml/qqmlmetatype_p.h +++ b/src/qml/qml/qqmlmetatype_p.h @@ -58,7 +58,7 @@ QT_BEGIN_NAMESPACE class QQmlTypeModule; -class QMutex; +class QRecursiveMutex; class QQmlError; namespace QV4 { class ExecutableCompilationUnit; } @@ -160,7 +160,7 @@ public: static void prependCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler); static void removeCachedUnitLookupFunction(QQmlPrivate::QmlUnitCacheLookupFunction handler); - static QMutex *typeRegistrationLock(); + static QRecursiveMutex *typeRegistrationLock(); static QString prettyTypeName(const QObject *object); diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index d5681b3449..f89608cd5d 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -57,6 +57,7 @@ #include <private/qqmldebugserviceinterfaces_p.h> #include <private/qqmlscriptdata_p.h> #include <private/qjsvalue_p.h> +#include <private/qv4generatorobject_p.h> #include <qtqml_tracepoints_p.h> @@ -1135,7 +1136,10 @@ void QQmlObjectCreator::setupFunctions() if (!property->isVMEFunction()) continue; - function = QV4::FunctionObject::createScriptFunction(qmlContext, runtimeFunction); + if (runtimeFunction->isGenerator()) + function = QV4::GeneratorFunction::create(qmlContext, runtimeFunction); + else + function = QV4::FunctionObject::createScriptFunction(qmlContext, runtimeFunction); _vmeMetaObject->setVmeMethod(property->coreIndex(), function); } } diff --git a/src/qml/qml/qqmlprivate.h b/src/qml/qml/qqmlprivate.h index eff78195d9..e6dd5e0b16 100644 --- a/src/qml/qml/qqmlprivate.h +++ b/src/qml/qml/qqmlprivate.h @@ -322,6 +322,13 @@ namespace QQmlPrivate int Q_QML_EXPORT qmlregister(RegistrationType, void *); void Q_QML_EXPORT qmlunregister(RegistrationType, quintptr); + struct Q_QML_EXPORT RegisterSingletonFunctor + { + QObject *operator()(QQmlEngine *, QJSEngine *); + + QObject *m_object; + bool alreadyCalled = false; + }; } QT_END_NAMESPACE diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index c8166695ba..5f57e0eca1 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -1216,10 +1216,18 @@ bool QQmlPropertyPrivate::write(QObject *object, if (propertyType == variantType && !isUrl && propertyType != qMetaTypeId<QList<QUrl>>() && !property.isQList()) { return property.writeProperty(object, const_cast<void *>(value.constData()), flags); } else if (property.isQObject()) { - QQmlMetaObject valMo = rawMetaObjectForType(enginePriv, variantType); + QVariant val = value; + int varType = variantType; + if (variantType == QMetaType::Nullptr) { + // This reflects the fact that you can assign a nullptr to a QObject pointer + // Without the change to QObjectStar, rawMetaObjectForType would not give us a QQmlMetaObject + varType = QMetaType::QObjectStar; + val = QVariant(QMetaType::QObjectStar, nullptr); + } + QQmlMetaObject valMo = rawMetaObjectForType(enginePriv, varType); if (valMo.isNull()) return false; - QObject *o = *static_cast<QObject *const *>(value.constData()); + QObject *o = *static_cast<QObject *const *>(val.constData()); QQmlMetaObject propMo = rawMetaObjectForType(enginePriv, propertyType); if (o) diff --git a/src/qml/qml/qqmlxmlhttprequest.cpp b/src/qml/qml/qqmlxmlhttprequest.cpp index b435f8ef66..4db0562c0e 100644 --- a/src/qml/qml/qqmlxmlhttprequest.cpp +++ b/src/qml/qml/qqmlxmlhttprequest.cpp @@ -1570,7 +1570,8 @@ void QQmlXMLHttpRequest::dispatchCallbackNow(Object *thisObj, bool done, bool er if (scope.engine->hasException) { QQmlError error = scope.engine->catchExceptionAsQmlError(); - QQmlEnginePrivate::warning(QQmlEnginePrivate::get(scope.engine->qmlEngine()), error); + QQmlEnginePrivate *qmlEnginePrivate = scope.engine->qmlEngine() ? QQmlEnginePrivate::get(scope.engine->qmlEngine()) : nullptr; + QQmlEnginePrivate::warning(qmlEnginePrivate, error); } }; @@ -1761,8 +1762,13 @@ ReturnedValue QQmlXMLHttpRequestCtor::method_open(const FunctionObject *b, const // Argument 1 - URL QUrl url = QUrl(argv[1].toQStringNoThrow()); - if (url.isRelative()) - url = scope.engine->callingQmlContext()->resolvedUrl(url); + if (url.isRelative()) { + QQmlContextData *qmlContextData = scope.engine->callingQmlContext(); + if (qmlContextData) + url = qmlContextData->resolvedUrl(url); + else + url = scope.engine->resolvedUrl(url.url()); + } bool async = true; // Argument 2 - async (optional) diff --git a/src/qml/types/qqmlbind.cpp b/src/qml/types/qqmlbind.cpp index 5067b6a02e..4b60108597 100644 --- a/src/qml/types/qqmlbind.cpp +++ b/src/qml/types/qqmlbind.cpp @@ -70,12 +70,8 @@ public: , delayed(false) , pendingEval(false) , restoreBinding(true) -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) , restoreValue(false) , restoreModeExplicit(false) -#else - , restoreValue(true) -#endif {} ~QQmlBindPrivate() { } @@ -93,9 +89,7 @@ public: bool pendingEval:1; bool restoreBinding:1; bool restoreValue:1; -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) bool restoreModeExplicit:1; -#endif void validate(QObject *binding) const; void clearPrev(); @@ -365,8 +359,7 @@ void QQmlBind::setDelayed(bool delayed) \li Binding.RestoreBindingOrValue The original value is always restored. \list - \warning The default value is Binding.RestoreBinding. This will change in - Qt 5.15 to Binding.RestoreBindingOrValue. + The default value is Binding.RestoreBinding. If you rely on any specific behavior regarding the restoration of plain values when bindings get disabled you should migrate to explicitly set the @@ -392,9 +385,7 @@ void QQmlBind::setRestoreMode(RestorationMode newMode) if (newMode != restoreMode()) { d->restoreValue = (newMode & RestoreValue); d->restoreBinding = (newMode & RestoreBinding); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) d->restoreModeExplicit = true; -#endif emit restoreModeChanged(); } } @@ -465,27 +456,11 @@ void QQmlBind::eval() Q_ASSERT(vmemo); vmemo->setVMEProperty(propPriv->core.coreIndex(), *d->v4Value.valueRef()); d->clearPrev(); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - } else if (!d->restoreModeExplicit) { - qmlWarning(this) - << "Not restoring previous value because restoreMode has not been set." - << "This behavior is deprecated." - << "In Qt < 5.15 the default is Binding.RestoreBinding." - << "In Qt >= 5.15 the default is Binding.RestoreBindingOrValue."; -#endif } } else if (d->prevIsVariant) { if (d->restoreValue) { d->prop.write(d->prevValue); d->clearPrev(); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - } else if (!d->restoreModeExplicit) { - qmlWarning(this) - << "Not restoring previous value because restoreMode has not been set." - << "This behavior is deprecated." - << "In Qt < 5.15 the default is Binding.RestoreBinding." - << "In Qt >= 5.15 the default is Binding.RestoreBindingOrValue."; -#endif } } return; |