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 "qqmlboundsignal_p.h" |
5 | |
6 | #include <private/qmetaobject_p.h> |
7 | #include <private/qmetaobjectbuilder_p.h> |
8 | #include "qqmlengine_p.h" |
9 | #include "qqmlglobal_p.h" |
10 | #include <private/qqmlprofiler_p.h> |
11 | #include <private/qqmldebugconnector_p.h> |
12 | #include <private/qqmldebugserviceinterfaces_p.h> |
13 | #include "qqmlinfo.h" |
14 | |
15 | #include <private/qjsvalue_p.h> |
16 | #include <private/qv4value_p.h> |
17 | #include <private/qv4jscall_p.h> |
18 | #include <private/qv4qobjectwrapper_p.h> |
19 | #include <private/qv4qmlcontext_p.h> |
20 | |
21 | #include <QtCore/qdebug.h> |
22 | |
23 | #include <qtqml_tracepoints_p.h> |
24 | |
25 | QT_BEGIN_NAMESPACE |
26 | |
27 | Q_TRACE_POINT(qtqml, QQmlHandlingSignal_entry, const QQmlEngine *engine, const QString &function, |
28 | const QString &fileName, int line, int column) |
29 | Q_TRACE_POINT(qtqml, QQmlHandlingSignal_exit) |
30 | |
31 | QQmlBoundSignalExpression::QQmlBoundSignalExpression(const QObject *target, int index, const QQmlRefPointer<QQmlContextData> &ctxt, QObject *scope, |
32 | const QString &expression, const QString &fileName, quint16 line, quint16 column, |
33 | const QString &handlerName, const QString ¶meterString) |
34 | : QQmlJavaScriptExpression(), |
35 | m_index(index), |
36 | m_target(target) |
37 | { |
38 | init(ctxt, scope); |
39 | |
40 | QV4::ExecutionEngine *v4 = engine()->handle(); |
41 | |
42 | QString function; |
43 | |
44 | // Add some leading whitespace to account for the binding's column offset. |
45 | // It's 2 off because a, we start counting at 1 and b, the '(' below is not counted. |
46 | function += QString(qMax(a: column, b: (quint16)2) - 2, QChar(QChar::Space)) |
47 | + QLatin1String("(function " ) + handlerName + QLatin1Char('('); |
48 | |
49 | if (parameterString.isEmpty()) { |
50 | QString error; |
51 | //TODO: look at using the property cache here (as in the compiler) |
52 | // for further optimization |
53 | QMetaMethod signal = QMetaObjectPrivate::signal(m: m_target->metaObject(), signal_index: m_index); |
54 | function += QQmlPropertyCache::signalParameterStringForJS(engine: v4, parameterNameList: signal.parameterNames(), errorString: &error); |
55 | |
56 | if (!error.isEmpty()) { |
57 | qmlWarning(me: scopeObject()) << error; |
58 | return; |
59 | } |
60 | } else |
61 | function += parameterString; |
62 | |
63 | function += QLatin1String(") { " ) + expression + QLatin1String(" })" ); |
64 | QV4::Scope valueScope(v4); |
65 | QV4::ScopedFunctionObject f(valueScope, evalFunction(ctxt: context(), scope: scopeObject(), code: function, filename: fileName, line)); |
66 | QV4::ScopedContext context(valueScope, f->scope()); |
67 | setupFunction(qmlContext: context, f: f->function()); |
68 | } |
69 | |
70 | QQmlBoundSignalExpression::QQmlBoundSignalExpression(const QObject *target, int index, const QQmlRefPointer<QQmlContextData> &ctxt, |
71 | QObject *scopeObject, QV4::Function *function, QV4::ExecutionContext *scope) |
72 | : QQmlJavaScriptExpression(), |
73 | m_index(index), |
74 | m_target(target) |
75 | { |
76 | // It's important to call init first, because m_index gets remapped in case of cloned signals. |
77 | init(ctxt, scope: scopeObject); |
78 | |
79 | QV4::ExecutionEngine *engine = ctxt->engine()->handle(); |
80 | |
81 | if (!function->isClosureWrapper()) { |
82 | QList<QByteArray> signalParameters = QMetaObjectPrivate::signal(m: m_target->metaObject(), signal_index: m_index).parameterNames(); |
83 | if (!signalParameters.isEmpty()) { |
84 | QString error; |
85 | QQmlPropertyCache::signalParameterStringForJS(engine, parameterNameList: signalParameters, errorString: &error); |
86 | if (!error.isEmpty()) { |
87 | qmlWarning(me: scopeObject) << error; |
88 | return; |
89 | } |
90 | function->updateInternalClass(engine, parameters: signalParameters); |
91 | } |
92 | } |
93 | |
94 | QV4::Scope valueScope(engine); |
95 | QV4::Scoped<QV4::QmlContext> qmlContext(valueScope, scope); |
96 | if (!qmlContext) |
97 | qmlContext = QV4::QmlContext::create(parent: engine->rootContext(), context: ctxt, scopeObject); |
98 | if (auto closure = function->nestedFunction()) { |
99 | // If the function is marked as having a nested function, then the user wrote: |
100 | // onSomeSignal: function() { /*....*/ } |
101 | // So take that nested function: |
102 | setupFunction(qmlContext, f: closure); |
103 | } else { |
104 | setupFunction(qmlContext, f: function); |
105 | |
106 | // If it's a closure wrapper but we cannot directly access the nested function |
107 | // we need to run the outer function to get the nested one. |
108 | if (function->isClosureWrapper()) { |
109 | bool isUndefined = false; |
110 | QV4::ScopedFunctionObject result( |
111 | valueScope, QQmlJavaScriptExpression::evaluate(isUndefined: &isUndefined)); |
112 | |
113 | Q_ASSERT(!isUndefined); |
114 | Q_ASSERT(result->function()); |
115 | Q_ASSERT(result->function()->compilationUnit == function->compilationUnit); |
116 | |
117 | QV4::Scoped<QV4::ExecutionContext> callContext(valueScope, result->scope()); |
118 | setupFunction(qmlContext: callContext, f: result->function()); |
119 | } |
120 | } |
121 | } |
122 | |
123 | void QQmlBoundSignalExpression::init(const QQmlRefPointer<QQmlContextData> &ctxt, QObject *scope) |
124 | { |
125 | setNotifyOnValueChanged(false); |
126 | setContext(ctxt); |
127 | setScopeObject(scope); |
128 | |
129 | Q_ASSERT(m_target && m_index > -1); |
130 | m_index = QQmlPropertyCache::originalClone(m_target, index: m_index); |
131 | } |
132 | |
133 | QQmlBoundSignalExpression::~QQmlBoundSignalExpression() |
134 | { |
135 | } |
136 | |
137 | QString QQmlBoundSignalExpression::expressionIdentifier() const |
138 | { |
139 | QQmlSourceLocation loc = sourceLocation(); |
140 | return loc.sourceFile + QLatin1Char(':') + QString::number(loc.line); |
141 | } |
142 | |
143 | void QQmlBoundSignalExpression::expressionChanged() |
144 | { |
145 | // bound signals do not notify on change. |
146 | } |
147 | |
148 | QString QQmlBoundSignalExpression::expression() const |
149 | { |
150 | if (expressionFunctionValid()) |
151 | return QStringLiteral("function() { [native code] }" ); |
152 | return QString(); |
153 | } |
154 | |
155 | // Parts of this function mirror code in QQmlExpressionPrivate::value() and v4Value(). |
156 | // Changes made here may need to be made there and vice versa. |
157 | void QQmlBoundSignalExpression::evaluate(void **a) |
158 | { |
159 | if (!expressionFunctionValid()) |
160 | return; |
161 | |
162 | QQmlEngine *qmlengine = engine(); |
163 | |
164 | // If there is no engine, we have no way to evaluate anything. |
165 | // This can happen on destruction. |
166 | if (!qmlengine) |
167 | return; |
168 | |
169 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: qmlengine); |
170 | QV4::ExecutionEngine *v4 = qmlengine->handle(); |
171 | QV4::Scope scope(v4); |
172 | |
173 | ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation. |
174 | |
175 | if (a) { |
176 | //TODO: lookup via signal index rather than method index as an optimization |
177 | const QMetaObject *targetMeta = m_target->metaObject(); |
178 | const QMetaMethod metaMethod = targetMeta->method( |
179 | index: QMetaObjectPrivate::signal(m: targetMeta, signal_index: m_index).methodIndex()); |
180 | |
181 | int argCount = metaMethod.parameterCount(); |
182 | QQmlMetaObject::ArgTypeStorage storage; |
183 | storage.reserve(sz: argCount + 1); |
184 | storage.append(t: QMetaType()); // We're not interested in the return value |
185 | for (int i = 0; i < argCount; ++i) { |
186 | const QMetaType type = metaMethod.parameterMetaType(index: i); |
187 | if (!type.isValid()) |
188 | argCount = 0; |
189 | else if (type.flags().testFlag(flag: QMetaType::IsEnumeration)) |
190 | storage.append(t: type.underlyingType()); |
191 | else |
192 | storage.append(t: type); |
193 | } |
194 | |
195 | QQmlJavaScriptExpression::evaluate(a, types: storage.constData(), argc: argCount); |
196 | } else { |
197 | void *ignoredResult = nullptr; |
198 | QMetaType invalidType; |
199 | QQmlJavaScriptExpression::evaluate(a: &ignoredResult, types: &invalidType, argc: 0); |
200 | } |
201 | |
202 | ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete. |
203 | } |
204 | |
205 | //////////////////////////////////////////////////////////////////////// |
206 | |
207 | |
208 | /*! \internal |
209 | \a signal MUST be in the signal index range (see QObjectPrivate::signalIndex()). |
210 | This is different from QMetaMethod::methodIndex(). |
211 | */ |
212 | QQmlBoundSignal::QQmlBoundSignal(QObject *target, int signal, QObject *owner, |
213 | QQmlEngine *engine) |
214 | : QQmlNotifierEndpoint(QQmlNotifierEndpoint::QQmlBoundSignal), |
215 | m_prevSignal(nullptr), m_nextSignal(nullptr), |
216 | m_enabled(true) |
217 | { |
218 | addToObject(owner); |
219 | |
220 | /* |
221 | If this is a cloned method, connect to the 'original'. For example, |
222 | for the signal 'void aSignal(int parameter = 0)', if the method |
223 | index refers to 'aSignal()', get the index of 'aSignal(int)'. |
224 | This ensures that 'parameter' will be available from QML. |
225 | */ |
226 | signal = QQmlPropertyCache::originalClone(target, index: signal); |
227 | QQmlNotifierEndpoint::connect(source: target, sourceSignal: signal, engine); |
228 | } |
229 | |
230 | QQmlBoundSignal::~QQmlBoundSignal() |
231 | { |
232 | removeFromObject(); |
233 | } |
234 | |
235 | void QQmlBoundSignal::addToObject(QObject *obj) |
236 | { |
237 | Q_ASSERT(!m_prevSignal); |
238 | Q_ASSERT(obj); |
239 | |
240 | QQmlData *data = QQmlData::get(object: obj, create: true); |
241 | |
242 | m_nextSignal = data->signalHandlers; |
243 | if (m_nextSignal) m_nextSignal->m_prevSignal = &m_nextSignal; |
244 | m_prevSignal = &data->signalHandlers; |
245 | data->signalHandlers = this; |
246 | } |
247 | |
248 | void QQmlBoundSignal::removeFromObject() |
249 | { |
250 | if (m_prevSignal) { |
251 | *m_prevSignal = m_nextSignal; |
252 | if (m_nextSignal) m_nextSignal->m_prevSignal = m_prevSignal; |
253 | m_prevSignal = nullptr; |
254 | m_nextSignal = nullptr; |
255 | } |
256 | } |
257 | |
258 | /*! |
259 | Returns the signal expression. |
260 | */ |
261 | QQmlBoundSignalExpression *QQmlBoundSignal::expression() const |
262 | { |
263 | return m_expression.data(); |
264 | } |
265 | |
266 | /*! |
267 | Sets the signal expression to \a e. |
268 | |
269 | The QQmlBoundSignal instance takes ownership of \a e (and does not add a reference). |
270 | */ |
271 | void QQmlBoundSignal::takeExpression(QQmlBoundSignalExpression *e) |
272 | { |
273 | m_expression.adopt(other: e); |
274 | if (m_expression) |
275 | m_expression->setNotifyOnValueChanged(false); |
276 | } |
277 | |
278 | /*! |
279 | This property holds whether the item will emit signals. |
280 | |
281 | The QQmlBoundSignal callback will only emit a signal if this property is set to true. |
282 | |
283 | By default, this property is true. |
284 | */ |
285 | void QQmlBoundSignal::setEnabled(bool enabled) |
286 | { |
287 | if (m_enabled == enabled) |
288 | return; |
289 | |
290 | m_enabled = enabled; |
291 | } |
292 | |
293 | void QQmlBoundSignal_callback(QQmlNotifierEndpoint *e, void **a) |
294 | { |
295 | QQmlBoundSignal *s = static_cast<QQmlBoundSignal*>(e); |
296 | |
297 | if (!s->m_expression || !s->m_enabled) |
298 | return; |
299 | |
300 | QV4DebugService *service = QQmlDebugConnector::service<QV4DebugService>(); |
301 | if (service) |
302 | service->signalEmitted(signal: QString::fromUtf8(ba: QMetaObjectPrivate::signal( |
303 | m: s->m_expression->target()->metaObject(), |
304 | signal_index: s->signalIndex()).methodSignature())); |
305 | |
306 | QQmlEngine *engine; |
307 | if (s->m_expression && (engine = s->m_expression->engine())) { |
308 | Q_TRACE_SCOPE(QQmlHandlingSignal, engine, |
309 | s->m_expression->function() ? s->m_expression->function()->name()->toQString() : QString(), |
310 | s->m_expression->sourceLocation().sourceFile, s->m_expression->sourceLocation().line, |
311 | s->m_expression->sourceLocation().column); |
312 | QQmlHandlingSignalProfiler prof(QQmlEnginePrivate::get(e: engine)->profiler, |
313 | s->m_expression.data()); |
314 | s->m_expression->evaluate(a); |
315 | if (s->m_expression && s->m_expression->hasError()) { |
316 | QQmlEnginePrivate::warning(engine, s->m_expression->error(engine)); |
317 | } |
318 | } |
319 | } |
320 | |
321 | //////////////////////////////////////////////////////////////////////// |
322 | |
323 | QQmlPropertyObserver::QQmlPropertyObserver(QQmlBoundSignalExpression *expr) |
324 | : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { |
325 | auto This = static_cast<QQmlPropertyObserver*>(self); |
326 | This->expression->evaluate(a: nullptr); |
327 | }) |
328 | { |
329 | expression.adopt(other: expr); |
330 | } |
331 | |
332 | QT_END_NAMESPACE |
333 | |