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 | |
4 | #include "qqmlbinding_p.h" |
5 | |
6 | #include "qqmlcontext.h" |
7 | #include "qqmldata_p.h" |
8 | |
9 | #include <private/qqmldebugserviceinterfaces_p.h> |
10 | #include <private/qqmldebugconnector_p.h> |
11 | |
12 | #include <private/qqmlprofiler_p.h> |
13 | #include <private/qqmlexpression_p.h> |
14 | #include <private/qqmlscriptstring_p.h> |
15 | #include <private/qqmlbuiltinfunctions_p.h> |
16 | #include <private/qqmlvmemetaobject_p.h> |
17 | #include <private/qqmlvaluetypewrapper_p.h> |
18 | #include <private/qv4qmlcontext_p.h> |
19 | #include <private/qv4qobjectwrapper_p.h> |
20 | #include <private/qv4variantobject_p.h> |
21 | #include <private/qv4jscall_p.h> |
22 | #include <private/qjsvalue_p.h> |
23 | |
24 | #include <qtqml_tracepoints_p.h> |
25 | |
26 | #include <QVariant> |
27 | #include <QtCore/qdebug.h> |
28 | #include <QVector> |
29 | |
30 | QT_BEGIN_NAMESPACE |
31 | |
32 | Q_TRACE_POINT(qtqml, QQmlBinding_entry, const QQmlEngine *engine, const QString &function, const QString &fileName, int line, int column) |
33 | Q_TRACE_POINT(qtqml, QQmlBinding_exit) |
34 | |
35 | QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt) |
36 | { |
37 | QQmlBinding *b = newBinding(property); |
38 | |
39 | if (ctxt && !ctxt->isValid()) |
40 | return b; |
41 | |
42 | const QQmlScriptStringPrivate *scriptPrivate = script.d.data(); |
43 | if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) |
44 | return b; |
45 | |
46 | QString url; |
47 | QV4::Function *runtimeFunction = nullptr; |
48 | |
49 | QQmlRefPointer<QQmlContextData> ctxtdata = QQmlContextData::get(context: scriptPrivate->context); |
50 | QQmlEnginePrivate *engine = QQmlEnginePrivate::get(e: scriptPrivate->context->engine()); |
51 | if (engine && ctxtdata && !ctxtdata->urlString().isEmpty() && ctxtdata->typeCompilationUnit()) { |
52 | url = ctxtdata->urlString(); |
53 | if (scriptPrivate->bindingId != QQmlBinding::Invalid) |
54 | runtimeFunction = ctxtdata->typeCompilationUnit()->runtimeFunctions.at(i: scriptPrivate->bindingId); |
55 | } |
56 | |
57 | b->setNotifyOnValueChanged(true); |
58 | b->QQmlJavaScriptExpression::setContext(QQmlContextData::get(context: ctxt ? ctxt : scriptPrivate->context)); |
59 | b->setScopeObject(obj ? obj : scriptPrivate->scope); |
60 | |
61 | QV4::ExecutionEngine *v4 = b->engine()->handle(); |
62 | if (runtimeFunction) { |
63 | QV4::Scope scope(v4); |
64 | QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(parent: v4->rootContext(), context: ctxtdata, scopeObject: b->scopeObject())); |
65 | b->setupFunction(qmlContext, f: runtimeFunction); |
66 | } else { |
67 | QString code = scriptPrivate->script; |
68 | b->createQmlBinding(ctxt: b->context(), scope: b->scopeObject(), code, filename: url, line: scriptPrivate->lineNumber); |
69 | } |
70 | |
71 | return b; |
72 | } |
73 | |
74 | QQmlSourceLocation QQmlBinding::sourceLocation() const |
75 | { |
76 | if (m_sourceLocation) |
77 | return *m_sourceLocation; |
78 | return QQmlJavaScriptExpression::sourceLocation(); |
79 | } |
80 | |
81 | void QQmlBinding::setSourceLocation(const QQmlSourceLocation &location) |
82 | { |
83 | if (m_sourceLocation) |
84 | delete m_sourceLocation; |
85 | m_sourceLocation = new QQmlSourceLocation(location); |
86 | } |
87 | |
88 | |
89 | QQmlBinding *QQmlBinding::create( |
90 | const QQmlPropertyData *property, const QString &str, QObject *obj, |
91 | const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber) |
92 | { |
93 | QQmlBinding *b = newBinding(property); |
94 | |
95 | b->setNotifyOnValueChanged(true); |
96 | b->QQmlJavaScriptExpression::setContext(ctxt); |
97 | b->setScopeObject(obj); |
98 | |
99 | b->createQmlBinding(ctxt, scope: obj, code: str, filename: url, line: lineNumber); |
100 | |
101 | return b; |
102 | } |
103 | |
104 | QQmlBinding *QQmlBinding::create( |
105 | const QQmlPropertyData *property, QV4::Function *function, QObject *obj, |
106 | const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope) |
107 | { |
108 | return create(propertyType: property ? property->propType() : QMetaType(), function, obj, ctxt, scope); |
109 | } |
110 | |
111 | QQmlBinding *QQmlBinding::create(QMetaType propertyType, QV4::Function *function, QObject *obj, |
112 | const QQmlRefPointer<QQmlContextData> &ctxt, |
113 | QV4::ExecutionContext *scope) |
114 | { |
115 | QQmlBinding *b = newBinding(propertyType); |
116 | |
117 | b->setNotifyOnValueChanged(true); |
118 | b->QQmlJavaScriptExpression::setContext(ctxt); |
119 | b->setScopeObject(obj); |
120 | |
121 | Q_ASSERT(scope); |
122 | b->setupFunction(qmlContext: scope, f: function); |
123 | |
124 | return b; |
125 | } |
126 | |
127 | QQmlBinding::~QQmlBinding() |
128 | { |
129 | delete m_sourceLocation; |
130 | } |
131 | |
132 | void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) |
133 | { |
134 | if (!enabledFlag() || !hasValidContext()) |
135 | return; |
136 | |
137 | // Check that the target has not been deleted |
138 | if (QQmlData::wasDeleted(object: targetObject())) |
139 | return; |
140 | |
141 | // Check for a binding update loop |
142 | if (Q_UNLIKELY(updatingFlag())) { |
143 | const QQmlPropertyData *d = nullptr; |
144 | QQmlPropertyData vtd; |
145 | getPropertyData(propertyData: &d, valueTypeData: &vtd); |
146 | Q_ASSERT(d); |
147 | QQmlProperty p = QQmlPropertyPrivate::restore(targetObject(), *d, &vtd, nullptr); |
148 | QQmlAbstractBinding::printBindingLoopError(prop: p); |
149 | return; |
150 | } |
151 | setUpdatingFlag(true); |
152 | |
153 | DeleteWatcher watcher(this); |
154 | |
155 | QQmlEngine *qmlEngine = engine(); |
156 | QV4::Scope scope(qmlEngine->handle()); |
157 | |
158 | if (canUseAccessor()) |
159 | flags.setFlag(flag: QQmlPropertyData::BypassInterceptor); |
160 | |
161 | Q_TRACE_SCOPE(QQmlBinding, qmlEngine, function() ? function()->name()->toQString() : QString(), |
162 | sourceLocation().sourceFile, sourceLocation().line, sourceLocation().column); |
163 | QQmlBindingProfiler prof(QQmlEnginePrivate::get(e: qmlEngine)->profiler, function()); |
164 | doUpdate(watcher, flags, scope); |
165 | |
166 | if (!watcher.wasDeleted()) |
167 | setUpdatingFlag(false); |
168 | } |
169 | |
170 | QV4::ReturnedValue QQmlBinding::evaluate(bool *isUndefined) |
171 | { |
172 | QV4::ExecutionEngine *v4 = engine()->handle(); |
173 | int argc = 0; |
174 | const QV4::Value *argv = nullptr; |
175 | const QV4::Value *thisObject = nullptr; |
176 | QV4::BoundFunction *b = nullptr; |
177 | if ((b = static_cast<QV4::BoundFunction *>(m_boundFunction.valueRef()))) { |
178 | QV4::Heap::MemberData *args = b->boundArgs(); |
179 | if (args) { |
180 | argc = args->values.size; |
181 | argv = args->values.data(); |
182 | } |
183 | thisObject = &b->d()->boundThis; |
184 | } |
185 | QV4::Scope scope(v4); |
186 | QV4::JSCallData jsCall(thisObject, argv, argc); |
187 | |
188 | return QQmlJavaScriptExpression::evaluate(callData: jsCall.callData(scope), isUndefined); |
189 | } |
190 | |
191 | template<int StaticPropType> |
192 | class GenericBinding: public QQmlBinding |
193 | { |
194 | protected: |
195 | // Returns true if successful, false if an error description was set on expression |
196 | Q_ALWAYS_INLINE bool write(void *result, QMetaType type, bool isUndefined, |
197 | QQmlPropertyData::WriteFlags flags) override final |
198 | { |
199 | const QQmlPropertyData *pd; |
200 | QQmlPropertyData vpd; |
201 | getPropertyData(propertyData: &pd, valueTypeData: &vpd); |
202 | Q_ASSERT(pd); |
203 | |
204 | if (isUndefined || vpd.isValid()) |
205 | return slowWrite(*pd, vpd, result, type, isUndefined, flags); |
206 | |
207 | if ((StaticPropType == QMetaType::UnknownType && pd->propType() == type) |
208 | || StaticPropType == type.id()) { |
209 | Q_ASSERT(targetObject()); |
210 | return pd->writeProperty(target: targetObject(), value: result, flags); |
211 | } |
212 | |
213 | // If the type didn't match, we need to do JavaScript conversion. This should be rare. |
214 | return write(engine()->handle()->metaTypeToJS(type, result), isUndefined, flags); |
215 | } |
216 | |
217 | // Returns true if successful, false if an error description was set on expression |
218 | Q_ALWAYS_INLINE bool write(const QV4::Value &result, bool isUndefined, |
219 | QQmlPropertyData::WriteFlags flags) override final |
220 | { |
221 | Q_ASSERT(targetObject()); |
222 | |
223 | const QQmlPropertyData *pd; |
224 | QQmlPropertyData vpd; |
225 | getPropertyData(propertyData: &pd, valueTypeData: &vpd); |
226 | Q_ASSERT(pd); |
227 | |
228 | int propertyType = StaticPropType; // If the binding is specialized to a type, the if and switch below will be constant-folded. |
229 | if (propertyType == QMetaType::UnknownType) |
230 | propertyType = pd->propType().id(); |
231 | |
232 | if (Q_LIKELY(!isUndefined && !vpd.isValid())) { |
233 | switch (propertyType) { |
234 | case QMetaType::Bool: |
235 | if (result.isBoolean()) |
236 | return doStore<bool>(result.booleanValue(), pd, flags); |
237 | else |
238 | return doStore<bool>(result.toBoolean(), pd, flags); |
239 | case QMetaType::Int: |
240 | if (result.isInteger()) |
241 | return doStore<int>(result.integerValue(), pd, flags); |
242 | else if (result.isNumber()) { |
243 | return doStore<int>(result.toInt32(), pd, flags); |
244 | } |
245 | break; |
246 | case QMetaType::Double: |
247 | if (result.isNumber()) |
248 | return doStore<double>(result.asDouble(), pd, flags); |
249 | break; |
250 | case QMetaType::Float: |
251 | if (result.isNumber()) |
252 | return doStore<float>(result.asDouble(), pd, flags); |
253 | break; |
254 | case QMetaType::QString: |
255 | if (result.isString()) |
256 | return doStore<QString>(result.toQStringNoThrow(), pd, flags); |
257 | break; |
258 | default: |
259 | if (const QV4::QQmlValueTypeWrapper *vtw = result.as<const QV4::QQmlValueTypeWrapper>()) { |
260 | if (vtw->d()->metaType() == pd->propType()) { |
261 | return vtw->write(target: m_target.data(), propertyIndex: pd->coreIndex()); |
262 | } |
263 | } |
264 | break; |
265 | } |
266 | } |
267 | |
268 | return slowWrite(*pd, vpd, result, isUndefined, flags); |
269 | } |
270 | |
271 | template <typename T> |
272 | Q_ALWAYS_INLINE bool doStore(T value, const QQmlPropertyData *pd, QQmlPropertyData::WriteFlags flags) const |
273 | { |
274 | void *o = &value; |
275 | return pd->writeProperty(target: targetObject(), value: o, flags); |
276 | } |
277 | }; |
278 | |
279 | class QQmlTranslationBinding : public GenericBinding<QMetaType::QString>, public QPropertyObserver { |
280 | public: |
281 | QQmlTranslationBinding(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) |
282 | : QPropertyObserver(&QQmlTranslationBinding::onLanguageChange) |
283 | { |
284 | setCompilationUnit(compilationUnit); |
285 | setSource(QQmlEnginePrivate::get(e: compilationUnit->engine)->translationLanguage); |
286 | } |
287 | |
288 | virtual QString bindingValue() const = 0; |
289 | |
290 | static void onLanguageChange(QPropertyObserver *observer, QUntypedPropertyData *) |
291 | { static_cast<QQmlTranslationBinding *>(observer)->update(); } |
292 | |
293 | void doUpdate(const DeleteWatcher &watcher, |
294 | QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) override final |
295 | { |
296 | if (watcher.wasDeleted()) |
297 | return; |
298 | |
299 | if (!isAddedToObject() || hasError()) |
300 | return; |
301 | |
302 | const QString result = this->bindingValue(); |
303 | |
304 | Q_ASSERT(targetObject()); |
305 | |
306 | const QQmlPropertyData *pd; |
307 | QQmlPropertyData vpd; |
308 | getPropertyData(propertyData: &pd, valueTypeData: &vpd); |
309 | Q_ASSERT(pd); |
310 | if (pd->propType().id() == QMetaType::QString) { |
311 | doStore(value: result, pd, flags); |
312 | } else { |
313 | QV4::ScopedString value(scope, scope.engine->newString(s: result)); |
314 | slowWrite(core: *pd, valueTypeData: vpd, result: value, /*isUndefined*/false, flags); |
315 | } |
316 | } |
317 | |
318 | bool hasDependencies() const override final { return true; } |
319 | }; |
320 | |
321 | class QQmlTranslationBindingFromBinding : public QQmlTranslationBinding |
322 | { |
323 | const QV4::CompiledData::Binding *m_binding; |
324 | |
325 | public: |
326 | QQmlTranslationBindingFromBinding( |
327 | const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, |
328 | const QV4::CompiledData::Binding *binding) |
329 | : QQmlTranslationBinding(compilationUnit), m_binding(binding) |
330 | { |
331 | } |
332 | |
333 | QString bindingValue() const override |
334 | { |
335 | return this->m_compilationUnit->bindingValueAsString(binding: m_binding); |
336 | } |
337 | |
338 | QQmlSourceLocation sourceLocation() const override final |
339 | { |
340 | return QQmlSourceLocation(m_compilationUnit->fileName(), m_binding->valueLocation.line(), |
341 | m_binding->valueLocation.column()); |
342 | } |
343 | }; |
344 | |
345 | class QQmlTranslationBindingFromTranslationInfo : public QQmlTranslationBinding |
346 | { |
347 | QQmlTranslation m_translationData; |
348 | |
349 | quint16 m_line; |
350 | quint16 m_column; |
351 | |
352 | public: |
353 | QQmlTranslationBindingFromTranslationInfo( |
354 | const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, |
355 | const QQmlTranslation &translationData, quint16 line, quint16 column) |
356 | : QQmlTranslationBinding(compilationUnit), |
357 | m_translationData(translationData), |
358 | m_line(line), |
359 | m_column(column) |
360 | { |
361 | } |
362 | |
363 | virtual QString bindingValue() const override { return m_translationData.translate(); } |
364 | |
365 | QQmlSourceLocation sourceLocation() const override final |
366 | { |
367 | return QQmlSourceLocation(m_compilationUnit->fileName(), m_line, m_column); |
368 | } |
369 | }; |
370 | |
371 | QQmlBinding *QQmlBinding::createTranslationBinding( |
372 | const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, |
373 | const QV4::CompiledData::Binding *binding, QObject *obj, |
374 | const QQmlRefPointer<QQmlContextData> &ctxt) |
375 | { |
376 | QQmlTranslationBinding *b = new QQmlTranslationBindingFromBinding(unit, binding); |
377 | |
378 | b->setNotifyOnValueChanged(true); |
379 | b->QQmlJavaScriptExpression::setContext(ctxt); |
380 | b->setScopeObject(obj); |
381 | #if QT_CONFIG(translation) && QT_CONFIG(qml_debug) |
382 | if (QQmlDebugTranslationService *service |
383 | = QQmlDebugConnector::service<QQmlDebugTranslationService>()) { |
384 | service->foundTranslationBinding( |
385 | translationBindingInformation: TranslationBindingInformation::create(compilationUnit: unit, binding, scopeObject: b->scopeObject(), ctxt)); |
386 | } |
387 | #endif |
388 | return b; |
389 | } |
390 | |
391 | QQmlBinding *QQmlBinding::createTranslationBinding( |
392 | const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, |
393 | const QQmlRefPointer<QQmlContextData> &ctxt, const QString &propertyName, |
394 | const QQmlTranslation &translationData, const QQmlSourceLocation &location, QObject *obj) |
395 | { |
396 | QQmlTranslationBinding *b = new QQmlTranslationBindingFromTranslationInfo( |
397 | unit, translationData, location.column, location.line); |
398 | |
399 | b->setNotifyOnValueChanged(true); |
400 | b->QQmlJavaScriptExpression::setContext(ctxt); |
401 | b->setScopeObject(obj); |
402 | |
403 | #if QT_CONFIG(translation) && QT_CONFIG(qml_debug) |
404 | QString originString; |
405 | if (QQmlDebugTranslationService *service = |
406 | QQmlDebugConnector::service<QQmlDebugTranslationService>()) { |
407 | service->foundTranslationBinding(translationBindingInformation: { .compilationUnit: unit, .scopeObject: b->scopeObject(), .ctxt: ctxt, |
408 | |
409 | .propertyName: propertyName, .translation: translationData, |
410 | |
411 | .line: location.line, .column: location.column }); |
412 | } |
413 | #endif |
414 | return b; |
415 | } |
416 | |
417 | bool QQmlBinding::slowWrite( |
418 | const QQmlPropertyData &core, const QQmlPropertyData &valueTypeData, const void *result, |
419 | QMetaType resultType, bool isUndefined, QQmlPropertyData::WriteFlags flags) |
420 | { |
421 | // The logic in this method is obscure. It follows what the other slowWrite does, minus the type |
422 | // conversions and the checking for binding functions. If you're writing a C++ type, and |
423 | // you're passing a binding function wrapped into QJSValue, you probably want it to be assigned. |
424 | |
425 | if (hasError()) |
426 | return false; |
427 | |
428 | QQmlEngine *qmlEngine = engine(); |
429 | const QMetaType metaType = valueTypeData.isValid() ? valueTypeData.propType() : core.propType(); |
430 | QQmlJavaScriptExpression::DeleteWatcher watcher(this); |
431 | |
432 | if (core.isVarProperty()) { |
433 | QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(obj: m_target.data()); |
434 | Q_ASSERT(vmemo); |
435 | vmemo->setVMEProperty(index: core.coreIndex(), |
436 | v: qmlEngine->handle()->metaTypeToJS(type: resultType, data: result)); |
437 | } else if (isUndefined && core.isResettable()) { |
438 | void *args[] = { nullptr }; |
439 | QMetaObject::metacall(m_target.data(), QMetaObject::ResetProperty, core.coreIndex(), args); |
440 | } else if (isUndefined && metaType == QMetaType::fromType<QVariant>()) { |
441 | QQmlPropertyPrivate::writeValueProperty( |
442 | m_target.data(), core, valueTypeData, QVariant(), context(), flags); |
443 | } else if (metaType == QMetaType::fromType<QJSValue>()) { |
444 | QQmlPropertyPrivate::writeValueProperty( |
445 | m_target.data(), core, valueTypeData, |
446 | QVariant(resultType, result), context(), flags); |
447 | } else if (isUndefined) { |
448 | const char *name = metaType.name(); |
449 | const QString typeName = name |
450 | ? QString::fromUtf8(utf8: name) |
451 | : QStringLiteral("[unknown property type]" ); |
452 | delayedError()->setErrorDescription( |
453 | QStringLiteral("Unable to assign [undefined] to " ) + typeName); |
454 | return false; |
455 | } else if (!QQmlPropertyPrivate::writeValueProperty( |
456 | m_target.data(), core, valueTypeData, QVariant(resultType, result), |
457 | context(), flags)) { |
458 | if (watcher.wasDeleted()) |
459 | return true; |
460 | handleWriteError(result, resultType, metaType); |
461 | return false; |
462 | } |
463 | |
464 | return true; |
465 | } |
466 | |
467 | Q_NEVER_INLINE bool QQmlBinding::slowWrite(const QQmlPropertyData &core, |
468 | const QQmlPropertyData &valueTypeData, |
469 | const QV4::Value &result, |
470 | bool isUndefined, QQmlPropertyData::WriteFlags flags) |
471 | { |
472 | const QMetaType metaType = valueTypeData.isValid() ? valueTypeData.propType() : core.propType(); |
473 | const int type = metaType.id(); |
474 | |
475 | QQmlJavaScriptExpression::DeleteWatcher watcher(this); |
476 | |
477 | QVariant value; |
478 | bool isVarProperty = core.isVarProperty(); |
479 | |
480 | if (isUndefined) { |
481 | } else if (core.isQList()) { |
482 | if (core.propType().flags() & QMetaType::IsQmlList) |
483 | value = QV4::ExecutionEngine::toVariant(value: result, typeHint: QMetaType::fromType<QList<QObject*>>()); |
484 | else |
485 | value = QV4::ExecutionEngine::toVariant(value: result, typeHint: core.propType()); |
486 | } else if (result.isNull() && core.isQObject()) { |
487 | value = QVariant::fromValue(value: (QObject *)nullptr); |
488 | } else if (core.propType() == QMetaType::fromType<QList<QUrl>>()) { |
489 | const QVariant resultVariant |
490 | = QV4::ExecutionEngine::toVariant(value: result, typeHint: QMetaType::fromType<QList<QUrl>>()); |
491 | value = QVariant::fromValue(value: QQmlPropertyPrivate::resolveUrlsOnAssignment() |
492 | ? QQmlPropertyPrivate::urlSequence(value: resultVariant, ctxt: context()) |
493 | : QQmlPropertyPrivate::urlSequence(value: resultVariant)); |
494 | } else if (!isVarProperty && metaType != QMetaType::fromType<QJSValue>()) { |
495 | value = QV4::ExecutionEngine::toVariant(value: result, typeHint: metaType); |
496 | } |
497 | |
498 | if (hasError()) { |
499 | return false; |
500 | } else if (isVarProperty) { |
501 | const QV4::FunctionObject *f = result.as<QV4::FunctionObject>(); |
502 | if (f && f->isBinding()) { |
503 | // we explicitly disallow this case to avoid confusion. Users can still store one |
504 | // in an array in a var property if they need to, but the common case is user error. |
505 | delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration." )); |
506 | return false; |
507 | } |
508 | |
509 | QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(obj: m_target.data()); |
510 | Q_ASSERT(vmemo); |
511 | vmemo->setVMEProperty(index: core.coreIndex(), v: result); |
512 | } else if (isUndefined |
513 | && (valueTypeData.isValid() ? valueTypeData.isResettable() : core.isResettable())) { |
514 | QQmlPropertyPrivate::resetValueProperty( |
515 | m_target.data(), core, valueTypeData, context(), flags); |
516 | } else if (isUndefined && type == QMetaType::QVariant) { |
517 | QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, QVariant(), context(), flags); |
518 | } else if (metaType == QMetaType::fromType<QJSValue>()) { |
519 | const QV4::FunctionObject *f = result.as<QV4::FunctionObject>(); |
520 | if (f && f->isBinding()) { |
521 | delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration." )); |
522 | return false; |
523 | } |
524 | QQmlPropertyPrivate::writeValueProperty( |
525 | m_target.data(), core, valueTypeData, |
526 | QVariant::fromValue(value: QJSValuePrivate::fromReturnedValue(d: result.asReturnedValue())), |
527 | context(), flags); |
528 | } else if (isUndefined) { |
529 | const char *name = QMetaType(type).name(); |
530 | const QLatin1String typeName(name ? name : "[unknown property type]" ); |
531 | delayedError()->setErrorDescription(QLatin1String("Unable to assign [undefined] to " ) |
532 | + typeName); |
533 | return false; |
534 | } else if (const QV4::FunctionObject *f = result.as<QV4::FunctionObject>()) { |
535 | if (f->isBinding()) |
536 | delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration." )); |
537 | else |
538 | delayedError()->setErrorDescription(QLatin1String("Unable to assign a function to a property of any type other than var." )); |
539 | return false; |
540 | } else if (!QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, value, context(), flags)) { |
541 | |
542 | if (watcher.wasDeleted()) |
543 | return true; |
544 | handleWriteError(result: value.constData(), resultType: value.metaType(), metaType); |
545 | return false; |
546 | } |
547 | |
548 | return true; |
549 | } |
550 | |
551 | void QQmlBinding::handleWriteError(const void *result, QMetaType resultType, QMetaType metaType) |
552 | { |
553 | const char *valueType = nullptr; |
554 | const char *propertyType = nullptr; |
555 | |
556 | if (resultType.flags() & QMetaType::PointerToQObject) { |
557 | if (QObject *o = *(QObject *const *)result) { |
558 | valueType = o->metaObject()->className(); |
559 | QQmlMetaObject propertyMetaObject = QQmlPropertyPrivate::rawMetaObjectForType(metaType); |
560 | if (!propertyMetaObject.isNull()) |
561 | propertyType = propertyMetaObject.className(); |
562 | } |
563 | } else if (resultType.isValid()) { |
564 | if (resultType == QMetaType::fromType<std::nullptr_t>() |
565 | || resultType == QMetaType::fromType<void *>()) { |
566 | valueType = "null" ; |
567 | } else { |
568 | valueType = resultType.name(); |
569 | } |
570 | } |
571 | |
572 | if (!valueType) |
573 | valueType = "undefined" ; |
574 | if (!propertyType) |
575 | propertyType = metaType.name(); |
576 | if (!propertyType) |
577 | propertyType = "[unknown property type]" ; |
578 | |
579 | delayedError()->setErrorDescription(QStringLiteral("Unable to assign " ) |
580 | + QString::fromUtf8(utf8: valueType) |
581 | + QStringLiteral(" to " ) |
582 | + QString::fromUtf8(utf8: propertyType)); |
583 | } |
584 | |
585 | QVariant QQmlBinding::evaluate() |
586 | { |
587 | QQmlEngine *qmlEngine = engine(); |
588 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: qmlEngine); |
589 | ep->referenceScarceResources(); |
590 | |
591 | bool isUndefined = false; |
592 | |
593 | QV4::Scope scope(qmlEngine->handle()); |
594 | QV4::ScopedValue result(scope, QQmlJavaScriptExpression::evaluate(isUndefined: &isUndefined)); |
595 | |
596 | ep->dereferenceScarceResources(); |
597 | |
598 | return QV4::ExecutionEngine::toVariant(value: result, typeHint: QMetaType::fromType<QList<QObject*> >()); |
599 | } |
600 | |
601 | void QQmlBinding::expressionChanged() |
602 | { |
603 | update(); |
604 | } |
605 | |
606 | void QQmlBinding::refresh() |
607 | { |
608 | update(); |
609 | } |
610 | |
611 | void QQmlBinding::setEnabled(bool e, QQmlPropertyData::WriteFlags flags) |
612 | { |
613 | const bool wasEnabled = enabledFlag(); |
614 | setEnabledFlag(e); |
615 | setNotifyOnValueChanged(e); |
616 | updateCanUseAccessor(); |
617 | |
618 | if (e && !wasEnabled) |
619 | update(flags); |
620 | } |
621 | |
622 | QString QQmlBinding::expression() const |
623 | { |
624 | return QStringLiteral("function() { [native code] }" ); |
625 | } |
626 | |
627 | QVector<QQmlProperty> QQmlBinding::dependencies() const |
628 | { |
629 | QVector<QQmlProperty> dependencies; |
630 | if (!m_target.data()) |
631 | return dependencies; |
632 | |
633 | for (QQmlJavaScriptExpressionGuard *guard = activeGuards.first(); guard; guard = activeGuards.next(v: guard)) { |
634 | if (guard->signalIndex() == -1) // guard's sender is a QQmlNotifier, not a QObject*. |
635 | continue; |
636 | |
637 | QObject *senderObject = guard->senderAsObject(); |
638 | if (!senderObject) |
639 | continue; |
640 | |
641 | const QMetaObject *senderMeta = senderObject->metaObject(); |
642 | if (!senderMeta) |
643 | continue; |
644 | |
645 | for (int i = 0; i < senderMeta->propertyCount(); i++) { |
646 | QMetaProperty property = senderMeta->property(index: i); |
647 | if (property.notifySignalIndex() == QMetaObjectPrivate::signal(m: senderMeta, signal_index: guard->signalIndex()).methodIndex()) { |
648 | dependencies.push_back(t: QQmlProperty(senderObject, QString::fromUtf8(utf8: property.name()))); |
649 | } |
650 | } |
651 | } |
652 | |
653 | for (auto trigger = qpropertyChangeTriggers; trigger; trigger = trigger->next) { |
654 | QMetaProperty prop = trigger->property(); |
655 | if (prop.isValid()) |
656 | dependencies.push_back(t: QQmlProperty(trigger->target, QString::fromUtf8(utf8: prop.name()))); |
657 | } |
658 | |
659 | return dependencies; |
660 | } |
661 | |
662 | bool QQmlBinding::hasDependencies() const |
663 | { |
664 | return !activeGuards.isEmpty() || qpropertyChangeTriggers; |
665 | } |
666 | |
667 | void QQmlBinding::doUpdate(const DeleteWatcher &watcher, QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) |
668 | { |
669 | auto ep = QQmlEnginePrivate::get(e: scope.engine); |
670 | ep->referenceScarceResources(); |
671 | |
672 | bool error = false; |
673 | auto canWrite = [&]() { return !watcher.wasDeleted() && isAddedToObject() && !hasError(); }; |
674 | const QV4::Function *v4Function = function(); |
675 | if (v4Function && v4Function->kind == QV4::Function::AotCompiled && !hasBoundFunction()) { |
676 | const auto returnType = v4Function->aotCompiledFunction->returnType; |
677 | if (returnType == QMetaType::fromType<QVariant>()) { |
678 | // It expects uninitialized memory |
679 | Q_ALLOCA_VAR(QVariant, result, sizeof(QVariant)); |
680 | const bool isUndefined = !evaluate(result, type: returnType); |
681 | if (canWrite()) |
682 | error = !write(result: result->data(), type: result->metaType(), isUndefined, flags); |
683 | result->~QVariant(); |
684 | } else { |
685 | const auto size = returnType.sizeOf(); |
686 | if (Q_LIKELY(size > 0)) { |
687 | Q_ALLOCA_VAR(void, result, size); |
688 | const bool isUndefined = !evaluate(result, type: returnType); |
689 | if (canWrite()) |
690 | error = !write(result, type: returnType, isUndefined, flags); |
691 | returnType.destruct(data: result); |
692 | } else if (canWrite()) { |
693 | error = !write(result: QV4::Encode::undefined(), isUndefined: true, flags); |
694 | } |
695 | } |
696 | } else { |
697 | bool isUndefined = false; |
698 | QV4::ScopedValue result(scope, evaluate(isUndefined: &isUndefined)); |
699 | if (canWrite()) |
700 | error = !write(result, isUndefined, flags); |
701 | } |
702 | |
703 | if (!watcher.wasDeleted()) { |
704 | |
705 | if (error) { |
706 | delayedError()->setErrorLocation(sourceLocation()); |
707 | delayedError()->setErrorObject(m_target.data()); |
708 | } |
709 | |
710 | if (hasError()) { |
711 | if (!delayedError()->addError(ep)) ep->warning(this->error(engine())); |
712 | } else { |
713 | clearError(); |
714 | } |
715 | } |
716 | |
717 | ep->dereferenceScarceResources(); |
718 | } |
719 | |
720 | class QObjectPointerBinding: public QQmlBinding |
721 | { |
722 | QQmlMetaObject targetMetaObject; |
723 | |
724 | public: |
725 | QObjectPointerBinding(QMetaType propertyType) |
726 | : targetMetaObject(QQmlPropertyPrivate::rawMetaObjectForType(metaType: propertyType)) |
727 | {} |
728 | |
729 | protected: |
730 | Q_ALWAYS_INLINE bool write(void *result, QMetaType type, bool isUndefined, |
731 | QQmlPropertyData::WriteFlags flags) override final |
732 | { |
733 | const QQmlPropertyData *pd; |
734 | QQmlPropertyData vtpd; |
735 | getPropertyData(propertyData: &pd, valueTypeData: &vtpd); |
736 | if (Q_UNLIKELY(isUndefined || vtpd.isValid())) |
737 | return slowWrite(core: *pd, valueTypeData: vtpd, result, resultType: type, isUndefined, flags); |
738 | |
739 | // Check if the result is a QObject: |
740 | QObject *resultObject = nullptr; |
741 | QQmlMetaObject resultMo; |
742 | const auto typeFlags = type.flags(); |
743 | if (!result || ((typeFlags & QMetaType::IsPointer) && !*static_cast<void **>(result))) { |
744 | // Special case: we can always write a nullptr. Don't bother checking anything else. |
745 | return pd->writeProperty(target: targetObject(), value: &resultObject, flags); |
746 | } else if (typeFlags & QMetaType::PointerToQObject) { |
747 | resultObject = *static_cast<QObject **>(result); |
748 | if (!resultObject) |
749 | return pd->writeProperty(target: targetObject(), value: &resultObject, flags); |
750 | if (QQmlData *ddata = QQmlData::get(object: resultObject, create: false)) |
751 | resultMo = ddata->propertyCache; |
752 | if (resultMo.isNull()) |
753 | resultMo = resultObject->metaObject(); |
754 | } else if (type == QMetaType::fromType<QVariant>()) { |
755 | const QVariant value = *static_cast<QVariant *>(result); |
756 | resultMo = QQmlPropertyPrivate::rawMetaObjectForType(metaType: value.metaType()); |
757 | if (resultMo.isNull()) |
758 | return slowWrite(core: *pd, valueTypeData: vtpd, result, resultType: type, isUndefined, flags); |
759 | resultObject = *static_cast<QObject *const *>(value.constData()); |
760 | } else { |
761 | return slowWrite(core: *pd, valueTypeData: vtpd, result, resultType: type, isUndefined, flags); |
762 | } |
763 | |
764 | return compareAndSet(resultMo, resultObject, pd, flags, slowWrite: [&]() { |
765 | return slowWrite(core: *pd, valueTypeData: vtpd, result, resultType: type, isUndefined, flags); |
766 | }); |
767 | } |
768 | |
769 | Q_ALWAYS_INLINE bool write(const QV4::Value &result, bool isUndefined, |
770 | QQmlPropertyData::WriteFlags flags) override final |
771 | { |
772 | const QQmlPropertyData *pd; |
773 | QQmlPropertyData vtpd; |
774 | getPropertyData(propertyData: &pd, valueTypeData: &vtpd); |
775 | if (Q_UNLIKELY(isUndefined || vtpd.isValid())) |
776 | return slowWrite(core: *pd, valueTypeData: vtpd, result, isUndefined, flags); |
777 | |
778 | // Check if the result is a QObject: |
779 | QObject *resultObject = nullptr; |
780 | QQmlMetaObject resultMo; |
781 | if (result.isNull()) { |
782 | // Special case: we can always write a nullptr. Don't bother checking anything else. |
783 | return pd->writeProperty(target: targetObject(), value: &resultObject, flags); |
784 | } else if (auto wrapper = result.as<QV4::QObjectWrapper>()) { |
785 | resultObject = wrapper->object(); |
786 | if (!resultObject) |
787 | return pd->writeProperty(target: targetObject(), value: &resultObject, flags); |
788 | if (QQmlData *ddata = QQmlData::get(object: resultObject, create: false)) |
789 | resultMo = ddata->propertyCache; |
790 | if (resultMo.isNull()) { |
791 | resultMo = resultObject->metaObject(); |
792 | } |
793 | } else if (auto variant = result.as<QV4::VariantObject>()) { |
794 | const QVariant value = variant->d()->data(); |
795 | resultMo = QQmlPropertyPrivate::rawMetaObjectForType(metaType: value.metaType()); |
796 | if (resultMo.isNull()) |
797 | return slowWrite(core: *pd, valueTypeData: vtpd, result, isUndefined, flags); |
798 | resultObject = *static_cast<QObject *const *>(value.constData()); |
799 | } else { |
800 | return slowWrite(core: *pd, valueTypeData: vtpd, result, isUndefined, flags); |
801 | } |
802 | |
803 | return compareAndSet(resultMo, resultObject, pd, flags, slowWrite: [&]() { |
804 | return slowWrite(core: *pd, valueTypeData: vtpd, result, isUndefined, flags); |
805 | }); |
806 | } |
807 | |
808 | private: |
809 | using QQmlBinding::slowWrite; |
810 | |
811 | template<typename SlowWrite> |
812 | bool compareAndSet(const QQmlMetaObject &resultMo, QObject *resultObject, const QQmlPropertyData *pd, |
813 | QQmlPropertyData::WriteFlags flags, const SlowWrite &slowWrite) const |
814 | { |
815 | if (QQmlMetaObject::canConvert(from: resultMo, to: targetMetaObject)) { |
816 | return pd->writeProperty(target: targetObject(), value: &resultObject, flags); |
817 | } else if (!resultObject && QQmlMetaObject::canConvert(from: targetMetaObject, to: resultMo)) { |
818 | // In the case of a null QObject, we assign the null if there is |
819 | // any change that the null variant type could be up or down cast to |
820 | // the property type. |
821 | return pd->writeProperty(target: targetObject(), value: &resultObject, flags); |
822 | } else { |
823 | return slowWrite(); |
824 | } |
825 | } |
826 | }; |
827 | |
828 | QQmlBinding *QQmlBinding::newBinding(const QQmlPropertyData *property) |
829 | { |
830 | return newBinding(propertyType: property ? property->propType() : QMetaType()); |
831 | } |
832 | |
833 | QQmlBinding *QQmlBinding::newBinding(QMetaType propertyType) |
834 | { |
835 | if (propertyType.flags() & QMetaType::PointerToQObject) |
836 | return new QObjectPointerBinding(propertyType); |
837 | |
838 | switch (propertyType.id()) { |
839 | case QMetaType::Bool: |
840 | return new GenericBinding<QMetaType::Bool>; |
841 | case QMetaType::Int: |
842 | return new GenericBinding<QMetaType::Int>; |
843 | case QMetaType::Double: |
844 | return new GenericBinding<QMetaType::Double>; |
845 | case QMetaType::Float: |
846 | return new GenericBinding<QMetaType::Float>; |
847 | case QMetaType::QString: |
848 | return new GenericBinding<QMetaType::QString>; |
849 | default: |
850 | return new GenericBinding<QMetaType::UnknownType>; |
851 | } |
852 | } |
853 | |
854 | QT_END_NAMESPACE |
855 | |