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
25QT_BEGIN_NAMESPACE
26
27Q_TRACE_POINT(qtqml, QQmlHandlingSignal_entry, const QQmlEngine *engine, const QString &function,
28 const QString &fileName, int line, int column)
29Q_TRACE_POINT(qtqml, QQmlHandlingSignal_exit)
30
31QQmlBoundSignalExpression::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 &parameterString)
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
70QQmlBoundSignalExpression::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
123void 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
133QQmlBoundSignalExpression::~QQmlBoundSignalExpression()
134{
135}
136
137QString QQmlBoundSignalExpression::expressionIdentifier() const
138{
139 QQmlSourceLocation loc = sourceLocation();
140 return loc.sourceFile + QLatin1Char(':') + QString::number(loc.line);
141}
142
143void QQmlBoundSignalExpression::expressionChanged()
144{
145 // bound signals do not notify on change.
146}
147
148QString 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.
157void 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*/
212QQmlBoundSignal::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
230QQmlBoundSignal::~QQmlBoundSignal()
231{
232 removeFromObject();
233}
234
235void 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
248void 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*/
261QQmlBoundSignalExpression *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*/
271void 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 */
285void QQmlBoundSignal::setEnabled(bool enabled)
286{
287 if (m_enabled == enabled)
288 return;
289
290 m_enabled = enabled;
291}
292
293void 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
323QQmlPropertyObserver::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
332QT_END_NAMESPACE
333

source code of qtdeclarative/src/qml/qml/qqmlboundsignal.cpp