Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlboundsignal.cpp
Go to the documentation of this file.
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
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
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(column, (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_target->metaObject(), m_index);
54 function += QQmlPropertyCache::signalParameterStringForJS(signal.parameterNames(), &error);
55
56 if (!error.isEmpty()) {
57 qmlWarning(scopeObject()) << error;
58 return;
59 }
60 } else
61 function += parameterString;
62
63 function += QLatin1String(") { ") + expression + QLatin1String(" })");
64 QV4::Scope valueScope(v4);
65 QV4::Scoped<QV4::JavaScriptFunctionObject> f(
66 valueScope, evalFunction(context(), scopeObject(), function, fileName, line));
67 QV4::ScopedContext context(valueScope, f->scope());
68 setupFunction(context, f->function());
69}
70
71QQmlBoundSignalExpression::QQmlBoundSignalExpression(const QObject *target, int index, const QQmlRefPointer<QQmlContextData> &ctxt,
72 QObject *scopeObject, QV4::Function *function, QV4::ExecutionContext *scope)
73 : QQmlJavaScriptExpression(),
74 m_index(index),
75 m_target(target)
76{
77 // It's important to call init first, because m_index gets remapped in case of cloned signals.
78 init(ctxt, scopeObject);
80 QV4::ExecutionEngine *engine = ctxt->engine()->handle();
81
82 if (!function->isClosureWrapper()) {
83 QList<QByteArray> signalParameters = QMetaObjectPrivate::signal(m_target->metaObject(), m_index).parameterNames();
84 if (!signalParameters.isEmpty()) {
85 QString error;
86 QQmlPropertyCache::signalParameterStringForJS(signalParameters, &error);
87 if (!error.isEmpty()) {
88 qmlWarning(scopeObject) << error;
89 return;
90 }
91 function->updateInternalClass(engine, signalParameters);
92 }
93 }
94
95 QV4::Scope valueScope(engine);
96 QV4::Scoped<QV4::QmlContext> qmlContext(valueScope, scope);
97 if (!qmlContext)
98 qmlContext = QV4::QmlContext::create(engine->rootContext(), ctxt, scopeObject);
99 if (auto closure = function->nestedFunction()) {
100 // If the function is marked as having a nested function, then the user wrote:
101 // onSomeSignal: function() { /*....*/ }
102 // So take that nested function:
103 setupFunction(qmlContext, closure);
104 } else {
105 setupFunction(qmlContext, function);
106
107 // If it's a closure wrapper but we cannot directly access the nested function
108 // we need to run the outer function to get the nested one.
109 if (function->isClosureWrapper()) {
110 bool isUndefined = false;
111 QV4::Scoped<QV4::JavaScriptFunctionObject> result(
112 valueScope, QQmlJavaScriptExpression::evaluate(&isUndefined));
113
114 Q_ASSERT(!isUndefined);
115 Q_ASSERT(result->function());
116 Q_ASSERT(result->function()->compilationUnit == function->compilationUnit);
117
118 QV4::Scoped<QV4::ExecutionContext> callContext(valueScope, result->scope());
119 setupFunction(callContext, result->function());
120 }
121 }
122}
123
124void QQmlBoundSignalExpression::init(const QQmlRefPointer<QQmlContextData> &ctxt, QObject *scope)
125{
126 setNotifyOnValueChanged(false);
127 setContext(ctxt);
128 setScopeObject(scope);
129
130 Q_ASSERT(m_target && m_index > -1);
131 m_index = QQmlPropertyCache::originalClone(m_target, m_index);
132}
133
134QQmlBoundSignalExpression::~QQmlBoundSignalExpression()
135{
136}
137
138QString QQmlBoundSignalExpression::expressionIdentifier() const
139{
140 QQmlSourceLocation loc = sourceLocation();
141 return loc.sourceFile + QLatin1Char(':') + QString::number(loc.line);
142}
143
144void QQmlBoundSignalExpression::expressionChanged()
145{
146 // bound signals do not notify on change.
147}
148
149QString QQmlBoundSignalExpression::expression() const
150{
151 if (expressionFunctionValid())
152 return QStringLiteral("function() { [native code] }");
153 return QString();
154}
155
156// Parts of this function mirror code in QQmlExpressionPrivate::value() and v4Value().
157// Changes made here may need to be made there and vice versa.
158void QQmlBoundSignalExpression::evaluate(void **a)
159{
160 if (!expressionFunctionValid())
161 return;
162
163 QQmlEngine *qmlengine = engine();
164
165 // If there is no engine, we have no way to evaluate anything.
166 // This can happen on destruction.
167 if (!qmlengine)
168 return;
169
170 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlengine);
171 QV4::ExecutionEngine *v4 = qmlengine->handle();
172 QV4::Scope scope(v4);
173
174 ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation.
175
176 if (a) {
177 //TODO: lookup via signal index rather than method index as an optimization
178 const QMetaObject *targetMeta = m_target->metaObject();
179 const QMetaMethod metaMethod = targetMeta->method(
180 QMetaObjectPrivate::signal(targetMeta, m_index).methodIndex());
181
182 int argCount = metaMethod.parameterCount();
183 QQmlMetaObject::ArgTypeStorage<9> storage;
184 storage.reserve(argCount + 1);
185 storage.append(QMetaType()); // We're not interested in the return value
186 for (int i = 0; i < argCount; ++i) {
187 const QMetaType type = metaMethod.parameterMetaType(i);
188 if (!type.isValid())
189 argCount = 0;
190 else if (type.flags().testFlag(QMetaType::IsEnumeration))
191 storage.append(type.underlyingType());
192 else
193 storage.append(type);
194 }
195
196 QQmlJavaScriptExpression::evaluate(a, storage.constData(), argCount);
197 } else {
198 void *ignoredResult = nullptr;
199 QMetaType invalidType;
200 QQmlJavaScriptExpression::evaluate(&ignoredResult, &invalidType, 0);
201 }
202
203 ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete.
204}
205
206////////////////////////////////////////////////////////////////////////
207
208
209/*! \internal
210 \a signal MUST be in the signal index range (see QObjectPrivate::signalIndex()).
211 This is different from QMetaMethod::methodIndex().
212*/
213QQmlBoundSignal::QQmlBoundSignal(QObject *target, int signal, QObject *owner,
214 QQmlEngine *engine)
215 : QQmlNotifierEndpoint(QQmlNotifierEndpoint::QQmlBoundSignal),
216 m_prevSignal(nullptr), m_nextSignal(nullptr),
217 m_enabled(true)
218{
219 addToObject(owner);
220
221 /*
222 If this is a cloned method, connect to the 'original'. For example,
223 for the signal 'void aSignal(int parameter = 0)', if the method
224 index refers to 'aSignal()', get the index of 'aSignal(int)'.
225 This ensures that 'parameter' will be available from QML.
226 */
227 signal = QQmlPropertyCache::originalClone(target, signal);
228 QQmlNotifierEndpoint::connect(target, signal, engine);
229}
230
231QQmlBoundSignal::~QQmlBoundSignal()
232{
233 removeFromObject();
234}
235
236void QQmlBoundSignal::addToObject(QObject *obj)
237{
238 Q_ASSERT(!m_prevSignal);
239 Q_ASSERT(obj);
240
241 QQmlData *data = QQmlData::get(obj, true);
242
243 m_nextSignal = data->signalHandlers;
244 if (m_nextSignal) m_nextSignal->m_prevSignal = &m_nextSignal;
245 m_prevSignal = &data->signalHandlers;
246 data->signalHandlers = this;
247}
248
249void QQmlBoundSignal::removeFromObject()
250{
251 if (m_prevSignal) {
252 *m_prevSignal = m_nextSignal;
253 if (m_nextSignal) m_nextSignal->m_prevSignal = m_prevSignal;
254 m_prevSignal = nullptr;
255 m_nextSignal = nullptr;
256 }
257}
258
259/*!
260 Returns the signal expression.
261*/
262QQmlBoundSignalExpression *QQmlBoundSignal::expression() const
263{
264 return m_expression.data();
265}
266
267/*!
268 Sets the signal expression to \a e.
269
270 The QQmlBoundSignal instance takes ownership of \a e (and does not add a reference).
271*/
272void QQmlBoundSignal::takeExpression(QQmlBoundSignalExpression *e)
273{
274 m_expression.adopt(e);
275 if (m_expression)
276 m_expression->setNotifyOnValueChanged(false);
277}
278
279/*!
280 This property holds whether the item will emit signals.
281
282 The QQmlBoundSignal callback will only emit a signal if this property is set to true.
283
284 By default, this property is true.
285 */
286void QQmlBoundSignal::setEnabled(bool enabled)
287{
288 if (m_enabled == enabled)
289 return;
290
291 m_enabled = enabled;
292}
293
294void QQmlBoundSignal_callback(QQmlNotifierEndpoint *e, void **a)
295{
296 QQmlBoundSignal *s = static_cast<QQmlBoundSignal*>(e);
297
298 if (!s->m_expression || !s->m_enabled)
299 return;
300
301 QV4DebugService *service = QQmlDebugConnector::service<QV4DebugService>();
302 if (service)
303 service->signalEmitted(QString::fromUtf8(QMetaObjectPrivate::signal(
304 s->m_expression->target()->metaObject(),
305 s->signalIndex()).methodSignature()));
306
307 QQmlEngine *engine;
308 if (s->m_expression && (engine = s->m_expression->engine())) {
309 Q_TRACE_SCOPE(QQmlHandlingSignal, engine,
310 s->m_expression->function() ? s->m_expression->function()->name()->toQString() : QString(),
311 s->m_expression->sourceLocation().sourceFile, s->m_expression->sourceLocation().line,
312 s->m_expression->sourceLocation().column);
313 QQmlHandlingSignalProfiler prof(QQmlEnginePrivate::get(engine)->profiler,
314 s->m_expression.data());
315 s->m_expression->evaluate(a);
316 if (s->m_expression && s->m_expression->hasError()) {
317 QQmlEnginePrivate::warning(engine, s->m_expression->error(engine));
318 }
319 }
320}
321
322////////////////////////////////////////////////////////////////////////
323
324QQmlPropertyObserver::QQmlPropertyObserver(QQmlBoundSignalExpression *expr)
326 auto This = static_cast<QQmlPropertyObserver*>(self);
327 This->expression->evaluate(nullptr);
328 })
329{
330 expression.adopt(expr);
331}
332
333QT_END_NAMESPACE
QQmlPropertyObserver(QQmlBoundSignalExpression *expr)
void QQmlBoundSignal_callback(QQmlNotifierEndpoint *e, void **a)