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 &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(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