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
qqmlpropertybinding.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 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
5
6#include <private/qqmlbinding_p.h>
7#include <private/qqmlglobal_p.h>
8#include <private/qqmlscriptstring_p.h>
9#include <private/qv4functionobject_p.h>
10#include <private/qv4jscall_p.h>
11#include <private/qv4qmlcontext_p.h>
12
13#include <QtQml/qqmlinfo.h>
14
15#include <QtCore/qloggingcategory.h>
16
18
19using namespace Qt::Literals::StringLiterals;
20
21Q_STATIC_LOGGING_CATEGORY(lcQQPropertyBinding, "qt.qml.propertybinding");
22
23QUntypedPropertyBinding QQmlPropertyBinding::create(const QQmlPropertyData *pd, QV4::Function *function,
24 QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt,
25 QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex)
26{
27 Q_ASSERT(pd);
28 return create(pd->propType(), function, obj, ctxt, scope, target, targetIndex);
29}
30
31QUntypedPropertyBinding QQmlPropertyBinding::create(QMetaType propertyType, QV4::Function *function,
32 QObject *obj,
33 const QQmlRefPointer<QQmlContextData> &ctxt,
34 QV4::ExecutionContext *scope, QObject *target,
35 QQmlPropertyIndex targetIndex)
36{
37 auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment()
38 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
39 auto binding = new (buffer) QQmlPropertyBinding(propertyType, target, targetIndex,
40 TargetData::WithoutBoundFunction);
41 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
42 Q_ASSERT(binding->jsExpression() == js);
43 Q_ASSERT(js->asBinding() == binding);
44 Q_UNUSED(js);
45 binding->jsExpression()->setNotifyOnValueChanged(true);
46 binding->jsExpression()->setContext(ctxt);
47 binding->jsExpression()->setScopeObject(obj);
48 binding->jsExpression()->setupFunction(scope, function);
49 return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
50}
51
52QUntypedPropertyBinding QQmlPropertyBinding::createFromCodeString(const QQmlPropertyData *pd, const QString& str, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber, QObject *target, QQmlPropertyIndex targetIndex)
53{
54 auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment()
55 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
56 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, TargetData::WithoutBoundFunction);
57 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
58 Q_ASSERT(binding->jsExpression() == js);
59 Q_ASSERT(js->asBinding() == binding);
60 Q_UNUSED(js);
61 binding->jsExpression()->setNotifyOnValueChanged(true);
62 binding->jsExpression()->setContext(ctxt);
63 binding->jsExpression()->createQmlBinding(ctxt, obj, str, url, lineNumber);
64 return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
65}
66
67QUntypedPropertyBinding QQmlPropertyBinding::createFromScriptString(const QQmlPropertyData *property, const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt, QObject *target, QQmlPropertyIndex targetIndex)
68{
69 const QQmlScriptStringPrivate *scriptPrivate = script.d.data();
70 // without a valid context, we cannot create anything
71 if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) {
72 return {};
73 }
74
75 auto scopeObject = obj ? obj : scriptPrivate->scope;
76
77 QV4::Function *runtimeFunction = nullptr;
78 QString url;
79 QQmlRefPointer<QQmlContextData> ctxtdata = QQmlContextData::get(scriptPrivate->context);
80 QQmlEnginePrivate *engine = QQmlEnginePrivate::get(scriptPrivate->context->engine());
81 if (engine && ctxtdata && !ctxtdata->urlString().isEmpty() && ctxtdata->typeCompilationUnit()) {
82 url = ctxtdata->urlString();
83 if (scriptPrivate->bindingId != QQmlBinding::Invalid)
84 runtimeFunction = ctxtdata->typeCompilationUnit()->runtimeFunctions.at(scriptPrivate->bindingId);
85 }
86 // Do we actually have a function in the script string? If not, this becomes createCodeFromString
87 if (!runtimeFunction)
88 return createFromCodeString(property, scriptPrivate->script, obj, ctxtdata, url, scriptPrivate->lineNumber, target, targetIndex);
89
90 auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment()
91 + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
92 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(property->propType()), target, targetIndex, TargetData::WithoutBoundFunction);
93 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS();
94 Q_ASSERT(binding->jsExpression() == js);
95 Q_ASSERT(js->asBinding() == binding);
96 js->setContext(QQmlContextData::get(ctxt ? ctxt : scriptPrivate->context));
97
98 QV4::ExecutionEngine *v4 = engine->v4Engine.get();
99 QV4::Scope scope(v4);
100 QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(v4->rootContext(), ctxtdata, scopeObject));
101 js->setupFunction(qmlContext, runtimeFunction);
102 return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
103}
104
105QUntypedPropertyBinding QQmlPropertyBinding::createFromBoundFunction(const QQmlPropertyData *pd, QV4::BoundFunction *function, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex)
106{
107 auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment()
108 + sizeof(QQmlPropertyBindingJSForBoundFunction)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[]
109 auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, TargetData::HasBoundFunction);
110 auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJSForBoundFunction();
111 Q_ASSERT(binding->jsExpression() == js);
112 Q_ASSERT(js->asBinding() == binding);
113 Q_UNUSED(js);
114 binding->jsExpression()->setNotifyOnValueChanged(true);
115 binding->jsExpression()->setContext(ctxt);
116 binding->jsExpression()->setScopeObject(obj);
117 binding->jsExpression()->setupFunction(scope, function->function());
118 js->m_boundFunction.set(function->engine(), *function);
119 return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data()));
120}
121
122/*!
123 \fn bool QQmlPropertyBindingJS::hasDependencies()
124 \internal
125
126 Returns true if this binding has dependencies.
127 Dependencies can be either QProperty dependencies or dependencies of
128 the JS expression (aka activeGuards). Translations end up as a QProperty
129 dependency, so they do not need any special handling
130 Note that a QQmlPropertyBinding never stores qpropertyChangeTriggers.
131 */
132
133
134void QQmlPropertyBindingJS::expressionChanged()
135{
136 if (!hasValidContext())
137 return;
138
139 auto binding = asBinding();
140 if (!binding->propertyDataPtr)
141 return;
142 const auto currentTag = m_error.tag();
143 if (currentTag == InEvaluationLoop) {
144 QQmlError err;
145 auto location = QQmlJavaScriptExpression::sourceLocation();
146 err.setUrl(QUrl{location.sourceFile});
147 err.setLine(location.line);
148 err.setColumn(location.column);
149 const auto ctxt = context();
150 QQmlEngine *engine = ctxt ? ctxt->engine() : nullptr;
151 if (engine)
152 err.setDescription(asBinding()->createBindingLoopErrorDescription());
153 else
154 err.setDescription(QString::fromLatin1("Binding loop detected"));
155 err.setObject(asBinding()->target());
156 qmlWarning(this->scopeObject(), err);
157 return;
158 }
159 m_error.setTag(InEvaluationLoop);
160 PendingBindingObserverList bindingObservers;
161 binding->evaluateRecursive(bindingObservers);
162 binding->notifyNonRecursive(bindingObservers);
163 m_error.setTag(NoTag);
164}
165
166QQmlPropertyBinding::QQmlPropertyBinding(QMetaType mt, QObject *target, QQmlPropertyIndex targetIndex, TargetData::BoundFunction hasBoundFunction)
167 : QPropertyBindingPrivate(mt,
168 bindingFunctionVTableForQQmlPropertyBinding(mt),
169 QPropertyBindingSourceLocation(), true)
170{
171 static_assert (std::is_trivially_destructible_v<TargetData>);
172 static_assert (sizeof(TargetData) + sizeof(DeclarativeErrorCallback) <= sizeof(QPropertyBindingSourceLocation));
173 static_assert (alignof(TargetData) <= alignof(QPropertyBindingSourceLocation));
174 const auto state = hasBoundFunction ? TargetData::HasBoundFunction : TargetData::WithoutBoundFunction;
175 new (&declarativeExtraData) TargetData {target, targetIndex, state};
176 errorCallBack = bindingErrorCallback;
177}
178
179static QtPrivate::QPropertyBindingData *bindingDataFromPropertyData(QUntypedPropertyData *dataPtr, QMetaType type)
180{
181 // XXX Qt 7: We need a clean way to access the binding data
182 /* This function makes the (dangerous) assumption that if we could not get the binding data
183 from the binding storage, we must have been handed a QProperty.
184 This does hold for anything a user could write, as there the only ways of providing a bindable property
185 are to use the Q_X_BINDABLE macros, or to directly expose a QProperty.
186 As long as we can ensure that any "fancier" property we implement is not resettable, we should be fine.
187 We procede to calculate the address of the binding data pointer from the address of the data pointer
188 */
189 Q_ASSERT(dataPtr);
190 std::byte *qpropertyPointer = reinterpret_cast<std::byte *>(dataPtr);
191 qpropertyPointer += type.sizeOf();
192 constexpr auto alignment = alignof(QtPrivate::QPropertyBindingData *);
193 auto aligned = (quintptr(qpropertyPointer) + alignment - 1) & ~(alignment - 1); // ensure pointer alignment
194 return reinterpret_cast<QtPrivate::QPropertyBindingData *>(aligned);
195}
196
197void QQmlPropertyBinding::handleUndefinedAssignment(QQmlEnginePrivate *ep, void *dataPtr)
198{
199 const QQmlPropertyData *propertyData = nullptr;
200 QQmlPropertyData valueTypeData;
201 QQmlData *data = QQmlData::get(target(), false);
202 Q_ASSERT(data);
203 if (Q_UNLIKELY(!data->propertyCache))
204 data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject());
205
206 propertyData = data->propertyCache->property(targetIndex().coreIndex());
207 Q_ASSERT(propertyData);
208 Q_ASSERT(!targetIndex().hasValueTypeIndex());
209 QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr);
210 // helper function for writing back value into dataPtr
211 // this is necessary for QObjectCompatProperty, which doesn't give us the "real" dataPtr
212 // if we don't write the correct value, we would otherwise set the default constructed value
213 auto writeBackCurrentValue = [&](QVariant &&currentValue) {
214 if (currentValue.metaType() != valueMetaType())
215 currentValue.convert(valueMetaType());
216 auto metaType = valueMetaType();
217 metaType.destruct(dataPtr);
218 metaType.construct(dataPtr, currentValue.constData());
219 };
220 if (prop.isResettable()) {
221 // Normally a reset would remove any existing binding; but now we need to keep the binding alive
222 // to handle the case where this binding becomes defined again
223 // We therefore detach the binding, call reset, and reattach again
224 const auto storage = qGetBindingStorage(target());
225 auto bindingData = storage->bindingData(propertyDataPtr);
226 if (!bindingData)
227 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
228 QPropertyBindingDataPointer bindingDataPointer{bindingData};
229 auto firstObserver = takeObservers();
230 bindingData->d_ref() = 0;
231 if (firstObserver) {
232 bindingDataPointer.setObservers(firstObserver.ptr);
233 }
234 Q_ASSERT(!bindingData->hasBinding());
235 setIsUndefined(true);
236 //suspend binding evaluation state for reset and subsequent read
237 auto state = QtPrivate::suspendCurrentBindingStatus();
238 prop.reset(); // May re-allocate the bindingData
239 QVariant currentValue = QVariant(prop.propertyMetaType(), propertyDataPtr);
240 QtPrivate::restoreBindingStatus(state);
241 writeBackCurrentValue(std::move(currentValue));
242
243 // Re-fetch binding data
244 bindingData = storage->bindingData(propertyDataPtr);
245 if (!bindingData)
246 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
247 bindingDataPointer = QPropertyBindingDataPointer {bindingData};
248
249 // reattach the binding (without causing a new notification)
250 if (Q_UNLIKELY(bindingData->d() & QtPrivate::QPropertyBindingData::BindingBit)) {
251 qCWarning(lcQQPropertyBinding) << "Resetting " << prop.name() << "due to the binding becoming undefined caused a new binding to be installed\n"
252 << "The old binding binding will be abandoned";
253 deref();
254 return;
255 }
256 // reset might have changed observers (?), so refresh firstObserver
257 firstObserver = bindingDataPointer.firstObserver();
258 bindingData->d_ref() = reinterpret_cast<quintptr>(this) | QtPrivate::QPropertyBindingData::BindingBit;
259 if (firstObserver)
260 prependObserver(firstObserver);
261 } else {
262 QQmlError qmlError;
263 auto location = jsExpression()->sourceLocation();
264 qmlError.setColumn(location.column);
265 qmlError.setLine(location.line);
266 qmlError.setUrl(QUrl {location.sourceFile});
267 const QString description = QStringLiteral(R"(QML %1: Unable to assign [undefined] to "%2")").arg(QQmlMetaType::prettyTypeName(target()) , prop.name());
268 qmlError.setDescription(description);
269 qmlError.setObject(target());
270 ep->warning(qmlError);
271 }
272}
273
274QString QQmlPropertyBinding::createBindingLoopErrorDescription()
275{
276 const QQmlPropertyData *propertyData = nullptr;
277 QQmlPropertyData valueTypeData;
278 QQmlData *data = QQmlData::get(target(), false);
279 Q_ASSERT(data);
280 if (Q_UNLIKELY(!data->propertyCache))
281 data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject());
282
283 propertyData = data->propertyCache->property(targetIndex().coreIndex());
284 Q_ASSERT(propertyData);
285 Q_ASSERT(!targetIndex().hasValueTypeIndex());
286 QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr);
287 return R"(QML %1: Binding loop detected for property "%2")"_L1.arg(QQmlMetaType::prettyTypeName(target()) , prop.name());
288}
289
290void QQmlPropertyBinding::bindingErrorCallback(QPropertyBindingPrivate *that)
291{
292 auto This = static_cast<QQmlPropertyBinding *>(that);
293 auto target = This->target();
294 auto engine = qmlEngine(target);
295 if (!engine)
296 return;
297
298 auto error = This->bindingError();
299 QQmlError qmlError;
300 auto location = This->jsExpression()->sourceLocation();
301 qmlError.setColumn(location.column);
302 qmlError.setLine(location.line);
303 qmlError.setUrl(QUrl {location.sourceFile});
304 auto description = error.description();
305 if (error.type() == QPropertyBindingError::BindingLoop) {
306 description = This->createBindingLoopErrorDescription();
307 }
308 qmlError.setDescription(description);
309 qmlError.setObject(target);
310 QQmlEnginePrivate::get(engine)->warning(qmlError);
311}
312
313template<typename TranslateWithUnit>
315 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
316 TranslateWithUnit &&translateWithUnit)
317{
318 return [compilationUnit, translateWithUnit](QMetaType metaType, void *dataPtr) -> bool {
319 // Create a dependency to the translationLanguage
320 QQmlEnginePrivate::get(compilationUnit->engine)->translationLanguage.value();
321
322 QVariant resultVariant(translateWithUnit(compilationUnit));
323 if (metaType != QMetaType::fromType<QString>())
324 resultVariant.convert(metaType);
325
326 const bool hasChanged = !metaType.equals(resultVariant.constData(), dataPtr);
327 metaType.destruct(dataPtr);
328 metaType.construct(dataPtr, resultVariant.constData());
329 return hasChanged;
330 };
331}
332
333QUntypedPropertyBinding QQmlTranslationPropertyBinding::create(
334 const QQmlPropertyData *pd,
335 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
336 const QV4::CompiledData::Binding *binding)
337{
338 auto translationBinding = qQmlTranslationPropertyBindingCreateBinding(
339 compilationUnit,
340 [binding](const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) {
341 return compilationUnit->bindingValueAsString(binding);
342 });
343
344 return QUntypedPropertyBinding(QMetaType(pd->propType()), translationBinding,
345 QPropertyBindingSourceLocation());
346}
347
348QUntypedPropertyBinding QQmlTranslationPropertyBinding::create(
349 const QMetaType &propertyType,
350 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
351 const QQmlTranslation &translationData)
352{
353 auto translationBinding = qQmlTranslationPropertyBindingCreateBinding(
354 compilationUnit,
355 [translationData](
356 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) {
357 Q_UNUSED(compilationUnit);
358 return translationData.translate();
359 });
360
361 return QUntypedPropertyBinding(propertyType, translationBinding,
362 QPropertyBindingSourceLocation());
363}
364
365QV4::ReturnedValue QQmlPropertyBindingJSForBoundFunction::evaluate(bool *isUndefined)
366{
367 QV4::ExecutionEngine *v4 = engine()->handle();
368 int argc = 0;
369 const QV4::Value *argv = nullptr;
370 const QV4::Value *thisObject = nullptr;
371 QV4::BoundFunction *b = nullptr;
372 if ((b = m_boundFunction.as<QV4::BoundFunction>())) {
373 QV4::Heap::MemberData *args = b->boundArgs();
374 if (args) {
375 argc = args->values.size;
376 argv = args->values.data();
377 }
378 thisObject = &b->d()->boundThis;
379 }
380 QV4::Scope scope(v4);
381 QV4::JSCallData jsCall(thisObject, argv, argc);
382
383 return QQmlJavaScriptExpression::evaluate(jsCall.callData(scope), isUndefined);
384}
385
386QT_END_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")
static QtPrivate::QPropertyBindingData * bindingDataFromPropertyData(QUntypedPropertyData *dataPtr, QMetaType type)
auto qQmlTranslationPropertyBindingCreateBinding(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &compilationUnit, TranslateWithUnit &&translateWithUnit)