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 | |
64 | QT_BEGIN_NAMESPACE |
65 | |
66 | QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index, |
67 | QQmlContextData *ctxt, QObject *scope, const QString &expression, |
68 | const QString &fileName, quint16 line, quint16 column, |
69 | const QString &handlerName, |
70 | const QString ¶meterString) |
71 | : QQmlJavaScriptExpression(), |
72 | m_index(index), |
73 | m_target(target) |
74 | { |
75 | init(ctxt, scope); |
76 | |
77 | QV4::ExecutionEngine *v4 = engine()->handle(); |
78 | |
79 | QString function; |
80 | |
81 | // Add some leading whitespace to account for the binding's column offset. |
82 | // It's 2 off because a, we start counting at 1 and b, the '(' below is not counted. |
83 | function += QString(qMax(column, (quint16)2) - 2, QChar(QChar::Space)) |
84 | + QLatin1String("(function " ) + handlerName + QLatin1Char('('); |
85 | |
86 | if (parameterString.isEmpty()) { |
87 | QString error; |
88 | //TODO: look at using the property cache here (as in the compiler) |
89 | // for further optimization |
90 | QMetaMethod signal = QMetaObjectPrivate::signal(m_target->metaObject(), m_index); |
91 | function += QQmlPropertyCache::signalParameterStringForJS(v4, signal.parameterNames(), &error); |
92 | |
93 | if (!error.isEmpty()) { |
94 | qmlWarning(scopeObject()) << error; |
95 | return; |
96 | } |
97 | } else |
98 | function += parameterString; |
99 | |
100 | function += QLatin1String(") { " ) + expression + QLatin1String(" })" ); |
101 | QV4::Scope valueScope(v4); |
102 | QV4::ScopedFunctionObject f(valueScope, evalFunction(context(), scopeObject(), function, fileName, line)); |
103 | QV4::ScopedContext context(valueScope, f->scope()); |
104 | setupFunction(context, f->function()); |
105 | } |
106 | |
107 | QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index, QQmlContextData *ctxt, QObject *scopeObject, |
108 | QV4::Function *function, QV4::ExecutionContext *scope) |
109 | : QQmlJavaScriptExpression(), |
110 | m_index(index), |
111 | m_target(target) |
112 | { |
113 | // It's important to call init first, because m_index gets remapped in case of cloned signals. |
114 | init(ctxt, scopeObject); |
115 | |
116 | QV4::ExecutionEngine *engine = ctxt->engine->handle(); |
117 | |
118 | // If the function is marked as having a nested function, then the user wrote: |
119 | // onSomeSignal: function() { /*....*/ } |
120 | // So take that nested function: |
121 | if (auto closure = function->nestedFunction()) { |
122 | function = closure; |
123 | } else { |
124 | QList<QByteArray> signalParameters = QMetaObjectPrivate::signal(m_target->metaObject(), m_index).parameterNames(); |
125 | if (!signalParameters.isEmpty()) { |
126 | QString error; |
127 | QQmlPropertyCache::signalParameterStringForJS(engine, signalParameters, &error); |
128 | if (!error.isEmpty()) { |
129 | qmlWarning(scopeObject) << error; |
130 | return; |
131 | } |
132 | function->updateInternalClass(engine, signalParameters); |
133 | } |
134 | } |
135 | |
136 | QV4::Scope valueScope(engine); |
137 | QV4::Scoped<QV4::QmlContext> qmlContext(valueScope, scope); |
138 | if (!qmlContext) |
139 | qmlContext = QV4::QmlContext::create(engine->rootContext(), ctxt, scopeObject); |
140 | setupFunction(qmlContext, function); |
141 | } |
142 | |
143 | void QQmlBoundSignalExpression::init(QQmlContextData *ctxt, QObject *scope) |
144 | { |
145 | setNotifyOnValueChanged(false); |
146 | setContext(ctxt); |
147 | setScopeObject(scope); |
148 | |
149 | Q_ASSERT(m_target && m_index > -1); |
150 | m_index = QQmlPropertyCache::originalClone(m_target, m_index); |
151 | } |
152 | |
153 | QQmlBoundSignalExpression::~QQmlBoundSignalExpression() |
154 | { |
155 | } |
156 | |
157 | QString QQmlBoundSignalExpression::expressionIdentifier() const |
158 | { |
159 | QQmlSourceLocation loc = sourceLocation(); |
160 | return loc.sourceFile + QLatin1Char(':') + QString::number(loc.line); |
161 | } |
162 | |
163 | void QQmlBoundSignalExpression::expressionChanged() |
164 | { |
165 | // bound signals do not notify on change. |
166 | } |
167 | |
168 | QString QQmlBoundSignalExpression::expression() const |
169 | { |
170 | if (expressionFunctionValid()) |
171 | return QStringLiteral("function() { [native code] }" ); |
172 | return QString(); |
173 | } |
174 | |
175 | // Parts of this function mirror code in QQmlExpressionPrivate::value() and v8value(). |
176 | // Changes made here may need to be made there and vice versa. |
177 | void QQmlBoundSignalExpression::evaluate(void **a) |
178 | { |
179 | Q_ASSERT (context() && engine()); |
180 | |
181 | if (!expressionFunctionValid()) |
182 | return; |
183 | |
184 | QQmlEngine *qmlengine = engine(); |
185 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlengine); |
186 | QV4::ExecutionEngine *v4 = qmlengine->handle(); |
187 | QV4::Scope scope(v4); |
188 | |
189 | ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation. |
190 | |
191 | QQmlMetaObject::ArgTypeStorage storage; |
192 | //TODO: lookup via signal index rather than method index as an optimization |
193 | int methodIndex = QMetaObjectPrivate::signal(m_target->metaObject(), m_index).methodIndex(); |
194 | int *argsTypes = QQmlMetaObject(m_target).methodParameterTypes(methodIndex, &storage, nullptr); |
195 | int argCount = argsTypes ? *argsTypes : 0; |
196 | |
197 | QV4::JSCallData jsCall(scope, argCount); |
198 | for (int ii = 0; ii < argCount; ++ii) { |
199 | int type = argsTypes[ii + 1]; |
200 | //### ideally we would use metaTypeToJS, however it currently gives different results |
201 | // for several cases (such as QVariant type and QObject-derived types) |
202 | //args[ii] = engine->metaTypeToJS(type, a[ii + 1]); |
203 | if (type == qMetaTypeId<QJSValue>()) { |
204 | if (QV4::Value *v4Value = QJSValuePrivate::valueForData(reinterpret_cast<QJSValue *>(a[ii + 1]), &jsCall->args[ii])) |
205 | jsCall->args[ii] = *v4Value; |
206 | else |
207 | jsCall->args[ii] = QV4::Encode::undefined(); |
208 | } else if (type == QMetaType::QVariant) { |
209 | jsCall->args[ii] = scope.engine->fromVariant(*((QVariant *)a[ii + 1])); |
210 | } else if (type == QMetaType::Int) { |
211 | //### optimization. Can go away if we switch to metaTypeToJS, or be expanded otherwise |
212 | jsCall->args[ii] = QV4::Value::fromInt32(*reinterpret_cast<const int*>(a[ii + 1])); |
213 | } else if (ep->isQObject(type)) { |
214 | if (!*reinterpret_cast<void* const *>(a[ii + 1])) |
215 | jsCall->args[ii] = QV4::Value::nullValue(); |
216 | else |
217 | jsCall->args[ii] = QV4::QObjectWrapper::wrap(v4, *reinterpret_cast<QObject* const *>(a[ii + 1])); |
218 | } else { |
219 | jsCall->args[ii] = scope.engine->fromVariant(QVariant(type, a[ii + 1])); |
220 | } |
221 | } |
222 | |
223 | QQmlJavaScriptExpression::evaluate(jsCall.callData(), nullptr); |
224 | |
225 | ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete. |
226 | } |
227 | |
228 | void QQmlBoundSignalExpression::evaluate(const QList<QVariant> &args) |
229 | { |
230 | Q_ASSERT (context() && engine()); |
231 | |
232 | if (!expressionFunctionValid()) |
233 | return; |
234 | |
235 | QQmlEngine *qmlengine = engine(); |
236 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlengine); |
237 | QV4::Scope scope(qmlengine->handle()); |
238 | |
239 | ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation. |
240 | |
241 | QV4::JSCallData jsCall(scope, args.count()); |
242 | for (int ii = 0; ii < args.count(); ++ii) { |
243 | jsCall->args[ii] = scope.engine->fromVariant(args[ii]); |
244 | } |
245 | |
246 | QQmlJavaScriptExpression::evaluate(jsCall.callData(), nullptr); |
247 | |
248 | ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete. |
249 | } |
250 | |
251 | //////////////////////////////////////////////////////////////////////// |
252 | |
253 | |
254 | /*! \internal |
255 | \a signal MUST be in the signal index range (see QObjectPrivate::signalIndex()). |
256 | This is different from QMetaMethod::methodIndex(). |
257 | */ |
258 | QQmlBoundSignal::QQmlBoundSignal(QObject *target, int signal, QObject *owner, |
259 | QQmlEngine *engine) |
260 | : QQmlNotifierEndpoint(QQmlNotifierEndpoint::QQmlBoundSignal), |
261 | m_prevSignal(nullptr), m_nextSignal(nullptr), |
262 | m_enabled(true), m_expression(nullptr) |
263 | { |
264 | addToObject(owner); |
265 | |
266 | /* |
267 | If this is a cloned method, connect to the 'original'. For example, |
268 | for the signal 'void aSignal(int parameter = 0)', if the method |
269 | index refers to 'aSignal()', get the index of 'aSignal(int)'. |
270 | This ensures that 'parameter' will be available from QML. |
271 | */ |
272 | signal = QQmlPropertyCache::originalClone(target, signal); |
273 | QQmlNotifierEndpoint::connect(target, signal, engine); |
274 | } |
275 | |
276 | QQmlBoundSignal::~QQmlBoundSignal() |
277 | { |
278 | removeFromObject(); |
279 | } |
280 | |
281 | void QQmlBoundSignal::addToObject(QObject *obj) |
282 | { |
283 | Q_ASSERT(!m_prevSignal); |
284 | Q_ASSERT(obj); |
285 | |
286 | QQmlData *data = QQmlData::get(obj, true); |
287 | |
288 | m_nextSignal = data->signalHandlers; |
289 | if (m_nextSignal) m_nextSignal->m_prevSignal = &m_nextSignal; |
290 | m_prevSignal = &data->signalHandlers; |
291 | data->signalHandlers = this; |
292 | } |
293 | |
294 | void QQmlBoundSignal::removeFromObject() |
295 | { |
296 | if (m_prevSignal) { |
297 | *m_prevSignal = m_nextSignal; |
298 | if (m_nextSignal) m_nextSignal->m_prevSignal = m_prevSignal; |
299 | m_prevSignal = nullptr; |
300 | m_nextSignal = nullptr; |
301 | } |
302 | } |
303 | |
304 | |
305 | /*! |
306 | Returns the signal expression. |
307 | */ |
308 | QQmlBoundSignalExpression *QQmlBoundSignal::expression() const |
309 | { |
310 | return m_expression; |
311 | } |
312 | |
313 | /*! |
314 | Sets the signal expression to \a e. |
315 | |
316 | The QQmlBoundSignal instance takes ownership of \a e (and does not add a reference). |
317 | */ |
318 | void QQmlBoundSignal::takeExpression(QQmlBoundSignalExpression *e) |
319 | { |
320 | m_expression.take(e); |
321 | if (m_expression) |
322 | m_expression->setNotifyOnValueChanged(false); |
323 | } |
324 | |
325 | /*! |
326 | This property holds whether the item will emit signals. |
327 | |
328 | The QQmlBoundSignal callback will only emit a signal if this property is set to true. |
329 | |
330 | By default, this property is true. |
331 | */ |
332 | void QQmlBoundSignal::setEnabled(bool enabled) |
333 | { |
334 | if (m_enabled == enabled) |
335 | return; |
336 | |
337 | m_enabled = enabled; |
338 | } |
339 | |
340 | void QQmlBoundSignal_callback(QQmlNotifierEndpoint *e, void **a) |
341 | { |
342 | QQmlBoundSignal *s = static_cast<QQmlBoundSignal*>(e); |
343 | |
344 | if (!s->m_expression || !s->m_enabled) |
345 | return; |
346 | |
347 | QV4DebugService *service = QQmlDebugConnector::service<QV4DebugService>(); |
348 | if (service) |
349 | service->signalEmitted(QString::fromUtf8(QMetaObjectPrivate::signal( |
350 | s->m_expression->target()->metaObject(), |
351 | s->signalIndex()).methodSignature())); |
352 | |
353 | QQmlEngine *engine; |
354 | if (s->m_expression && (engine = s->m_expression->engine())) { |
355 | QQmlHandlingSignalProfiler prof(QQmlEnginePrivate::get(engine)->profiler, s->m_expression); |
356 | s->m_expression->evaluate(a); |
357 | if (s->m_expression && s->m_expression->hasError()) { |
358 | QQmlEnginePrivate::warning(engine, s->m_expression->error(engine)); |
359 | } |
360 | } |
361 | } |
362 | |
363 | //////////////////////////////////////////////////////////////////////// |
364 | |
365 | QQmlBoundSignalExpressionPointer::QQmlBoundSignalExpressionPointer(QQmlBoundSignalExpression *o) |
366 | : o(o) |
367 | { |
368 | if (o) o->addref(); |
369 | } |
370 | |
371 | QQmlBoundSignalExpressionPointer::QQmlBoundSignalExpressionPointer(const QQmlBoundSignalExpressionPointer &other) |
372 | : o(other.o) |
373 | { |
374 | if (o) o->addref(); |
375 | } |
376 | |
377 | QQmlBoundSignalExpressionPointer::~QQmlBoundSignalExpressionPointer() |
378 | { |
379 | if (o) o->release(); |
380 | } |
381 | |
382 | QQmlBoundSignalExpressionPointer &QQmlBoundSignalExpressionPointer::operator=(const QQmlBoundSignalExpressionPointer &other) |
383 | { |
384 | if (other.o) other.o->addref(); |
385 | if (o) o->release(); |
386 | o = other.o; |
387 | return *this; |
388 | } |
389 | |
390 | QQmlBoundSignalExpressionPointer &QQmlBoundSignalExpressionPointer::operator=(QQmlBoundSignalExpression *other) |
391 | { |
392 | if (other) other->addref(); |
393 | if (o) o->release(); |
394 | o = other; |
395 | return *this; |
396 | } |
397 | |
398 | /*! |
399 | Takes ownership of \a other. take() does *not* add a reference, as it assumes ownership |
400 | of the callers reference of other. |
401 | */ |
402 | QQmlBoundSignalExpressionPointer &QQmlBoundSignalExpressionPointer::take(QQmlBoundSignalExpression *other) |
403 | { |
404 | if (o) o->release(); |
405 | o = other; |
406 | return *this; |
407 | } |
408 | |
409 | QT_END_NAMESPACE |
410 | |