Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlconnections.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
6
7#include <private/qqmlboundsignal_p.h>
8#include <private/qqmlcontext_p.h>
9#include <private/qqmlexpression_p.h>
10#include <private/qqmlproperty_p.h>
11#include <private/qqmlsignalnames_p.h>
12#include <private/qqmlvmemetaobject_p.h>
13#include <private/qv4jscall_p.h>
14#include <private/qv4qobjectwrapper_p.h>
15
16#include <QtQml/qqmlcontext.h>
17#include <QtQml/qqmlinfo.h>
18
19#include <QtCore/qdebug.h>
20#include <QtCore/qloggingcategory.h>
21#include <QtCore/qstringlist.h>
22
23#include <private/qobject_p.h>
24
26
27Q_STATIC_LOGGING_CATEGORY(lcQmlConnections, "qt.qml.connections")
28
29// This is the equivalent of QQmlBoundSignal for C++ methods as as slots.
30// If a type derived from QQmlConnnections is compiled using qmltc, the
31// JavaScript functions it contains are turned into C++ methods and we cannot
32// use QQmlBoundSignal to connect to those.
33struct QQmlConnectionSlotDispatcher : public QtPrivate::QSlotObjectBase
34{
35 QV4::ExecutionEngine *v4 = nullptr;
36 QObject *receiver = nullptr;
37
38 // Signals rarely have more than one argument.
39 QQmlMetaObject::ArgTypeStorage<2> signalMetaTypes;
40 QQmlMetaObject::ArgTypeStorage<2> slotMetaTypes;
41
42 QMetaObject::Connection connection;
43
44 int slotIndex = -1;
45 bool enabled = true;
46
47 QQmlConnectionSlotDispatcher(
48 QV4::ExecutionEngine *v4, QObject *sender, const QQmlPropertyData &signal,
49 QObject *receiver, const QQmlPropertyData &slot, bool enabled)
50 : QtPrivate::QSlotObjectBase(&impl)
51 , v4(v4)
52 , receiver(receiver)
53 , slotIndex(slot.coreIndex())
54 , enabled(enabled)
55 {
56 const QQmlMetaObject senderMeta(sender->metaObject());
57 senderMeta.methodReturnAndParameterTypes(
58 signal, senderMeta.metaObject()->method(signal.coreIndex()), &signalMetaTypes);
59
60 const QQmlMetaObject receiverMeta(receiver->metaObject());
61 receiverMeta.methodReturnAndParameterTypes(
62 slot, receiverMeta.metaObject()->method(slotIndex), &slotMetaTypes);
63 }
64
65 template<typename ArgTypeStorage>
66 struct TypedFunction
67 {
68 Q_DISABLE_COPY_MOVE(TypedFunction)
69 public:
70 TypedFunction(const ArgTypeStorage *storage) : storage(storage) {}
71
72 QMetaType returnMetaType() const { return storage->at(0); }
73 qsizetype parameterCount() const { return storage->size() - 1; }
74 QMetaType parameterMetaType(qsizetype i) const { return storage->at(i + 1); }
75
76 private:
77 const ArgTypeStorage *storage;
78 };
79
80 static void impl(int which, QSlotObjectBase *base, QObject *, void **metaArgs, bool *ret)
81 {
82 switch (which) {
83 case Destroy: {
84 delete static_cast<QQmlConnectionSlotDispatcher *>(base);
85 break;
86 }
87 case Call: {
88 QQmlConnectionSlotDispatcher *self = static_cast<QQmlConnectionSlotDispatcher *>(base);
89 QV4::ExecutionEngine *v4 = self->v4;
90 if (!v4)
91 break;
92
93 if (!self->enabled)
94 break;
95
96 TypedFunction typedFunction(&self->slotMetaTypes);
97 QV4::coerceAndCall(
98 v4, &typedFunction, metaArgs,
99 self->signalMetaTypes.data(), self->signalMetaTypes.size() - 1,
100 [&](void **argv, int) {
101 self->receiver->metaObject()->metacall(
102 self->receiver, QMetaObject::InvokeMetaMethod,
103 self->slotIndex, argv);
104 });
105
106 if (v4->hasException) {
107 QQmlError error = v4->catchExceptionAsQmlError();
108 if (QQmlEngine *qmlEngine = v4->qmlEngine()) {
109 QQmlEnginePrivate::get(qmlEngine)->warning(error);
110 } else {
111 QMessageLogger(
112 qPrintable(error.url().toString()), error.line(), nullptr)
113 .warning().noquote()
114 << error.toString();
115 }
116 }
117 break;
118 }
119 case Compare:
120 // We're not implementing the Compare protocol here. It's insane.
121 // QQmlConnectionSlotDispatcher compares false to anything. We use
122 // the regular QObject::disconnect with QMetaObject::Connection.
123 *ret = false;
124 break;
125 case NumOperations:
126 break;
127 }
128 };
129};
130
145
146/*!
147 \qmltype Connections
148 \inqmlmodule QtQml
149 \ingroup qtquick-interceptors
150 \brief Describes generalized connections to signals.
151
152 A Connections object creates a connection to a QML signal.
153
154 When connecting to signals in QML, the usual way is to create an
155 "on<Signal>" handler that reacts when a signal is received, like this:
156
157 \qml
158 MouseArea {
159 onClicked: (mouse)=> { foo(mouse) }
160 }
161 \endqml
162
163 However, it is not possible to connect to a signal in this way in some
164 cases, such as when:
165
166 \list
167 \li Multiple connections to the same signal are required
168 \li Creating connections outside the scope of the signal sender
169 \li Connecting to targets not defined in QML
170 \endlist
171
172 When any of these are needed, the Connections type can be used instead.
173
174 For example, the above code can be changed to use a Connections object,
175 like this:
176
177 \qml
178 MouseArea {
179 Connections {
180 function onClicked(mouse) { foo(mouse) }
181 }
182 }
183 \endqml
184
185 More generally, the Connections object can be a child of some object other than
186 the sender of the signal:
187
188 \qml
189 MouseArea {
190 id: area
191 }
192 // ...
193 \endqml
194 \qml
195 Connections {
196 target: area
197 function onClicked(mouse) { foo(mouse) }
198 }
199 \endqml
200
201 \note For backwards compatibility you can also specify the signal handlers
202 without \c{function}, like you would specify them directly in the target
203 object. This is not recommended. If you specify one signal handler this way,
204 then all signal handlers specified as \c{function} in the same Connections
205 object are ignored.
206
207 \sa {Qt Qml}
208*/
209QQmlConnections::QQmlConnections(QObject *parent) :
210 QObject(*(new QQmlConnectionsPrivate), parent)
211{
212}
213
214QQmlConnections::~QQmlConnections()
215{
216 Q_D(QQmlConnections);
217
218 // The slot dispatchers hold cyclic references to their connections. Clear them.
219 for (const auto &bound : std::as_const(d->boundsignals)) {
220 if (QQmlConnectionSlotDispatcher *dispatcher = bound.isT2() ? bound.asT2() : nullptr) {
221 // No need to explicitly disconnect anymore since 'this' is the receiver.
222 // But to be safe, explicitly break any cyclic references between the connection
223 // and the slot object.
224 dispatcher->connection = {};
225 dispatcher->destroyIfLastRef();
226 }
227 }
228}
229
230/*!
231 \qmlproperty QtObject QtQml::Connections::target
232 This property holds the object that sends the signal.
233
234 If this property is not set, the \c target defaults to the parent of the Connection.
235
236 If set to null, no connection is made and any signal handlers are ignored
237 until the target is not null.
238*/
239QObject *QQmlConnections::target() const
240{
241 Q_D(const QQmlConnections);
242 return d->targetSet ? d->target.data() : parent();
243}
244
246{
247public:
248 QQmlBoundSignalDeleter(QQmlBoundSignal *signal) : m_signal(signal) { m_signal->removeFromObject(); }
249 ~QQmlBoundSignalDeleter() { delete m_signal; }
250
251private:
252 QQmlBoundSignal *m_signal;
253};
254
255void QQmlConnections::setTarget(QObject *obj)
256{
257 Q_D(QQmlConnections);
258 if (d->targetSet && d->target == obj)
259 return;
260 d->targetSet = true; // even if setting to 0, it is *set*
261 for (const auto &bound : std::as_const(d->boundsignals)) {
262 // It is possible that target is being changed due to one of our signal
263 // handlers -> use deleteLater().
264 if (QQmlBoundSignal *signal = bound.isT1() ? bound.asT1() : nullptr) {
265 if (signal->isNotifying())
266 (new QQmlBoundSignalDeleter(signal))->deleteLater();
267 else
268 delete signal;
269 } else {
270 QQmlConnectionSlotDispatcher *dispatcher = bound.asT2();
271 QObject::disconnect(std::exchange(dispatcher->connection, {}));
272 dispatcher->destroyIfLastRef();
273 }
274 }
275 d->boundsignals.clear();
276 d->target = obj;
277 connectSignals();
278 emit targetChanged();
279}
280
281/*!
282 \qmlproperty bool QtQml::Connections::enabled
283 \since 5.7
284
285 This property holds whether the item accepts change events.
286
287 By default, this property is \c true.
288*/
289bool QQmlConnections::isEnabled() const
290{
291 Q_D(const QQmlConnections);
292 return d->enabled;
293}
294
295void QQmlConnections::setEnabled(bool enabled)
296{
297 Q_D(QQmlConnections);
298 if (d->enabled == enabled)
299 return;
300
301 d->enabled = enabled;
302
303 for (const auto &bound : std::as_const(d->boundsignals)) {
304 if (QQmlBoundSignal *signal = bound.isT1() ? bound.asT1() : nullptr)
305 signal->setEnabled(d->enabled);
306 else
307 bound.asT2()->enabled = enabled;
308 }
309
310 emit enabledChanged();
311}
312
313/*!
314 \qmlproperty bool QtQml::Connections::ignoreUnknownSignals
315
316 Normally, a connection to a non-existent signal produces runtime errors.
317
318 If this property is set to \c true, such errors are ignored.
319 This is useful if you intend to connect to different types of objects, handling
320 a different set of signals for each object.
321*/
322bool QQmlConnections::ignoreUnknownSignals() const
323{
324 Q_D(const QQmlConnections);
325 return d->ignoreUnknownSignals;
326}
327
328void QQmlConnections::setIgnoreUnknownSignals(bool ignore)
329{
330 Q_D(QQmlConnections);
331 d->ignoreUnknownSignals = ignore;
332}
333
335 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
336 const QList<const QV4::CompiledData::Binding *> &props)
337{
338 for (int ii = 0; ii < props.size(); ++ii) {
339 const QV4::CompiledData::Binding *binding = props.at(ii);
340 const QString &propName = compilationUnit->stringAt(binding->propertyNameIndex);
341
342 if (!QQmlSignalNames::isHandlerName(propName)) {
343 error(props.at(ii), QQmlConnections::tr("Cannot assign to non-existent property \"%1\"").arg(propName));
344 return;
345 }
346
347 if (binding->type() == QV4::CompiledData::Binding::Type_Script)
348 continue;
349
350 if (binding->type() >= QV4::CompiledData::Binding::Type_Object) {
351 const QV4::CompiledData::Object *target = compilationUnit->objectAt(binding->value.objectIndex);
352 if (!compilationUnit->stringAt(target->inheritedTypeNameIndex).isEmpty())
353 error(binding, QQmlConnections::tr("Connections: nested objects not allowed"));
354 else
355 error(binding, QQmlConnections::tr("Connections: syntax error"));
356 return;
357 }
358
359 error(binding, QQmlConnections::tr("Connections: script expected"));
360 return;
361 }
362}
363
364void QQmlConnectionsParser::applyBindings(QObject *object, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
365{
367 static_cast<QQmlConnectionsPrivate *>(QObjectPrivate::get(object));
368 p->compilationUnit = compilationUnit;
369 p->bindings = bindings;
370}
371
372void QQmlConnections::connectSignals()
373{
374 Q_D(QQmlConnections);
375 if (!d->componentcomplete || (d->targetSet && !target()))
376 return;
377
378 if (d->bindings.isEmpty()) {
379 connectSignalsToMethods();
380 } else {
381 if (lcQmlConnections().isWarningEnabled()) {
382 qmlWarning(this) << tr("Implicitly defined onFoo properties in Connections are deprecated. "
383 "Use this syntax instead: function onFoo(<arguments>) { ... }");
384 }
385 connectSignalsToBindings();
386 }
387}
388
389void QQmlConnections::connectSignalsToMethods()
390{
391 Q_D(QQmlConnections);
392
393 QObject *target = this->target();
394 QQmlData *ddata = QQmlData::get(this);
395 if (!ddata || !ddata->propertyCache || !ddata->context || !ddata->context->isValid())
396 return;
397
398 QV4::ExecutionEngine *engine = ddata->context->engine()->handle();
399
400 QQmlRefPointer<QQmlContextData> ctxtdata = ddata->outerContext;
401 for (int i = ddata->propertyCache->methodOffset(),
402 end = ddata->propertyCache->methodOffset() + ddata->propertyCache->methodCount();
403 i < end;
404 ++i) {
405
406 const QQmlPropertyData *handler = ddata->propertyCache->method(i);
407 if (!handler)
408 continue;
409
410 const QString propName = handler->name(this);
411
412 QQmlProperty prop(target, propName);
413 if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) {
414 QQmlPropertyPrivate *propPrivate = QQmlPropertyPrivate::get(prop);
415 QV4::Scope scope(engine);
416 QV4::ScopedContext global(scope, engine->rootContext());
417
418 if (QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(this)) {
419 const int signalIndex = propPrivate->signalIndex();
420 auto *signal = new QQmlBoundSignal(target, signalIndex, this, qmlEngine(this));
421 signal->setEnabled(d->enabled);
422
423 QV4::Scoped<QV4::JavaScriptFunctionObject> method(
424 scope, vmeMetaObject->vmeMethod(handler->coreIndex()));
425
426 QQmlBoundSignalExpression *expression = ctxtdata
427 ? new QQmlBoundSignalExpression(
428 target, signalIndex, ctxtdata, this, method->function())
429 : nullptr;
430
431 signal->takeExpression(expression);
432 d->boundsignals += signal;
433 } else {
434 QQmlConnectionSlotDispatcher *slot = new QQmlConnectionSlotDispatcher(
435 scope.engine, target, propPrivate->core, this, *handler, d->enabled);
436 slot->connection = QObjectPrivate::connect(
437 target, prop.index(), slot, Qt::AutoConnection);
438 slot->ref();
439 d->boundsignals += slot;
440 }
441 } else if (!d->ignoreUnknownSignals
442 && propName.startsWith(QLatin1String("on")) && propName.size() > 2
443 && propName.at(2).isUpper()) {
444 qmlWarning(this) << tr("Detected function \"%1\" in Connections element. "
445 "This is probably intended to be a signal handler but no "
446 "signal of the target matches the name.").arg(propName);
447 }
448 }
449}
450
451// TODO: Drop this as soon as we can
452void QQmlConnections::connectSignalsToBindings()
453{
454 Q_D(QQmlConnections);
455 QObject *target = this->target();
456 QQmlData *ddata = QQmlData::get(this);
457 QQmlRefPointer<QQmlContextData> ctxtdata = ddata ? ddata->outerContext : nullptr;
458
459 for (const QV4::CompiledData::Binding *binding : std::as_const(d->bindings)) {
460 Q_ASSERT(binding->type() == QV4::CompiledData::Binding::Type_Script);
461 QString propName = d->compilationUnit->stringAt(binding->propertyNameIndex);
462
463 QQmlProperty prop(target, propName);
464 if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) {
465 int signalIndex = QQmlPropertyPrivate::get(prop)->signalIndex();
466 QQmlBoundSignal *signal =
467 new QQmlBoundSignal(target, signalIndex, this, qmlEngine(this));
468 signal->setEnabled(d->enabled);
469
470 auto f = d->compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex];
471 QQmlBoundSignalExpression *expression =
472 ctxtdata ? new QQmlBoundSignalExpression(target, signalIndex, ctxtdata, this, f)
473 : nullptr;
474 signal->takeExpression(expression);
475 d->boundsignals += signal;
476 } else {
477 if (!d->ignoreUnknownSignals)
478 qmlWarning(this) << tr("Cannot assign to non-existent property \"%1\"").arg(propName);
479 }
480 }
481}
482
483void QQmlConnections::classBegin()
484{
485 Q_D(QQmlConnections);
486 d->componentcomplete=false;
487}
488
489void QQmlConnections::componentComplete()
490{
491 Q_D(QQmlConnections);
492 d->componentcomplete=true;
493 connectSignals();
494}
495
496QT_END_NAMESPACE
497
498#include "moc_qqmlconnections_p.cpp"
QQmlBoundSignalDeleter(QQmlBoundSignal *signal)
void applyBindings(QObject *object, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &compilationUnit, const QList< const QV4::CompiledData::Binding * > &bindings) override
void verifyBindings(const QQmlRefPointer< QV4::CompiledData::CompilationUnit > &compilationUnit, const QList< const QV4::CompiledData::Binding * > &props) override
QList< const QV4::CompiledData::Binding * > bindings
QQmlGuard< QObject > target
QList< QBiPointer< QQmlBoundSignal, QQmlConnectionSlotDispatcher > > boundsignals
QQmlRefPointer< QV4::ExecutableCompilationUnit > compilationUnit
Combined button and popup list for selecting options.