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