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();
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 auto binding = asBinding();
137 if (!binding->propertyDataPtr)
138 return;
139 const auto currentTag = m_error.tag();
140 if (currentTag == InEvaluationLoop) {
141 QQmlError err;
142 auto location = QQmlJavaScriptExpression::sourceLocation();
143 err.setUrl(QUrl{location.sourceFile});
144 err.setLine(location.line);
145 err.setColumn(location.column);
146 const auto ctxt = context();
147 QQmlEngine *engine = ctxt ? ctxt->engine() : nullptr;
148 if (engine)
149 err.setDescription(asBinding()->createBindingLoopErrorDescription());
150 else
151 err.setDescription(QString::fromLatin1("Binding loop detected"));
152 err.setObject(asBinding()->target());
153 qmlWarning(this->scopeObject(), err);
154 return;
155 }
156 m_error.setTag(InEvaluationLoop);
157 PendingBindingObserverList bindingObservers;
158 binding->evaluateRecursive(bindingObservers);
159 binding->notifyNonRecursive(bindingObservers);
160 m_error.setTag(NoTag);
161}
162
163QQmlPropertyBinding::QQmlPropertyBinding(QMetaType mt, QObject *target, QQmlPropertyIndex targetIndex, TargetData::BoundFunction hasBoundFunction)
164 : QPropertyBindingPrivate(mt,
165 bindingFunctionVTableForQQmlPropertyBinding(mt),
166 QPropertyBindingSourceLocation(), true)
167{
168 static_assert (std::is_trivially_destructible_v<TargetData>);
169 static_assert (sizeof(TargetData) + sizeof(DeclarativeErrorCallback) <= sizeof(QPropertyBindingSourceLocation));
170 static_assert (alignof(TargetData) <= alignof(QPropertyBindingSourceLocation));
171 const auto state = hasBoundFunction ? TargetData::HasBoundFunction : TargetData::WithoutBoundFunction;
172 new (&declarativeExtraData) TargetData {target, targetIndex, state};
173 errorCallBack = bindingErrorCallback;
174}
175
176static QtPrivate::QPropertyBindingData *bindingDataFromPropertyData(QUntypedPropertyData *dataPtr, QMetaType type)
177{
178 // XXX Qt 7: We need a clean way to access the binding data
179 /* This function makes the (dangerous) assumption that if we could not get the binding data
180 from the binding storage, we must have been handed a QProperty.
181 This does hold for anything a user could write, as there the only ways of providing a bindable property
182 are to use the Q_X_BINDABLE macros, or to directly expose a QProperty.
183 As long as we can ensure that any "fancier" property we implement is not resettable, we should be fine.
184 We procede to calculate the address of the binding data pointer from the address of the data pointer
185 */
186 Q_ASSERT(dataPtr);
187 std::byte *qpropertyPointer = reinterpret_cast<std::byte *>(dataPtr);
188 qpropertyPointer += type.sizeOf();
189 constexpr auto alignment = alignof(QtPrivate::QPropertyBindingData *);
190 auto aligned = (quintptr(qpropertyPointer) + alignment - 1) & ~(alignment - 1); // ensure pointer alignment
191 return reinterpret_cast<QtPrivate::QPropertyBindingData *>(aligned);
192}
193
194void QQmlPropertyBinding::handleUndefinedAssignment(QQmlEnginePrivate *ep, void *dataPtr)
195{
196 const QQmlPropertyData *propertyData = nullptr;
197 QQmlPropertyData valueTypeData;
198 QQmlData *data = QQmlData::get(target(), false);
199 Q_ASSERT(data);
200 if (Q_UNLIKELY(!data->propertyCache))
201 data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject());
202
203 propertyData = data->propertyCache->property(targetIndex().coreIndex());
204 Q_ASSERT(propertyData);
205 Q_ASSERT(!targetIndex().hasValueTypeIndex());
206 QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr);
207 // helper function for writing back value into dataPtr
208 // this is necessary for QObjectCompatProperty, which doesn't give us the "real" dataPtr
209 // if we don't write the correct value, we would otherwise set the default constructed value
210 auto writeBackCurrentValue = [&](QVariant &&currentValue) {
211 if (currentValue.metaType() != valueMetaType())
212 currentValue.convert(valueMetaType());
213 auto metaType = valueMetaType();
214 metaType.destruct(dataPtr);
215 metaType.construct(dataPtr, currentValue.constData());
216 };
217 if (prop.isResettable()) {
218 // Normally a reset would remove any existing binding; but now we need to keep the binding alive
219 // to handle the case where this binding becomes defined again
220 // We therefore detach the binding, call reset, and reattach again
221 const auto storage = qGetBindingStorage(target());
222 auto bindingData = storage->bindingData(propertyDataPtr);
223 if (!bindingData)
224 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
225 QPropertyBindingDataPointer bindingDataPointer{bindingData};
226 auto firstObserver = takeObservers();
227 bindingData->d_ref() = 0;
228 if (firstObserver) {
229 bindingDataPointer.setObservers(firstObserver.ptr);
230 }
231 Q_ASSERT(!bindingData->hasBinding());
232 setIsUndefined(true);
233 //suspend binding evaluation state for reset and subsequent read
234 auto state = QtPrivate::suspendCurrentBindingStatus();
235 prop.reset(); // May re-allocate the bindingData
236 QVariant currentValue = QVariant(prop.propertyMetaType(), propertyDataPtr);
237 QtPrivate::restoreBindingStatus(state);
238 writeBackCurrentValue(std::move(currentValue));
239
240 // Re-fetch binding data
241 bindingData = storage->bindingData(propertyDataPtr);
242 if (!bindingData)
243 bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType());
244 bindingDataPointer = QPropertyBindingDataPointer {bindingData};
245
246 // reattach the binding (without causing a new notification)
247 if (Q_UNLIKELY(bindingData->d() & QtPrivate::QPropertyBindingData::BindingBit)) {
248 qCWarning(lcQQPropertyBinding) << "Resetting " << prop.name() << "due to the binding becoming undefined caused a new binding to be installed\n"
249 << "The old binding binding will be abandoned";
250 deref();
251 return;
252 }
253 // reset might have changed observers (?), so refresh firstObserver
254 firstObserver = bindingDataPointer.firstObserver();
255 bindingData->d_ref() = reinterpret_cast<quintptr>(this) | QtPrivate::QPropertyBindingData::BindingBit;
256 if (firstObserver)
257 prependObserver(firstObserver);
258 } else {
259 QQmlError qmlError;
260 auto location = jsExpression()->sourceLocation();
261 qmlError.setColumn(location.column);
262 qmlError.setLine(location.line);
263 qmlError.setUrl(QUrl {location.sourceFile});
264 const QString description = QStringLiteral(R"(QML %1: Unable to assign [undefined] to "%2")").arg(QQmlMetaType::prettyTypeName(target()) , prop.name());
265 qmlError.setDescription(description);
266 qmlError.setObject(target());
267 ep->warning(qmlError);
268 }
269}
270
271QString QQmlPropertyBinding::createBindingLoopErrorDescription()
272{
273 const QQmlPropertyData *propertyData = nullptr;
274 QQmlPropertyData valueTypeData;
275 QQmlData *data = QQmlData::get(target(), false);
276 Q_ASSERT(data);
277 if (Q_UNLIKELY(!data->propertyCache))
278 data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject());
279
280 propertyData = data->propertyCache->property(targetIndex().coreIndex());
281 Q_ASSERT(propertyData);
282 Q_ASSERT(!targetIndex().hasValueTypeIndex());
283 QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr);
284 return R"(QML %1: Binding loop detected for property "%2")"_L1.arg(QQmlMetaType::prettyTypeName(target()) , prop.name());
285}
286
287void QQmlPropertyBinding::bindingErrorCallback(QPropertyBindingPrivate *that)
288{
289 auto This = static_cast<QQmlPropertyBinding *>(that);
290 auto target = This->target();
291 auto engine = qmlEngine(target);
292 if (!engine)
293 return;
294
295 auto error = This->bindingError();
296 QQmlError qmlError;
297 auto location = This->jsExpression()->sourceLocation();
298 qmlError.setColumn(location.column);
299 qmlError.setLine(location.line);
300 qmlError.setUrl(QUrl {location.sourceFile});
301 auto description = error.description();
302 if (error.type() == QPropertyBindingError::BindingLoop) {
303 description = This->createBindingLoopErrorDescription();
304 }
305 qmlError.setDescription(description);
306 qmlError.setObject(target);
307 QQmlEnginePrivate::get(engine)->warning(qmlError);
308}
309
310template<typename TranslateWithUnit>
312 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
313 TranslateWithUnit &&translateWithUnit)
314{
315 return [compilationUnit, translateWithUnit](QMetaType metaType, void *dataPtr) -> bool {
316 // Create a dependency to the translationLanguage
317 QQmlEnginePrivate::get(compilationUnit->engine)->translationLanguage.value();
318
319 QVariant resultVariant(translateWithUnit(compilationUnit));
320 if (metaType != QMetaType::fromType<QString>())
321 resultVariant.convert(metaType);
322
323 const bool hasChanged = !metaType.equals(resultVariant.constData(), dataPtr);
324 metaType.destruct(dataPtr);
325 metaType.construct(dataPtr, resultVariant.constData());
326 return hasChanged;
327 };
328}
329
330QUntypedPropertyBinding QQmlTranslationPropertyBinding::create(
331 const QQmlPropertyData *pd,
332 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
333 const QV4::CompiledData::Binding *binding)
334{
335 auto translationBinding = qQmlTranslationPropertyBindingCreateBinding(
336 compilationUnit,
337 [binding](const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) {
338 return compilationUnit->bindingValueAsString(binding);
339 });
340
341 return QUntypedPropertyBinding(QMetaType(pd->propType()), translationBinding,
342 QPropertyBindingSourceLocation());
343}
344
345QUntypedPropertyBinding QQmlTranslationPropertyBinding::create(
346 const QMetaType &propertyType,
347 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
348 const QQmlTranslation &translationData)
349{
350 auto translationBinding = qQmlTranslationPropertyBindingCreateBinding(
351 compilationUnit,
352 [translationData](
353 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) {
354 Q_UNUSED(compilationUnit);
355 return translationData.translate();
356 });
357
358 return QUntypedPropertyBinding(propertyType, translationBinding,
359 QPropertyBindingSourceLocation());
360}
361
362QV4::ReturnedValue QQmlPropertyBindingJSForBoundFunction::evaluate(bool *isUndefined)
363{
364 QV4::ExecutionEngine *v4 = engine()->handle();
365 int argc = 0;
366 const QV4::Value *argv = nullptr;
367 const QV4::Value *thisObject = nullptr;
368 QV4::BoundFunction *b = nullptr;
369 if ((b = m_boundFunction.as<QV4::BoundFunction>())) {
370 QV4::Heap::MemberData *args = b->boundArgs();
371 if (args) {
372 argc = args->values.size;
373 argv = args->values.data();
374 }
375 thisObject = &b->d()->boundThis;
376 }
377 QV4::Scope scope(v4);
378 QV4::JSCallData jsCall(thisObject, argv, argc);
379
380 return QQmlJavaScriptExpression::evaluate(jsCall.callData(scope), isUndefined);
381}
382
383QT_END_NAMESPACE
Combined button and popup list for selecting options.
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)