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 "qqmlbinding_p.h" |
41 | |
42 | #include "qqml.h" |
43 | #include "qqmlcontext.h" |
44 | #include "qqmlinfo.h" |
45 | #include "qqmldata_p.h" |
46 | |
47 | #include <private/qqmldebugserviceinterfaces_p.h> |
48 | #include <private/qqmldebugconnector_p.h> |
49 | |
50 | #include <private/qqmlprofiler_p.h> |
51 | #include <private/qqmlexpression_p.h> |
52 | #include <private/qqmlscriptstring_p.h> |
53 | #include <private/qqmlbuiltinfunctions_p.h> |
54 | #include <private/qqmlvmemetaobject_p.h> |
55 | #include <private/qqmlvaluetypewrapper_p.h> |
56 | #include <private/qv4qmlcontext_p.h> |
57 | #include <private/qv4qobjectwrapper_p.h> |
58 | #include <private/qv4variantobject_p.h> |
59 | #include <private/qv4jscall_p.h> |
60 | |
61 | #include <qtqml_tracepoints_p.h> |
62 | |
63 | #include <QVariant> |
64 | #include <QtCore/qdebug.h> |
65 | #include <QVector> |
66 | |
67 | QT_BEGIN_NAMESPACE |
68 | |
69 | QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt) |
70 | { |
71 | QQmlBinding *b = newBinding(engine: QQmlEnginePrivate::get(c: ctxt), property); |
72 | |
73 | if (ctxt && !ctxt->isValid()) |
74 | return b; |
75 | |
76 | const QQmlScriptStringPrivate *scriptPrivate = script.d.data(); |
77 | if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) |
78 | return b; |
79 | |
80 | QString url; |
81 | QV4::Function *runtimeFunction = nullptr; |
82 | |
83 | QQmlContextData *ctxtdata = QQmlContextData::get(context: scriptPrivate->context); |
84 | QQmlEnginePrivate *engine = QQmlEnginePrivate::get(e: scriptPrivate->context->engine()); |
85 | if (engine && ctxtdata && !ctxtdata->urlString().isEmpty() && ctxtdata->typeCompilationUnit) { |
86 | url = ctxtdata->urlString(); |
87 | if (scriptPrivate->bindingId != QQmlBinding::Invalid) |
88 | runtimeFunction = ctxtdata->typeCompilationUnit->runtimeFunctions.at(i: scriptPrivate->bindingId); |
89 | } |
90 | |
91 | b->setNotifyOnValueChanged(true); |
92 | b->QQmlJavaScriptExpression::setContext(QQmlContextData::get(context: ctxt ? ctxt : scriptPrivate->context)); |
93 | b->setScopeObject(obj ? obj : scriptPrivate->scope); |
94 | |
95 | QV4::ExecutionEngine *v4 = b->context()->engine->handle(); |
96 | if (runtimeFunction) { |
97 | QV4::Scope scope(v4); |
98 | QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(parent: v4->rootContext(), context: ctxtdata, scopeObject: b->scopeObject())); |
99 | b->setupFunction(qmlContext, f: runtimeFunction); |
100 | } else { |
101 | QString code = scriptPrivate->script; |
102 | b->createQmlBinding(ctxt: b->context(), scope: b->scopeObject(), code, filename: url, line: scriptPrivate->lineNumber); |
103 | } |
104 | |
105 | return b; |
106 | } |
107 | |
108 | QQmlSourceLocation QQmlBinding::sourceLocation() const |
109 | { |
110 | if (m_sourceLocation) |
111 | return *m_sourceLocation; |
112 | return QQmlJavaScriptExpression::sourceLocation(); |
113 | } |
114 | |
115 | void QQmlBinding::setSourceLocation(const QQmlSourceLocation &location) |
116 | { |
117 | if (m_sourceLocation) |
118 | delete m_sourceLocation; |
119 | m_sourceLocation = new QQmlSourceLocation(location); |
120 | } |
121 | |
122 | |
123 | QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, const QString &str, QObject *obj, |
124 | QQmlContextData *ctxt, const QString &url, quint16 lineNumber) |
125 | { |
126 | QQmlBinding *b = newBinding(engine: QQmlEnginePrivate::get(c: ctxt), property); |
127 | |
128 | b->setNotifyOnValueChanged(true); |
129 | b->QQmlJavaScriptExpression::setContext(ctxt); |
130 | b->setScopeObject(obj); |
131 | |
132 | b->createQmlBinding(ctxt, scope: obj, code: str, filename: url, line: lineNumber); |
133 | |
134 | return b; |
135 | } |
136 | |
137 | QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, QV4::Function *function, |
138 | QObject *obj, QQmlContextData *ctxt, QV4::ExecutionContext *scope) |
139 | { |
140 | QQmlBinding *b = newBinding(engine: QQmlEnginePrivate::get(c: ctxt), property); |
141 | |
142 | b->setNotifyOnValueChanged(true); |
143 | b->QQmlJavaScriptExpression::setContext(ctxt); |
144 | b->setScopeObject(obj); |
145 | |
146 | Q_ASSERT(scope); |
147 | b->setupFunction(qmlContext: scope, f: function); |
148 | |
149 | return b; |
150 | } |
151 | |
152 | QQmlBinding::~QQmlBinding() |
153 | { |
154 | delete m_sourceLocation; |
155 | } |
156 | |
157 | void QQmlBinding::setNotifyOnValueChanged(bool v) |
158 | { |
159 | QQmlJavaScriptExpression::setNotifyOnValueChanged(v); |
160 | } |
161 | |
162 | void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) |
163 | { |
164 | if (!enabledFlag() || !context() || !context()->isValid()) |
165 | return; |
166 | |
167 | // Check that the target has not been deleted |
168 | if (QQmlData::wasDeleted(object: targetObject())) |
169 | return; |
170 | |
171 | // Check for a binding update loop |
172 | if (Q_UNLIKELY(updatingFlag())) { |
173 | QQmlPropertyData *d = nullptr; |
174 | QQmlPropertyData vtd; |
175 | getPropertyData(propertyData: &d, valueTypeData: &vtd); |
176 | Q_ASSERT(d); |
177 | QQmlProperty p = QQmlPropertyPrivate::restore(targetObject(), *d, &vtd, nullptr); |
178 | QQmlAbstractBinding::printBindingLoopError(prop&: p); |
179 | return; |
180 | } |
181 | setUpdatingFlag(true); |
182 | |
183 | DeleteWatcher watcher(this); |
184 | |
185 | QQmlEngine *engine = context()->engine; |
186 | QV4::Scope scope(engine->handle()); |
187 | |
188 | if (canUseAccessor()) |
189 | flags.setFlag(flag: QQmlPropertyData::BypassInterceptor); |
190 | |
191 | Q_TRACE_SCOPE(QQmlBinding, engine, function() ? function()->name()->toQString() : QString(), |
192 | sourceLocation().sourceFile, sourceLocation().line, sourceLocation().column); |
193 | QQmlBindingProfiler prof(QQmlEnginePrivate::get(e: engine)->profiler, function()); |
194 | doUpdate(watcher, flags, scope); |
195 | |
196 | if (!watcher.wasDeleted()) |
197 | setUpdatingFlag(false); |
198 | } |
199 | |
200 | QV4::ReturnedValue QQmlBinding::evaluate(bool *isUndefined) |
201 | { |
202 | QV4::ExecutionEngine *v4 = context()->engine->handle(); |
203 | int argc = 0; |
204 | const QV4::Value *argv = nullptr; |
205 | const QV4::Value *thisObject = nullptr; |
206 | QV4::BoundFunction *b = nullptr; |
207 | if ((b = static_cast<QV4::BoundFunction *>(m_boundFunction.valueRef()))) { |
208 | QV4::Heap::MemberData *args = b->boundArgs(); |
209 | if (args) { |
210 | argc = args->values.size; |
211 | argv = args->values.data(); |
212 | } |
213 | thisObject = &b->d()->boundThis; |
214 | } |
215 | QV4::Scope scope(v4); |
216 | QV4::JSCallData jsCall(scope, argc, argv, thisObject); |
217 | |
218 | return QQmlJavaScriptExpression::evaluate(callData: jsCall.callData(), isUndefined); |
219 | } |
220 | |
221 | |
222 | // QQmlBindingBinding is for target properties which are of type "binding" (instead of, say, int or |
223 | // double). The reason for being is that GenericBinding::fastWrite needs a compile-time constant |
224 | // expression for the switch for the compiler to generate the optimal code, but |
225 | // qMetaTypeId<QQmlBinding *>() needs to be used for the ID. So QQmlBinding::newBinding uses that |
226 | // to instantiate this class. |
227 | class QQmlBindingBinding: public QQmlBinding |
228 | { |
229 | protected: |
230 | void doUpdate(const DeleteWatcher &, |
231 | QQmlPropertyData::WriteFlags flags, QV4::Scope &) override final |
232 | { |
233 | Q_ASSERT(!m_targetIndex.hasValueTypeIndex()); |
234 | QQmlPropertyData *pd = nullptr; |
235 | getPropertyData(propertyData: &pd, valueTypeData: nullptr); |
236 | QQmlBinding *thisPtr = this; |
237 | pd->writeProperty(target: *m_target, value: &thisPtr, flags); |
238 | } |
239 | }; |
240 | |
241 | // For any target that's not a binding, we have a common doUpdate. However, depending on the type |
242 | // of the target property, there is a specialized write method. |
243 | class QQmlNonbindingBinding: public QQmlBinding |
244 | { |
245 | protected: |
246 | void doUpdate(const DeleteWatcher &watcher, |
247 | QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) override |
248 | { |
249 | auto ep = QQmlEnginePrivate::get(e: scope.engine); |
250 | ep->referenceScarceResources(); |
251 | |
252 | bool isUndefined = false; |
253 | |
254 | QV4::ScopedValue result(scope, evaluate(isUndefined: &isUndefined)); |
255 | |
256 | bool error = false; |
257 | if (!watcher.wasDeleted() && isAddedToObject() && !hasError()) |
258 | error = !write(result, isUndefined, flags); |
259 | |
260 | if (!watcher.wasDeleted()) { |
261 | |
262 | if (error) { |
263 | delayedError()->setErrorLocation(sourceLocation()); |
264 | delayedError()->setErrorObject(m_target.data()); |
265 | } |
266 | |
267 | if (hasError()) { |
268 | if (!delayedError()->addError(ep)) ep->warning(this->error(context()->engine)); |
269 | } else { |
270 | clearError(); |
271 | } |
272 | } |
273 | |
274 | ep->dereferenceScarceResources(); |
275 | } |
276 | |
277 | virtual bool write(const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) = 0; |
278 | }; |
279 | |
280 | template<int StaticPropType> |
281 | class GenericBinding: public QQmlNonbindingBinding |
282 | { |
283 | protected: |
284 | // Returns true if successful, false if an error description was set on expression |
285 | Q_ALWAYS_INLINE bool write(const QV4::Value &result, bool isUndefined, |
286 | QQmlPropertyData::WriteFlags flags) override final |
287 | { |
288 | Q_ASSERT(targetObject()); |
289 | |
290 | QQmlPropertyData *pd; |
291 | QQmlPropertyData vpd; |
292 | getPropertyData(propertyData: &pd, valueTypeData: &vpd); |
293 | Q_ASSERT(pd); |
294 | |
295 | int propertyType = StaticPropType; // If the binding is specialized to a type, the if and switch below will be constant-folded. |
296 | if (propertyType == QMetaType::UnknownType) |
297 | propertyType = pd->propType(); |
298 | |
299 | if (Q_LIKELY(!isUndefined && !vpd.isValid())) { |
300 | switch (propertyType) { |
301 | case QMetaType::Bool: |
302 | if (result.isBoolean()) |
303 | return doStore<bool>(result.booleanValue(), pd, flags); |
304 | else |
305 | return doStore<bool>(result.toBoolean(), pd, flags); |
306 | case QMetaType::Int: |
307 | if (result.isInteger()) |
308 | return doStore<int>(result.integerValue(), pd, flags); |
309 | else if (result.isNumber()) { |
310 | return doStore<int>(QV4::StaticValue::toInteger(d: result.doubleValue()), pd, flags); |
311 | } |
312 | break; |
313 | case QMetaType::Double: |
314 | if (result.isNumber()) |
315 | return doStore<double>(result.asDouble(), pd, flags); |
316 | break; |
317 | case QMetaType::Float: |
318 | if (result.isNumber()) |
319 | return doStore<float>(result.asDouble(), pd, flags); |
320 | break; |
321 | case QMetaType::QString: |
322 | if (result.isString()) |
323 | return doStore<QString>(result.toQStringNoThrow(), pd, flags); |
324 | break; |
325 | default: |
326 | if (const QV4::QQmlValueTypeWrapper *vtw = result.as<const QV4::QQmlValueTypeWrapper>()) { |
327 | if (vtw->d()->valueType()->metaType.id() == pd->propType()) { |
328 | return vtw->write(target: m_target.data(), propertyIndex: pd->coreIndex()); |
329 | } |
330 | } |
331 | break; |
332 | } |
333 | } |
334 | |
335 | return slowWrite(core: *pd, valueTypeData: vpd, result, isUndefined, flags); |
336 | } |
337 | |
338 | template <typename T> |
339 | Q_ALWAYS_INLINE bool doStore(T value, const QQmlPropertyData *pd, QQmlPropertyData::WriteFlags flags) const |
340 | { |
341 | void *o = &value; |
342 | return pd->writeProperty(target: targetObject(), value: o, flags); |
343 | } |
344 | }; |
345 | |
346 | class QQmlTranslationBinding : public GenericBinding<QMetaType::QString> { |
347 | public: |
348 | QQmlTranslationBinding(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding) |
349 | { |
350 | setCompilationUnit(compilationUnit); |
351 | m_binding = binding; |
352 | } |
353 | |
354 | QQmlSourceLocation sourceLocation() const override final |
355 | { |
356 | return QQmlSourceLocation(m_compilationUnit->fileName(), m_binding->valueLocation.line, m_binding->valueLocation.column); |
357 | } |
358 | |
359 | |
360 | void doUpdate(const DeleteWatcher &watcher, |
361 | QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) override final |
362 | { |
363 | if (watcher.wasDeleted()) |
364 | return; |
365 | |
366 | if (!isAddedToObject() || hasError()) |
367 | return; |
368 | |
369 | const QString result = m_compilationUnit->bindingValueAsString(binding: m_binding); |
370 | |
371 | Q_ASSERT(targetObject()); |
372 | |
373 | QQmlPropertyData *pd; |
374 | QQmlPropertyData vpd; |
375 | getPropertyData(propertyData: &pd, valueTypeData: &vpd); |
376 | Q_ASSERT(pd); |
377 | if (pd->propType() == QMetaType::QString) { |
378 | doStore(value: result, pd, flags); |
379 | } else { |
380 | QV4::ScopedString value(scope, scope.engine->newString(s: result)); |
381 | slowWrite(core: *pd, valueTypeData: vpd, result: value, /*isUndefined*/false, flags); |
382 | } |
383 | } |
384 | |
385 | bool hasDependencies() const override final { return true; } |
386 | |
387 | private: |
388 | const QV4::CompiledData::Binding *m_binding; |
389 | }; |
390 | |
391 | QQmlBinding *QQmlBinding::createTranslationBinding(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, const QV4::CompiledData::Binding *binding, QObject *obj, QQmlContextData *ctxt) |
392 | { |
393 | QQmlTranslationBinding *b = new QQmlTranslationBinding(unit, binding); |
394 | |
395 | b->setNotifyOnValueChanged(true); |
396 | b->QQmlJavaScriptExpression::setContext(ctxt); |
397 | b->setScopeObject(obj); |
398 | |
399 | if (QQmlDebugTranslationService *service |
400 | = QQmlDebugConnector::service<QQmlDebugTranslationService>()) { |
401 | service->foundTranslationBinding(binding: b, scopeObject: obj, contextData: ctxt); |
402 | } |
403 | |
404 | return b; |
405 | } |
406 | |
407 | Q_NEVER_INLINE bool QQmlBinding::slowWrite(const QQmlPropertyData &core, |
408 | const QQmlPropertyData &valueTypeData, |
409 | const QV4::Value &result, |
410 | bool isUndefined, QQmlPropertyData::WriteFlags flags) |
411 | { |
412 | QQmlEngine *engine = context()->engine; |
413 | QV4::ExecutionEngine *v4engine = engine->handle(); |
414 | |
415 | int type = valueTypeData.isValid() ? valueTypeData.propType() : core.propType(); |
416 | |
417 | QQmlJavaScriptExpression::DeleteWatcher watcher(this); |
418 | |
419 | QVariant value; |
420 | bool isVarProperty = core.isVarProperty(); |
421 | |
422 | if (isUndefined) { |
423 | } else if (core.isQList()) { |
424 | value = v4engine->toVariant(value: result, typeHint: qMetaTypeId<QList<QObject *> >()); |
425 | } else if (result.isNull() && core.isQObject()) { |
426 | value = QVariant::fromValue(value: (QObject *)nullptr); |
427 | } else if (core.propType() == qMetaTypeId<QList<QUrl> >()) { |
428 | value = QQmlPropertyPrivate::resolvedUrlSequence(value: v4engine->toVariant(value: result, typeHint: qMetaTypeId<QList<QUrl> >()), context: context()); |
429 | } else if (!isVarProperty && type != qMetaTypeId<QJSValue>()) { |
430 | value = v4engine->toVariant(value: result, typeHint: type); |
431 | } |
432 | |
433 | if (hasError()) { |
434 | return false; |
435 | } else if (isVarProperty) { |
436 | const QV4::FunctionObject *f = result.as<QV4::FunctionObject>(); |
437 | if (f && f->isBinding()) { |
438 | // we explicitly disallow this case to avoid confusion. Users can still store one |
439 | // in an array in a var property if they need to, but the common case is user error. |
440 | delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration." )); |
441 | return false; |
442 | } |
443 | |
444 | QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(obj: m_target.data()); |
445 | Q_ASSERT(vmemo); |
446 | vmemo->setVMEProperty(index: core.coreIndex(), v: result); |
447 | } else if (isUndefined && core.isResettable()) { |
448 | void *args[] = { nullptr }; |
449 | QMetaObject::metacall(m_target.data(), QMetaObject::ResetProperty, core.coreIndex(), args); |
450 | } else if (isUndefined && type == qMetaTypeId<QVariant>()) { |
451 | QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, QVariant(), context(), flags); |
452 | } else if (type == qMetaTypeId<QJSValue>()) { |
453 | const QV4::FunctionObject *f = result.as<QV4::FunctionObject>(); |
454 | if (f && f->isBinding()) { |
455 | delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration." )); |
456 | return false; |
457 | } |
458 | QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, QVariant::fromValue( |
459 | value: QJSValue(v4engine, result.asReturnedValue())), |
460 | context(), flags); |
461 | } else if (isUndefined) { |
462 | const QLatin1String typeName(QMetaType::typeName(type) |
463 | ? QMetaType::typeName(type) |
464 | : "[unknown property type]" ); |
465 | delayedError()->setErrorDescription(QLatin1String("Unable to assign [undefined] to " ) |
466 | + typeName); |
467 | return false; |
468 | } else if (const QV4::FunctionObject *f = result.as<QV4::FunctionObject>()) { |
469 | if (f->isBinding()) |
470 | delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration." )); |
471 | else |
472 | delayedError()->setErrorDescription(QLatin1String("Unable to assign a function to a property of any type other than var." )); |
473 | return false; |
474 | } else if (!QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, value, context(), flags)) { |
475 | |
476 | if (watcher.wasDeleted()) |
477 | return true; |
478 | |
479 | const char *valueType = nullptr; |
480 | const char *propertyType = nullptr; |
481 | |
482 | const int userType = value.userType(); |
483 | if (userType == QMetaType::QObjectStar) { |
484 | if (QObject *o = *(QObject *const *)value.constData()) { |
485 | valueType = o->metaObject()->className(); |
486 | |
487 | QQmlMetaObject propertyMetaObject = QQmlPropertyPrivate::rawMetaObjectForType(QQmlEnginePrivate::get(e: engine), type); |
488 | if (!propertyMetaObject.isNull()) |
489 | propertyType = propertyMetaObject.className(); |
490 | } |
491 | } else if (userType != QMetaType::UnknownType) { |
492 | if (userType == QMetaType::Nullptr || userType == QMetaType::VoidStar) |
493 | valueType = "null" ; |
494 | else |
495 | valueType = QMetaType::typeName(type: userType); |
496 | } |
497 | |
498 | if (!valueType) |
499 | valueType = "undefined" ; |
500 | if (!propertyType) |
501 | propertyType = QMetaType::typeName(type); |
502 | if (!propertyType) |
503 | propertyType = "[unknown property type]" ; |
504 | |
505 | delayedError()->setErrorDescription(QLatin1String("Unable to assign " ) + |
506 | QLatin1String(valueType) + |
507 | QLatin1String(" to " ) + |
508 | QLatin1String(propertyType)); |
509 | return false; |
510 | } |
511 | |
512 | return true; |
513 | } |
514 | |
515 | QVariant QQmlBinding::evaluate() |
516 | { |
517 | QQmlEngine *engine = context()->engine; |
518 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: engine); |
519 | ep->referenceScarceResources(); |
520 | |
521 | bool isUndefined = false; |
522 | |
523 | QV4::Scope scope(engine->handle()); |
524 | QV4::ScopedValue result(scope, QQmlJavaScriptExpression::evaluate(isUndefined: &isUndefined)); |
525 | |
526 | ep->dereferenceScarceResources(); |
527 | |
528 | return scope.engine->toVariant(value: result, typeHint: qMetaTypeId<QList<QObject*> >()); |
529 | } |
530 | |
531 | QString QQmlBinding::expressionIdentifier() const |
532 | { |
533 | if (auto f = function()) { |
534 | QString url = f->sourceFile(); |
535 | uint lineNumber = f->compiledFunction->location.line; |
536 | uint columnNumber = f->compiledFunction->location.column; |
537 | return url + QString::asprintf(format: ":%u:%u" , lineNumber, columnNumber); |
538 | } |
539 | |
540 | return QStringLiteral("[native code]" ); |
541 | } |
542 | |
543 | void QQmlBinding::expressionChanged() |
544 | { |
545 | update(); |
546 | } |
547 | |
548 | void QQmlBinding::refresh() |
549 | { |
550 | update(); |
551 | } |
552 | |
553 | void QQmlBinding::setEnabled(bool e, QQmlPropertyData::WriteFlags flags) |
554 | { |
555 | const bool wasEnabled = enabledFlag(); |
556 | setEnabledFlag(e); |
557 | setNotifyOnValueChanged(e); |
558 | |
559 | m_nextBinding.setFlag2(); // Always use accessors, only not when: |
560 | if (auto interceptorMetaObject = QQmlInterceptorMetaObject::get(obj: targetObject())) { |
561 | if (!m_targetIndex.isValid() || interceptorMetaObject->intercepts(propertyIndex: m_targetIndex)) |
562 | m_nextBinding.clearFlag2(); |
563 | } |
564 | |
565 | if (e && !wasEnabled) |
566 | update(flags); |
567 | } |
568 | |
569 | QString QQmlBinding::expression() const |
570 | { |
571 | return QStringLiteral("function() { [native code] }" ); |
572 | } |
573 | |
574 | void QQmlBinding::setTarget(const QQmlProperty &prop) |
575 | { |
576 | auto pd = QQmlPropertyPrivate::get(p: prop); |
577 | setTarget(prop.object(), pd->core, valueType: &pd->valueTypeData); |
578 | } |
579 | |
580 | bool QQmlBinding::setTarget(QObject *object, const QQmlPropertyData &core, const QQmlPropertyData *valueType) |
581 | { |
582 | m_target = object; |
583 | |
584 | if (!object) { |
585 | m_targetIndex = QQmlPropertyIndex(); |
586 | return false; |
587 | } |
588 | |
589 | int coreIndex = core.coreIndex(); |
590 | int valueTypeIndex = valueType ? valueType->coreIndex() : -1; |
591 | for (bool isAlias = core.isAlias(); isAlias; ) { |
592 | QQmlVMEMetaObject *vme = QQmlVMEMetaObject::getForProperty(o: object, coreIndex); |
593 | |
594 | int aValueTypeIndex; |
595 | if (!vme->aliasTarget(index: coreIndex, target: &object, coreIndex: &coreIndex, valueTypeIndex: &aValueTypeIndex)) { |
596 | // can't resolve id (yet) |
597 | m_target = nullptr; |
598 | m_targetIndex = QQmlPropertyIndex(); |
599 | return false; |
600 | } |
601 | if (valueTypeIndex == -1) |
602 | valueTypeIndex = aValueTypeIndex; |
603 | |
604 | QQmlData *data = QQmlData::get(object, create: false); |
605 | if (!data || !data->propertyCache) { |
606 | m_target = nullptr; |
607 | m_targetIndex = QQmlPropertyIndex(); |
608 | return false; |
609 | } |
610 | QQmlPropertyData *propertyData = data->propertyCache->property(index: coreIndex); |
611 | Q_ASSERT(propertyData); |
612 | |
613 | m_target = object; |
614 | isAlias = propertyData->isAlias(); |
615 | coreIndex = propertyData->coreIndex(); |
616 | } |
617 | m_targetIndex = QQmlPropertyIndex(coreIndex, valueTypeIndex); |
618 | |
619 | QQmlData *data = QQmlData::get(object: *m_target, create: true); |
620 | if (!data->propertyCache) { |
621 | data->propertyCache = QQmlEnginePrivate::get(e: context()->engine)->cache(metaObject: m_target->metaObject()); |
622 | data->propertyCache->addref(); |
623 | } |
624 | |
625 | return true; |
626 | } |
627 | |
628 | void QQmlBinding::getPropertyData(QQmlPropertyData **propertyData, QQmlPropertyData *valueTypeData) const |
629 | { |
630 | Q_ASSERT(propertyData); |
631 | |
632 | QQmlData *data = QQmlData::get(object: *m_target, create: false); |
633 | Q_ASSERT(data); |
634 | |
635 | if (Q_UNLIKELY(!data->propertyCache)) { |
636 | data->propertyCache = QQmlEnginePrivate::get(e: context()->engine)->cache(metaObject: m_target->metaObject()); |
637 | data->propertyCache->addref(); |
638 | } |
639 | |
640 | *propertyData = data->propertyCache->property(index: m_targetIndex.coreIndex()); |
641 | Q_ASSERT(*propertyData); |
642 | |
643 | if (Q_UNLIKELY(m_targetIndex.hasValueTypeIndex() && valueTypeData)) { |
644 | const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(type: (*propertyData)->propType()); |
645 | Q_ASSERT(valueTypeMetaObject); |
646 | QMetaProperty vtProp = valueTypeMetaObject->property(index: m_targetIndex.valueTypeIndex()); |
647 | valueTypeData->setFlags(QQmlPropertyData::flagsForProperty(vtProp)); |
648 | |
649 | // valueTypeData is expected to be local here. It must not be shared with other threads. |
650 | valueTypeData->setPropType(vtProp.userType()); |
651 | |
652 | valueTypeData->setCoreIndex(m_targetIndex.valueTypeIndex()); |
653 | } |
654 | } |
655 | |
656 | QVector<QQmlProperty> QQmlBinding::dependencies() const |
657 | { |
658 | QVector<QQmlProperty> dependencies; |
659 | if (!m_target.data()) |
660 | return dependencies; |
661 | |
662 | for (QQmlJavaScriptExpressionGuard *guard = activeGuards.first(); guard; guard = activeGuards.next(v: guard)) { |
663 | if (guard->signalIndex() == -1) // guard's sender is a QQmlNotifier, not a QObject*. |
664 | continue; |
665 | |
666 | QObject *senderObject = guard->senderAsObject(); |
667 | if (!senderObject) |
668 | continue; |
669 | |
670 | const QMetaObject *senderMeta = senderObject->metaObject(); |
671 | if (!senderMeta) |
672 | continue; |
673 | |
674 | for (int i = 0; i < senderMeta->propertyCount(); i++) { |
675 | QMetaProperty property = senderMeta->property(index: i); |
676 | if (property.notifySignalIndex() == QMetaObjectPrivate::signal(m: senderMeta, signal_index: guard->signalIndex()).methodIndex()) { |
677 | dependencies.push_back(t: QQmlProperty(senderObject, QString::fromUtf8(str: senderObject->metaObject()->property(index: i).name()))); |
678 | } |
679 | } |
680 | } |
681 | |
682 | return dependencies; |
683 | } |
684 | |
685 | bool QQmlBinding::hasDependencies() const |
686 | { |
687 | return !activeGuards.isEmpty() || translationsCaptured(); |
688 | } |
689 | |
690 | class QObjectPointerBinding: public QQmlNonbindingBinding |
691 | { |
692 | QQmlMetaObject targetMetaObject; |
693 | |
694 | public: |
695 | QObjectPointerBinding(QQmlEnginePrivate *engine, int propertyType) |
696 | : targetMetaObject(QQmlPropertyPrivate::rawMetaObjectForType(engine, propertyType)) |
697 | {} |
698 | |
699 | protected: |
700 | Q_ALWAYS_INLINE bool write(const QV4::Value &result, bool isUndefined, |
701 | QQmlPropertyData::WriteFlags flags) override final |
702 | { |
703 | QQmlPropertyData *pd; |
704 | QQmlPropertyData vtpd; |
705 | getPropertyData(propertyData: &pd, valueTypeData: &vtpd); |
706 | if (Q_UNLIKELY(isUndefined || vtpd.isValid())) |
707 | return slowWrite(core: *pd, valueTypeData: vtpd, result, isUndefined, flags); |
708 | |
709 | // Check if the result is a QObject: |
710 | QObject *resultObject = nullptr; |
711 | QQmlMetaObject resultMo; |
712 | if (result.isNull()) { |
713 | // Special case: we can always write a nullptr. Don't bother checking anything else. |
714 | return pd->writeProperty(target: targetObject(), value: &resultObject, flags); |
715 | } else if (auto wrapper = result.as<QV4::QObjectWrapper>()) { |
716 | resultObject = wrapper->object(); |
717 | if (!resultObject) |
718 | return pd->writeProperty(target: targetObject(), value: &resultObject, flags); |
719 | if (QQmlData *ddata = QQmlData::get(object: resultObject, create: false)) |
720 | resultMo = ddata->propertyCache; |
721 | if (resultMo.isNull()) { |
722 | resultMo = resultObject->metaObject(); |
723 | } |
724 | } else if (auto variant = result.as<QV4::VariantObject>()) { |
725 | QVariant value = variant->d()->data(); |
726 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(c: context()); |
727 | resultMo = QQmlPropertyPrivate::rawMetaObjectForType(ep, value.userType()); |
728 | if (resultMo.isNull()) |
729 | return slowWrite(core: *pd, valueTypeData: vtpd, result, isUndefined, flags); |
730 | resultObject = *static_cast<QObject *const *>(value.constData()); |
731 | } else { |
732 | return slowWrite(core: *pd, valueTypeData: vtpd, result, isUndefined, flags); |
733 | } |
734 | |
735 | // Compare & set: |
736 | if (QQmlMetaObject::canConvert(from: resultMo, to: targetMetaObject)) { |
737 | return pd->writeProperty(target: targetObject(), value: &resultObject, flags); |
738 | } else if (!resultObject && QQmlMetaObject::canConvert(from: targetMetaObject, to: resultMo)) { |
739 | // In the case of a null QObject, we assign the null if there is |
740 | // any change that the null variant type could be up or down cast to |
741 | // the property type. |
742 | return pd->writeProperty(target: targetObject(), value: &resultObject, flags); |
743 | } else { |
744 | return slowWrite(core: *pd, valueTypeData: vtpd, result, isUndefined, flags); |
745 | } |
746 | } |
747 | }; |
748 | |
749 | QQmlBinding *QQmlBinding::newBinding(QQmlEnginePrivate *engine, const QQmlPropertyData *property) |
750 | { |
751 | if (property && property->isQObject()) |
752 | return new QObjectPointerBinding(engine, property->propType()); |
753 | |
754 | // If the property is not resolved at this point, you get a binding of unknown type. |
755 | // This has been the case for a long time and we keep it like this in Qt5 to be bug-compatible. |
756 | const int type = (property && property->isResolved()) |
757 | ? property->propType() |
758 | : QMetaType::UnknownType; |
759 | |
760 | if (type == qMetaTypeId<QQmlBinding *>()) { |
761 | return new QQmlBindingBinding; |
762 | } |
763 | |
764 | switch (type) { |
765 | case QMetaType::Bool: |
766 | return new GenericBinding<QMetaType::Bool>; |
767 | case QMetaType::Int: |
768 | return new GenericBinding<QMetaType::Int>; |
769 | case QMetaType::Double: |
770 | return new GenericBinding<QMetaType::Double>; |
771 | case QMetaType::Float: |
772 | return new GenericBinding<QMetaType::Float>; |
773 | case QMetaType::QString: |
774 | return new GenericBinding<QMetaType::QString>; |
775 | default: |
776 | return new GenericBinding<QMetaType::UnknownType>; |
777 | } |
778 | } |
779 | |
780 | QT_END_NAMESPACE |
781 | |