1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qqmlboundsignal_p.h"
41
42#include <private/qmetaobject_p.h>
43#include <private/qmetaobjectbuilder_p.h>
44#include "qqmlengine_p.h"
45#include "qqmlexpression_p.h"
46#include "qqmlcontext_p.h"
47#include "qqml.h"
48#include "qqmlcontext.h"
49#include "qqmlglobal_p.h"
50#include <private/qqmlprofiler_p.h>
51#include <private/qqmldebugconnector_p.h>
52#include <private/qqmldebugserviceinterfaces_p.h>
53#include "qqmlinfo.h"
54
55#include <private/qjsvalue_p.h>
56#include <private/qv4value_p.h>
57#include <private/qv4jscall_p.h>
58#include <private/qv4qobjectwrapper_p.h>
59#include <private/qv4qmlcontext_p.h>
60
61#include <QtCore/qdebug.h>
62
63#include <qtqml_tracepoints_p.h>
64
65QT_BEGIN_NAMESPACE
66
67QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index,
68 QQmlContextData *ctxt, QObject *scope, const QString &expression,
69 const QString &fileName, quint16 line, quint16 column,
70 const QString &handlerName,
71 const QString &parameterString)
72 : QQmlJavaScriptExpression(),
73 m_index(index),
74 m_target(target)
75{
76 init(ctxt, scope);
77
78 QV4::ExecutionEngine *v4 = engine()->handle();
79
80 QString function;
81
82 // Add some leading whitespace to account for the binding's column offset.
83 // It's 2 off because a, we start counting at 1 and b, the '(' below is not counted.
84 function += QString(qMax(a: column, b: (quint16)2) - 2, QChar(QChar::Space))
85 + QLatin1String("(function ") + handlerName + QLatin1Char('(');
86
87 if (parameterString.isEmpty()) {
88 QString error;
89 //TODO: look at using the property cache here (as in the compiler)
90 // for further optimization
91 QMetaMethod signal = QMetaObjectPrivate::signal(m: m_target->metaObject(), signal_index: m_index);
92 function += QQmlPropertyCache::signalParameterStringForJS(engine: v4, parameterNameList: signal.parameterNames(), errorString: &error);
93
94 if (!error.isEmpty()) {
95 qmlWarning(me: scopeObject()) << error;
96 return;
97 }
98 } else
99 function += parameterString;
100
101 function += QLatin1String(") { ") + expression + QLatin1String(" })");
102 QV4::Scope valueScope(v4);
103 QV4::ScopedFunctionObject f(valueScope, evalFunction(ctxt: context(), scope: scopeObject(), code: function, filename: fileName, line));
104 QV4::ScopedContext context(valueScope, f->scope());
105 setupFunction(qmlContext: context, f: f->function());
106}
107
108QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index, QQmlContextData *ctxt, QObject *scopeObject,
109 QV4::Function *function, QV4::ExecutionContext *scope)
110 : QQmlJavaScriptExpression(),
111 m_index(index),
112 m_target(target)
113{
114 // It's important to call init first, because m_index gets remapped in case of cloned signals.
115 init(ctxt, scope: scopeObject);
116
117 QV4::ExecutionEngine *engine = ctxt->engine->handle();
118
119 // If the function is marked as having a nested function, then the user wrote:
120 // onSomeSignal: function() { /*....*/ }
121 // So take that nested function:
122 if (auto closure = function->nestedFunction()) {
123 function = closure;
124 } else {
125 QList<QByteArray> signalParameters = QMetaObjectPrivate::signal(m: m_target->metaObject(), signal_index: m_index).parameterNames();
126 if (!signalParameters.isEmpty()) {
127 QString error;
128 QQmlPropertyCache::signalParameterStringForJS(engine, parameterNameList: signalParameters, errorString: &error);
129 if (!error.isEmpty()) {
130 qmlWarning(me: scopeObject) << error;
131 return;
132 }
133 function->updateInternalClass(engine, parameters: signalParameters);
134 }
135 }
136
137 QV4::Scope valueScope(engine);
138 QV4::Scoped<QV4::QmlContext> qmlContext(valueScope, scope);
139 if (!qmlContext)
140 qmlContext = QV4::QmlContext::create(parent: engine->rootContext(), context: ctxt, scopeObject);
141 setupFunction(qmlContext, f: function);
142}
143
144void QQmlBoundSignalExpression::init(QQmlContextData *ctxt, QObject *scope)
145{
146 setNotifyOnValueChanged(false);
147 setContext(ctxt);
148 setScopeObject(scope);
149
150 Q_ASSERT(m_target && m_index > -1);
151 m_index = QQmlPropertyCache::originalClone(m_target, index: m_index);
152}
153
154QQmlBoundSignalExpression::~QQmlBoundSignalExpression()
155{
156}
157
158QString QQmlBoundSignalExpression::expressionIdentifier() const
159{
160 QQmlSourceLocation loc = sourceLocation();
161 return loc.sourceFile + QLatin1Char(':') + QString::number(loc.line);
162}
163
164void QQmlBoundSignalExpression::expressionChanged()
165{
166 // bound signals do not notify on change.
167}
168
169QString QQmlBoundSignalExpression::expression() const
170{
171 if (expressionFunctionValid())
172 return QStringLiteral("function() { [native code] }");
173 return QString();
174}
175
176// Parts of this function mirror code in QQmlExpressionPrivate::value() and v8value().
177// Changes made here may need to be made there and vice versa.
178void QQmlBoundSignalExpression::evaluate(void **a)
179{
180 Q_ASSERT (context() && engine());
181
182 if (!expressionFunctionValid())
183 return;
184
185 QQmlEngine *qmlengine = engine();
186 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: qmlengine);
187 QV4::ExecutionEngine *v4 = qmlengine->handle();
188 QV4::Scope scope(v4);
189
190 ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation.
191
192 QQmlMetaObject::ArgTypeStorage storage;
193 //TODO: lookup via signal index rather than method index as an optimization
194 int methodIndex = QMetaObjectPrivate::signal(m: m_target->metaObject(), signal_index: m_index).methodIndex();
195 int *argsTypes = QQmlMetaObject(m_target).methodParameterTypes(index: methodIndex, argStorage: &storage, unknownTypeError: nullptr);
196 int argCount = argsTypes ? *argsTypes : 0;
197
198 QV4::JSCallData jsCall(scope, argCount);
199 for (int ii = 0; ii < argCount; ++ii) {
200 int type = argsTypes[ii + 1];
201 //### ideally we would use metaTypeToJS, however it currently gives different results
202 // for several cases (such as QVariant type and QObject-derived types)
203 //args[ii] = engine->metaTypeToJS(type, a[ii + 1]);
204 if (type == qMetaTypeId<QJSValue>()) {
205 if (QV4::Value *v4Value = QJSValuePrivate::valueForData(jsval: reinterpret_cast<QJSValue *>(a[ii + 1]), scratch: &jsCall->args[ii]))
206 jsCall->args[ii] = *v4Value;
207 else
208 jsCall->args[ii] = QV4::Encode::undefined();
209 } else if (type == QMetaType::QVariant) {
210 jsCall->args[ii] = scope.engine->fromVariant(*((QVariant *)a[ii + 1]));
211 } else if (type == QMetaType::Int) {
212 //### optimization. Can go away if we switch to metaTypeToJS, or be expanded otherwise
213 jsCall->args[ii] = QV4::Value::fromInt32(i: *reinterpret_cast<const int*>(a[ii + 1]));
214 } else if (ep->isQObject(type)) {
215 if (!*reinterpret_cast<void* const *>(a[ii + 1]))
216 jsCall->args[ii] = QV4::Value::nullValue();
217 else
218 jsCall->args[ii] = QV4::QObjectWrapper::wrap(engine: v4, object: *reinterpret_cast<QObject* const *>(a[ii + 1]));
219 } else {
220 jsCall->args[ii] = scope.engine->fromVariant(QVariant(type, a[ii + 1]));
221 }
222 }
223
224 QQmlJavaScriptExpression::evaluate(callData: jsCall.callData(), isUndefined: nullptr);
225
226 ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete.
227}
228
229void QQmlBoundSignalExpression::evaluate(const QList<QVariant> &args)
230{
231 Q_ASSERT (context() && engine());
232
233 if (!expressionFunctionValid())
234 return;
235
236 QQmlEngine *qmlengine = engine();
237 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: qmlengine);
238 QV4::Scope scope(qmlengine->handle());
239
240 ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation.
241
242 QV4::JSCallData jsCall(scope, args.count());
243 for (int ii = 0; ii < args.count(); ++ii) {
244 jsCall->args[ii] = scope.engine->fromVariant(args[ii]);
245 }
246
247 QQmlJavaScriptExpression::evaluate(callData: jsCall.callData(), isUndefined: nullptr);
248
249 ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete.
250}
251
252////////////////////////////////////////////////////////////////////////
253
254
255/*! \internal
256 \a signal MUST be in the signal index range (see QObjectPrivate::signalIndex()).
257 This is different from QMetaMethod::methodIndex().
258*/
259QQmlBoundSignal::QQmlBoundSignal(QObject *target, int signal, QObject *owner,
260 QQmlEngine *engine)
261 : QQmlNotifierEndpoint(QQmlNotifierEndpoint::QQmlBoundSignal),
262 m_prevSignal(nullptr), m_nextSignal(nullptr),
263 m_enabled(true), m_expression(nullptr)
264{
265 addToObject(owner);
266
267 /*
268 If this is a cloned method, connect to the 'original'. For example,
269 for the signal 'void aSignal(int parameter = 0)', if the method
270 index refers to 'aSignal()', get the index of 'aSignal(int)'.
271 This ensures that 'parameter' will be available from QML.
272 */
273 signal = QQmlPropertyCache::originalClone(target, index: signal);
274 QQmlNotifierEndpoint::connect(source: target, sourceSignal: signal, engine);
275}
276
277QQmlBoundSignal::~QQmlBoundSignal()
278{
279 removeFromObject();
280}
281
282void QQmlBoundSignal::addToObject(QObject *obj)
283{
284 Q_ASSERT(!m_prevSignal);
285 Q_ASSERT(obj);
286
287 QQmlData *data = QQmlData::get(object: obj, create: true);
288
289 m_nextSignal = data->signalHandlers;
290 if (m_nextSignal) m_nextSignal->m_prevSignal = &m_nextSignal;
291 m_prevSignal = &data->signalHandlers;
292 data->signalHandlers = this;
293}
294
295void QQmlBoundSignal::removeFromObject()
296{
297 if (m_prevSignal) {
298 *m_prevSignal = m_nextSignal;
299 if (m_nextSignal) m_nextSignal->m_prevSignal = m_prevSignal;
300 m_prevSignal = nullptr;
301 m_nextSignal = nullptr;
302 }
303}
304
305
306/*!
307 Returns the signal expression.
308*/
309QQmlBoundSignalExpression *QQmlBoundSignal::expression() const
310{
311 return m_expression;
312}
313
314/*!
315 Sets the signal expression to \a e.
316
317 The QQmlBoundSignal instance takes ownership of \a e (and does not add a reference).
318*/
319void QQmlBoundSignal::takeExpression(QQmlBoundSignalExpression *e)
320{
321 m_expression.take(e);
322 if (m_expression)
323 m_expression->setNotifyOnValueChanged(false);
324}
325
326/*!
327 This property holds whether the item will emit signals.
328
329 The QQmlBoundSignal callback will only emit a signal if this property is set to true.
330
331 By default, this property is true.
332 */
333void QQmlBoundSignal::setEnabled(bool enabled)
334{
335 if (m_enabled == enabled)
336 return;
337
338 m_enabled = enabled;
339}
340
341void QQmlBoundSignal_callback(QQmlNotifierEndpoint *e, void **a)
342{
343 QQmlBoundSignal *s = static_cast<QQmlBoundSignal*>(e);
344
345 if (!s->m_expression || !s->m_enabled)
346 return;
347
348 QV4DebugService *service = QQmlDebugConnector::service<QV4DebugService>();
349 if (service)
350 service->signalEmitted(signal: QString::fromUtf8(str: QMetaObjectPrivate::signal(
351 m: s->m_expression->target()->metaObject(),
352 signal_index: s->signalIndex()).methodSignature()));
353
354 QQmlEngine *engine;
355 if (s->m_expression && (engine = s->m_expression->engine())) {
356 Q_TRACE_SCOPE(QQmlHandlingSignal, engine,
357 s->m_expression->function() ? s->m_expression->function()->name()->toQString() : QString(),
358 s->m_expression->sourceLocation().sourceFile, s->m_expression->sourceLocation().line,
359 s->m_expression->sourceLocation().column);
360 QQmlHandlingSignalProfiler prof(QQmlEnginePrivate::get(e: engine)->profiler, s->m_expression);
361 s->m_expression->evaluate(a);
362 if (s->m_expression && s->m_expression->hasError()) {
363 QQmlEnginePrivate::warning(engine, s->m_expression->error(engine));
364 }
365 }
366}
367
368////////////////////////////////////////////////////////////////////////
369
370QQmlBoundSignalExpressionPointer::QQmlBoundSignalExpressionPointer(QQmlBoundSignalExpression *o)
371: o(o)
372{
373 if (o) o->addref();
374}
375
376QQmlBoundSignalExpressionPointer::QQmlBoundSignalExpressionPointer(const QQmlBoundSignalExpressionPointer &other)
377: o(other.o)
378{
379 if (o) o->addref();
380}
381
382QQmlBoundSignalExpressionPointer::~QQmlBoundSignalExpressionPointer()
383{
384 if (o) o->release();
385}
386
387QQmlBoundSignalExpressionPointer &QQmlBoundSignalExpressionPointer::operator=(const QQmlBoundSignalExpressionPointer &other)
388{
389 if (other.o) other.o->addref();
390 if (o) o->release();
391 o = other.o;
392 return *this;
393}
394
395QQmlBoundSignalExpressionPointer &QQmlBoundSignalExpressionPointer::operator=(QQmlBoundSignalExpression *other)
396{
397 if (other) other->addref();
398 if (o) o->release();
399 o = other;
400 return *this;
401}
402
403/*!
404Takes ownership of \a other. take() does *not* add a reference, as it assumes ownership
405of the callers reference of other.
406*/
407QQmlBoundSignalExpressionPointer &QQmlBoundSignalExpressionPointer::take(QQmlBoundSignalExpression *other)
408{
409 if (o) o->release();
410 o = other;
411 return *this;
412}
413
414QT_END_NAMESPACE
415

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