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
65 QT_BEGIN_NAMESPACE
66
QQmlBoundSignalExpression(QObject * target,int index,QQmlContextData * ctxt,QObject * scope,const QString & expression,const QString & fileName,quint16 line,quint16 column,const QString & handlerName,const QString & parameterString)67 QQmlBoundSignalExpression::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 ¶meterString)
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(column, (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_target->metaObject(), m_index);
92 function += QQmlPropertyCache::signalParameterStringForJS(v4, signal.parameterNames(), &error);
93
94 if (!error.isEmpty()) {
95 qmlWarning(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(context(), scopeObject(), function, fileName, line));
104 QV4::ScopedContext context(valueScope, f->scope());
105 setupFunction(context, f->function());
106 }
107
QQmlBoundSignalExpression(QObject * target,int index,QQmlContextData * ctxt,QObject * scopeObject,QV4::Function * function,QV4::ExecutionContext * scope)108 QQmlBoundSignalExpression::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, 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_target->metaObject(), m_index).parameterNames();
126 if (!signalParameters.isEmpty()) {
127 QString error;
128 QQmlPropertyCache::signalParameterStringForJS(engine, signalParameters, &error);
129 if (!error.isEmpty()) {
130 qmlWarning(scopeObject) << error;
131 return;
132 }
133 function->updateInternalClass(engine, 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(engine->rootContext(), ctxt, scopeObject);
141 setupFunction(qmlContext, function);
142 }
143
init(QQmlContextData * ctxt,QObject * scope)144 void 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, m_index);
152 }
153
~QQmlBoundSignalExpression()154 QQmlBoundSignalExpression::~QQmlBoundSignalExpression()
155 {
156 }
157
expressionIdentifier() const158 QString QQmlBoundSignalExpression::expressionIdentifier() const
159 {
160 QQmlSourceLocation loc = sourceLocation();
161 return loc.sourceFile + QLatin1Char(':') + QString::number(loc.line);
162 }
163
expressionChanged()164 void QQmlBoundSignalExpression::expressionChanged()
165 {
166 // bound signals do not notify on change.
167 }
168
expression() const169 QString 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.
evaluate(void ** a)178 void 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(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_target->metaObject(), m_index).methodIndex();
195 int *argsTypes = QQmlMetaObject(m_target).methodParameterTypes(methodIndex, &storage, 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(reinterpret_cast<QJSValue *>(a[ii + 1]), &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(*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(v4, *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(jsCall.callData(), nullptr);
225
226 ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete.
227 }
228
evaluate(const QList<QVariant> & args)229 void 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(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(jsCall.callData(), 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 */
QQmlBoundSignal(QObject * target,int signal,QObject * owner,QQmlEngine * engine)259 QQmlBoundSignal::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, signal);
274 QQmlNotifierEndpoint::connect(target, signal, engine);
275 }
276
~QQmlBoundSignal()277 QQmlBoundSignal::~QQmlBoundSignal()
278 {
279 removeFromObject();
280 }
281
addToObject(QObject * obj)282 void QQmlBoundSignal::addToObject(QObject *obj)
283 {
284 Q_ASSERT(!m_prevSignal);
285 Q_ASSERT(obj);
286
287 QQmlData *data = QQmlData::get(obj, 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
removeFromObject()295 void 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 */
expression() const309 QQmlBoundSignalExpression *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 */
takeExpression(QQmlBoundSignalExpression * e)319 void 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 */
setEnabled(bool enabled)333 void QQmlBoundSignal::setEnabled(bool enabled)
334 {
335 if (m_enabled == enabled)
336 return;
337
338 m_enabled = enabled;
339 }
340
QQmlBoundSignal_callback(QQmlNotifierEndpoint * e,void ** a)341 void 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(QString::fromUtf8(QMetaObjectPrivate::signal(
351 s->m_expression->target()->metaObject(),
352 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(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
QQmlBoundSignalExpressionPointer(QQmlBoundSignalExpression * o)370 QQmlBoundSignalExpressionPointer::QQmlBoundSignalExpressionPointer(QQmlBoundSignalExpression *o)
371 : o(o)
372 {
373 if (o) o->addref();
374 }
375
QQmlBoundSignalExpressionPointer(const QQmlBoundSignalExpressionPointer & other)376 QQmlBoundSignalExpressionPointer::QQmlBoundSignalExpressionPointer(const QQmlBoundSignalExpressionPointer &other)
377 : o(other.o)
378 {
379 if (o) o->addref();
380 }
381
~QQmlBoundSignalExpressionPointer()382 QQmlBoundSignalExpressionPointer::~QQmlBoundSignalExpressionPointer()
383 {
384 if (o) o->release();
385 }
386
operator =(const QQmlBoundSignalExpressionPointer & other)387 QQmlBoundSignalExpressionPointer &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
operator =(QQmlBoundSignalExpression * other)395 QQmlBoundSignalExpressionPointer &QQmlBoundSignalExpressionPointer::operator=(QQmlBoundSignalExpression *other)
396 {
397 if (other) other->addref();
398 if (o) o->release();
399 o = other;
400 return *this;
401 }
402
403 /*!
404 Takes ownership of \a other. take() does *not* add a reference, as it assumes ownership
405 of the callers reference of other.
406 */
take(QQmlBoundSignalExpression * other)407 QQmlBoundSignalExpressionPointer &QQmlBoundSignalExpressionPointer::take(QQmlBoundSignalExpression *other)
408 {
409 if (o) o->release();
410 o = other;
411 return *this;
412 }
413
414 QT_END_NAMESPACE
415