1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qqmldelayedcallqueue_p.h" |
5 | #include <private/qqmlengine_p.h> |
6 | #include <private/qqmljavascriptexpression_p.h> |
7 | #include <private/qv4value_p.h> |
8 | #include <private/qv4jscall_p.h> |
9 | #include <private/qv4qobjectwrapper_p.h> |
10 | #include <private/qv4qmlcontext_p.h> |
11 | |
12 | #include <QQmlError> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | // |
17 | // struct QQmlDelayedCallQueue::DelayedFunctionCall |
18 | // |
19 | |
20 | void QQmlDelayedCallQueue::DelayedFunctionCall::execute(QV4::ExecutionEngine *engine) const |
21 | { |
22 | if (!m_guarded || |
23 | (!m_objectGuard.isNull() && |
24 | !QQmlData::wasDeleted(object: m_objectGuard) && |
25 | QQmlData::get(object: m_objectGuard) && |
26 | !QQmlData::get(object: m_objectGuard)->isQueuedForDeletion)) { |
27 | |
28 | QV4::Scope scope(engine); |
29 | |
30 | QV4::ArrayObject *array = m_args.as<QV4::ArrayObject>(); |
31 | const QV4::FunctionObject *callback = m_function.as<QV4::FunctionObject>(); |
32 | Q_ASSERT(callback); |
33 | const int argCount = array ? array->getLength() : 0; |
34 | QV4::JSCallArguments jsCallData(scope, argCount); |
35 | *jsCallData.thisObject = QV4::Encode::undefined(); |
36 | |
37 | for (int i = 0; i < argCount; i++) { |
38 | jsCallData.args[i] = array->get(idx: i); |
39 | } |
40 | |
41 | callback->call(data: jsCallData); |
42 | |
43 | if (scope.hasException()) { |
44 | QQmlError error = scope.engine->catchExceptionAsQmlError(); |
45 | error.setDescription(error.description() + QLatin1String(" (exception occurred during delayed function evaluation)" )); |
46 | QQmlEnginePrivate::warning(QQmlEnginePrivate::get(e: scope.engine->qmlEngine()), error); |
47 | } |
48 | } |
49 | } |
50 | |
51 | // |
52 | // class QQmlDelayedCallQueue |
53 | // |
54 | |
55 | QQmlDelayedCallQueue::QQmlDelayedCallQueue() |
56 | : QObject(nullptr), m_engine(nullptr), m_callbackOutstanding(false) |
57 | { |
58 | } |
59 | |
60 | QQmlDelayedCallQueue::~QQmlDelayedCallQueue() |
61 | { |
62 | } |
63 | |
64 | void QQmlDelayedCallQueue::init(QV4::ExecutionEngine* engine) |
65 | { |
66 | m_engine = engine; |
67 | |
68 | const QMetaObject &metaObject = QQmlDelayedCallQueue::staticMetaObject; |
69 | int methodIndex = metaObject.indexOfSlot(slot: "ticked()" ); |
70 | m_tickedMethod = metaObject.method(index: methodIndex); |
71 | } |
72 | |
73 | QV4::ReturnedValue QQmlDelayedCallQueue::addUniquelyAndExecuteLater(QV4::ExecutionEngine *engine, QQmlV4Function *args) |
74 | { |
75 | QQmlDelayedCallQueue *self = engine->delayedCallQueue(); |
76 | |
77 | QV4::Scope scope(engine); |
78 | if (args->length() == 0) |
79 | THROW_GENERIC_ERROR("Qt.callLater: no arguments given" ); |
80 | |
81 | QV4::ScopedValue firstArgument(scope, (*args)[0]); |
82 | |
83 | const QV4::FunctionObject *func = firstArgument->as<QV4::FunctionObject>(); |
84 | |
85 | if (!func) |
86 | THROW_GENERIC_ERROR("Qt.callLater: first argument not a function or signal" ); |
87 | |
88 | QPair<QObject *, int> functionData = QV4::QObjectMethod::extractQtMethod(function: func); |
89 | |
90 | QVector<DelayedFunctionCall>::Iterator iter; |
91 | if (functionData.second != -1) { |
92 | // This is a QObject function wrapper |
93 | iter = self->m_delayedFunctionCalls.begin(); |
94 | while (iter != self->m_delayedFunctionCalls.end()) { |
95 | DelayedFunctionCall& dfc = *iter; |
96 | QPair<QObject *, int> storedFunctionData = QV4::QObjectMethod::extractQtMethod(function: dfc.m_function.as<QV4::FunctionObject>()); |
97 | if (storedFunctionData == functionData) { |
98 | break; // Already stored! |
99 | } |
100 | ++iter; |
101 | } |
102 | } else { |
103 | // This is a JavaScript function (dynamic slot on VMEMO) |
104 | iter = self->m_delayedFunctionCalls.begin(); |
105 | while (iter != self->m_delayedFunctionCalls.end()) { |
106 | DelayedFunctionCall& dfc = *iter; |
107 | if (firstArgument->asReturnedValue() == dfc.m_function.value()) { |
108 | break; // Already stored! |
109 | } |
110 | ++iter; |
111 | } |
112 | } |
113 | |
114 | const bool functionAlreadyStored = (iter != self->m_delayedFunctionCalls.end()); |
115 | if (functionAlreadyStored) { |
116 | DelayedFunctionCall dfc = *iter; |
117 | self->m_delayedFunctionCalls.erase(pos: iter); |
118 | self->m_delayedFunctionCalls.append(t: dfc); |
119 | } else { |
120 | self->m_delayedFunctionCalls.append(t: QV4::PersistentValue(engine, firstArgument)); |
121 | } |
122 | |
123 | DelayedFunctionCall& dfc = self->m_delayedFunctionCalls.last(); |
124 | if (dfc.m_objectGuard.isNull()) { |
125 | if (functionData.second != -1) { |
126 | // if it's a qobject function wrapper, guard against qobject deletion |
127 | dfc.m_objectGuard = QQmlGuard<QObject>(functionData.first); |
128 | dfc.m_guarded = true; |
129 | } else if (func->scope()->type == QV4::Heap::ExecutionContext::Type_QmlContext) { |
130 | QV4::QmlContext::Data *g = static_cast<QV4::QmlContext::Data *>(func->scope()); |
131 | Q_ASSERT(g->qml()->scopeObject); |
132 | dfc.m_objectGuard = QQmlGuard<QObject>(g->qml()->scopeObject); |
133 | dfc.m_guarded = true; |
134 | } |
135 | } |
136 | self->storeAnyArguments(dfc, args, offset: 1, engine); |
137 | |
138 | if (!self->m_callbackOutstanding) { |
139 | self->m_tickedMethod.invoke(obj: self, c: Qt::QueuedConnection); |
140 | self->m_callbackOutstanding = true; |
141 | } |
142 | return QV4::Encode::undefined(); |
143 | } |
144 | |
145 | void QQmlDelayedCallQueue::storeAnyArguments(DelayedFunctionCall &dfc, QQmlV4Function *args, int offset, QV4::ExecutionEngine *engine) |
146 | { |
147 | const int length = args->length() - offset; |
148 | if (length == 0) { |
149 | dfc.m_args.clear(); |
150 | return; |
151 | } |
152 | QV4::Scope scope(engine); |
153 | QV4::ScopedArrayObject array(scope, engine->newArrayObject(count: length)); |
154 | uint i = 0; |
155 | for (int j = offset, ej = args->length(); j < ej; ++i, ++j) |
156 | array->put(idx: i, v: (*args)[j]); |
157 | dfc.m_args.set(engine, value: array); |
158 | } |
159 | |
160 | void QQmlDelayedCallQueue::executeAllExpired_Later() |
161 | { |
162 | // Make a local copy of the list and clear m_delayedFunctionCalls |
163 | // This ensures correct behavior in the case of recursive calls to Qt.callLater() |
164 | QVector<DelayedFunctionCall> delayedCalls = m_delayedFunctionCalls; |
165 | m_delayedFunctionCalls.clear(); |
166 | |
167 | QVector<DelayedFunctionCall>::Iterator iter = delayedCalls.begin(); |
168 | while (iter != delayedCalls.end()) { |
169 | DelayedFunctionCall& dfc = *iter; |
170 | dfc.execute(engine: m_engine); |
171 | ++iter; |
172 | } |
173 | } |
174 | |
175 | void QQmlDelayedCallQueue::ticked() |
176 | { |
177 | m_callbackOutstanding = false; |
178 | executeAllExpired_Later(); |
179 | } |
180 | |
181 | QT_END_NAMESPACE |
182 | |
183 | #include "moc_qqmldelayedcallqueue_p.cpp" |
184 | |