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 | |
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(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 | |
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, 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 | |
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, index: m_index); |
152 | } |
153 | |
154 | QQmlBoundSignalExpression::~QQmlBoundSignalExpression() |
155 | { |
156 | } |
157 | |
158 | QString QQmlBoundSignalExpression::expressionIdentifier() const |
159 | { |
160 | QQmlSourceLocation loc = sourceLocation(); |
161 | return loc.sourceFile + QLatin1Char(':') + QString::number(loc.line); |
162 | } |
163 | |
164 | void QQmlBoundSignalExpression::expressionChanged() |
165 | { |
166 | // bound signals do not notify on change. |
167 | } |
168 | |
169 | 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. |
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(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 | |
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(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 | */ |
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, index: signal); |
274 | QQmlNotifierEndpoint::connect(source: target, sourceSignal: signal, engine); |
275 | } |
276 | |
277 | QQmlBoundSignal::~QQmlBoundSignal() |
278 | { |
279 | removeFromObject(); |
280 | } |
281 | |
282 | void 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 | |
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 | */ |
309 | 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 | */ |
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 | */ |
333 | void QQmlBoundSignal::setEnabled(bool enabled) |
334 | { |
335 | if (m_enabled == enabled) |
336 | return; |
337 | |
338 | m_enabled = enabled; |
339 | } |
340 | |
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(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 | |
370 | QQmlBoundSignalExpressionPointer::QQmlBoundSignalExpressionPointer(QQmlBoundSignalExpression *o) |
371 | : o(o) |
372 | { |
373 | if (o) o->addref(); |
374 | } |
375 | |
376 | QQmlBoundSignalExpressionPointer::QQmlBoundSignalExpressionPointer(const QQmlBoundSignalExpressionPointer &other) |
377 | : o(other.o) |
378 | { |
379 | if (o) o->addref(); |
380 | } |
381 | |
382 | QQmlBoundSignalExpressionPointer::~QQmlBoundSignalExpressionPointer() |
383 | { |
384 | if (o) o->release(); |
385 | } |
386 | |
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 | |
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 | */ |
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 | |