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 "qqmlcontext.h" |
41 | #include "qqmlcontext_p.h" |
42 | #include "qqmlcomponentattached_p.h" |
43 | |
44 | #include "qqmlcomponent_p.h" |
45 | #include "qqmlexpression_p.h" |
46 | #include "qqmlengine_p.h" |
47 | #include "qqmlengine.h" |
48 | #include "qqmlinfo.h" |
49 | #include "qqmlabstracturlinterceptor.h" |
50 | |
51 | #include <qjsengine.h> |
52 | #include <QtCore/qvarlengtharray.h> |
53 | #include <private/qmetaobject_p.h> |
54 | #include <QtQml/private/qqmlcontext_p.h> |
55 | #include <QtCore/qdebug.h> |
56 | |
57 | QT_BEGIN_NAMESPACE |
58 | |
59 | QQmlContextPrivate::QQmlContextPrivate() |
60 | : data(nullptr), notifyIndex(-1) |
61 | { |
62 | } |
63 | |
64 | /*! |
65 | \class QQmlContext |
66 | \brief The QQmlContext class defines a context within a QML engine. |
67 | \inmodule QtQml |
68 | |
69 | Contexts allow data to be exposed to the QML components instantiated by the |
70 | QML engine. |
71 | |
72 | Each QQmlContext contains a set of properties, distinct from its QObject |
73 | properties, that allow data to be explicitly bound to a context by name. The |
74 | context properties are defined and updated by calling |
75 | QQmlContext::setContextProperty(). The following example shows a Qt model |
76 | being bound to a context and then accessed from a QML file. |
77 | |
78 | \code |
79 | QQmlEngine engine; |
80 | QStringListModel modelData; |
81 | QQmlContext *context = new QQmlContext(engine.rootContext()); |
82 | context->setContextProperty("myModel", &modelData); |
83 | |
84 | QQmlComponent component(&engine); |
85 | component.setData("import QtQuick 2.0; ListView { model: myModel }", QUrl()); |
86 | QObject *window = component.create(context); |
87 | \endcode |
88 | |
89 | \note It is the responsibility of the creator to delete any QQmlContext it |
90 | constructs. If the \c context object in the example is no longer needed when the |
91 | \c window component instance is destroyed, the \c context must be destroyed explicitly. |
92 | The simplest way to ensure this is to set \c window as the parent of \c context. |
93 | |
94 | To simplify binding and maintaining larger data sets, a context object can be set |
95 | on a QQmlContext. All the properties of the context object are available |
96 | by name in the context, as though they were all individually added through calls |
97 | to QQmlContext::setContextProperty(). Changes to the property's values are |
98 | detected through the property's notify signal. Setting a context object is both |
99 | faster and easier than manually adding and maintaining context property values. |
100 | |
101 | The following example has the same effect as the previous one, but it uses a context |
102 | object. |
103 | |
104 | \code |
105 | class MyDataSet : public QObject { |
106 | // ... |
107 | Q_PROPERTY(QAbstractItemModel *myModel READ model NOTIFY modelChanged) |
108 | // ... |
109 | }; |
110 | |
111 | MyDataSet myDataSet; |
112 | QQmlEngine engine; |
113 | QQmlContext *context = new QQmlContext(engine.rootContext()); |
114 | context->setContextObject(&myDataSet); |
115 | |
116 | QQmlComponent component(&engine); |
117 | component.setData("import QtQuick 2.0; ListView { model: myModel }", QUrl()); |
118 | component.create(context); |
119 | \endcode |
120 | |
121 | All properties added explicitly by QQmlContext::setContextProperty() take |
122 | precedence over the context object's properties. |
123 | |
124 | \section2 The Context Hierarchy |
125 | |
126 | Contexts form a hierarchy. The root of this hierarchy is the QML engine's |
127 | \l {QQmlEngine::rootContext()}{root context}. Child contexts inherit |
128 | the context properties of their parents; if a child context sets a context property |
129 | that already exists in its parent, the new context property overrides that of the |
130 | parent. |
131 | |
132 | The following example defines two contexts - \c context1 and \c context2. The |
133 | second context overrides the "b" context property inherited from the first with a |
134 | new value. |
135 | |
136 | \code |
137 | QQmlEngine engine; |
138 | QQmlContext *context1 = new QQmlContext(engine.rootContext()); |
139 | QQmlContext *context2 = new QQmlContext(context1); |
140 | |
141 | context1->setContextProperty("a", 9001); |
142 | context1->setContextProperty("b", 9001); |
143 | |
144 | context2->setContextProperty("b", 42); |
145 | \endcode |
146 | |
147 | While QML objects instantiated in a context are not strictly owned by that |
148 | context, their bindings are. If a context is destroyed, the property bindings of |
149 | outstanding QML objects will stop evaluating. |
150 | |
151 | \warning Setting the context object or adding new context properties after an object |
152 | has been created in that context is an expensive operation (essentially forcing all bindings |
153 | to reevaluate). Thus whenever possible you should complete "setup" of the context |
154 | before using it to create any objects. |
155 | |
156 | \sa {qtqml-cppintegration-exposecppattributes.html}{Exposing Attributes of C++ Types to QML} |
157 | */ |
158 | |
159 | /*! \internal */ |
160 | QQmlContext::QQmlContext(QQmlEngine *e, bool) |
161 | : QObject(*(new QQmlContextPrivate)) |
162 | { |
163 | Q_D(QQmlContext); |
164 | d->data = new QQmlContextData(this); |
165 | ++d->data->refCount; |
166 | |
167 | d->data->engine = e; |
168 | } |
169 | |
170 | /*! |
171 | Create a new QQmlContext as a child of \a engine's root context, and the |
172 | QObject \a parent. |
173 | */ |
174 | QQmlContext::QQmlContext(QQmlEngine *engine, QObject *parent) |
175 | : QObject(*(new QQmlContextPrivate), parent) |
176 | { |
177 | Q_D(QQmlContext); |
178 | d->data = new QQmlContextData(this); |
179 | ++d->data->refCount; |
180 | |
181 | d->data->setParent(engine?QQmlContextData::get(context: engine->rootContext()):nullptr); |
182 | } |
183 | |
184 | /*! |
185 | Create a new QQmlContext with the given \a parentContext, and the |
186 | QObject \a parent. |
187 | */ |
188 | QQmlContext::QQmlContext(QQmlContext *parentContext, QObject *parent) |
189 | : QObject(*(new QQmlContextPrivate), parent) |
190 | { |
191 | Q_D(QQmlContext); |
192 | d->data = new QQmlContextData(this); |
193 | ++d->data->refCount; |
194 | |
195 | d->data->setParent(parentContext?QQmlContextData::get(context: parentContext):nullptr); |
196 | } |
197 | |
198 | /*! |
199 | \internal |
200 | */ |
201 | QQmlContext::QQmlContext(QQmlContextData *data) |
202 | : QObject(*(new QQmlContextPrivate), nullptr) |
203 | { |
204 | Q_D(QQmlContext); |
205 | d->data = data; |
206 | // don't add a refcount here, as the data owns this context |
207 | } |
208 | |
209 | /*! |
210 | Destroys the QQmlContext. |
211 | |
212 | Any expressions, or sub-contexts dependent on this context will be |
213 | invalidated, but not destroyed (unless they are parented to the QQmlContext |
214 | object). |
215 | */ |
216 | QQmlContext::~QQmlContext() |
217 | { |
218 | Q_D(QQmlContext); |
219 | |
220 | d->data->publicContext = nullptr; |
221 | if (!--d->data->refCount) |
222 | d->data->destroy(); |
223 | } |
224 | |
225 | /*! |
226 | Returns whether the context is valid. |
227 | |
228 | To be valid, a context must have a engine, and it's contextObject(), if any, |
229 | must not have been deleted. |
230 | */ |
231 | bool QQmlContext::isValid() const |
232 | { |
233 | Q_D(const QQmlContext); |
234 | return d->data && d->data->isValid(); |
235 | } |
236 | |
237 | /*! |
238 | Return the context's QQmlEngine, or \nullptr if the context has no QQmlEngine or the |
239 | QQmlEngine was destroyed. |
240 | */ |
241 | QQmlEngine *QQmlContext::engine() const |
242 | { |
243 | Q_D(const QQmlContext); |
244 | return d->data->engine; |
245 | } |
246 | |
247 | /*! |
248 | Return the context's parent QQmlContext, or \nullptr if this context has no |
249 | parent or if the parent has been destroyed. |
250 | */ |
251 | QQmlContext *QQmlContext::parentContext() const |
252 | { |
253 | Q_D(const QQmlContext); |
254 | return d->data->parent?d->data->parent->asQQmlContext():nullptr; |
255 | } |
256 | |
257 | /*! |
258 | Return the context object, or \nullptr if there is no context object. |
259 | */ |
260 | QObject *QQmlContext::contextObject() const |
261 | { |
262 | Q_D(const QQmlContext); |
263 | return d->data->contextObject; |
264 | } |
265 | |
266 | /*! |
267 | Set the context \a object. |
268 | */ |
269 | void QQmlContext::setContextObject(QObject *object) |
270 | { |
271 | Q_D(QQmlContext); |
272 | |
273 | QQmlContextData *data = d->data; |
274 | |
275 | if (data->isInternal) { |
276 | qWarning(msg: "QQmlContext: Cannot set context object for internal context." ); |
277 | return; |
278 | } |
279 | |
280 | if (!isValid()) { |
281 | qWarning(msg: "QQmlContext: Cannot set context object on invalid context." ); |
282 | return; |
283 | } |
284 | |
285 | data->contextObject = object; |
286 | data->refreshExpressions(); |
287 | } |
288 | |
289 | /*! |
290 | Set a the \a value of the \a name property on this context. |
291 | */ |
292 | void QQmlContext::setContextProperty(const QString &name, const QVariant &value) |
293 | { |
294 | Q_D(QQmlContext); |
295 | if (d->notifyIndex == -1) |
296 | d->notifyIndex = QMetaObjectPrivate::absoluteSignalCount(m: &QQmlContext::staticMetaObject); |
297 | |
298 | QQmlContextData *data = d->data; |
299 | |
300 | if (data->isInternal) { |
301 | qWarning(msg: "QQmlContext: Cannot set property on internal context." ); |
302 | return; |
303 | } |
304 | |
305 | if (!isValid()) { |
306 | qWarning(msg: "QQmlContext: Cannot set property on invalid context." ); |
307 | return; |
308 | } |
309 | |
310 | QV4::IdentifierHash &properties = data->detachedPropertyNames(); |
311 | int idx = properties.value(str: name); |
312 | if (idx == -1) { |
313 | properties.add(str: name, value: data->idValueCount + d->propertyValues.count()); |
314 | d->propertyValues.append(t: value); |
315 | |
316 | data->refreshExpressions(); |
317 | } else { |
318 | d->propertyValues[idx] = value; |
319 | QMetaObject::activate(sender: this, signal_offset: d->notifyIndex, local_signal_index: idx, argv: nullptr); |
320 | } |
321 | |
322 | if (auto *obj = qvariant_cast<QObject *>(v: value)) { |
323 | connect(sender: obj, signal: &QObject::destroyed, context: this, slot: [d, name](QObject *destroyed) { |
324 | d->dropDestroyedQObject(name, destroyed); |
325 | }); |
326 | } |
327 | } |
328 | |
329 | /*! |
330 | Set the \a value of the \a name property on this context. |
331 | |
332 | QQmlContext does \b not take ownership of \a value. |
333 | */ |
334 | void QQmlContext::setContextProperty(const QString &name, QObject *value) |
335 | { |
336 | setContextProperty(name, value: QVariant::fromValue(value)); |
337 | } |
338 | |
339 | /*! |
340 | \since 5.11 |
341 | |
342 | Set a batch of \a properties on this context. |
343 | |
344 | Setting all properties in one batch avoids unnecessary |
345 | refreshing expressions, and is therefore recommended |
346 | instead of calling \l setContextProperty() for each individual property. |
347 | |
348 | \sa QQmlContext::setContextProperty() |
349 | */ |
350 | void QQmlContext::setContextProperties(const QVector<PropertyPair> &properties) |
351 | { |
352 | Q_D(const QQmlContext); |
353 | |
354 | QQmlContextData *data = d->data; |
355 | |
356 | QQmlJavaScriptExpression *expressions = data->expressions; |
357 | QQmlContextData *childContexts = data->childContexts; |
358 | |
359 | data->expressions = nullptr; |
360 | data->childContexts = nullptr; |
361 | |
362 | for (const auto &property : properties) |
363 | setContextProperty(name: property.name, value: property.value); |
364 | |
365 | data->expressions = expressions; |
366 | data->childContexts = childContexts; |
367 | |
368 | data->refreshExpressions(); |
369 | } |
370 | |
371 | /*! |
372 | \since 5.11 |
373 | |
374 | \class QQmlContext::PropertyPair |
375 | \inmodule QtQml |
376 | |
377 | This struct contains a property name and a property value. |
378 | It is used as a parameter for the \c setContextProperties function. |
379 | |
380 | \sa QQmlContext::setContextProperties() |
381 | */ |
382 | |
383 | /*! |
384 | Returns the value of the \a name property for this context |
385 | as a QVariant. |
386 | */ |
387 | QVariant QQmlContext::contextProperty(const QString &name) const |
388 | { |
389 | Q_D(const QQmlContext); |
390 | QVariant value; |
391 | int idx = -1; |
392 | |
393 | QQmlContextData *data = d->data; |
394 | |
395 | const QV4::IdentifierHash &properties = data->propertyNames(); |
396 | if (properties.count()) |
397 | idx = properties.value(str: name); |
398 | |
399 | if (idx == -1) { |
400 | if (data->contextObject) { |
401 | QObject *obj = data->contextObject; |
402 | QQmlPropertyData local; |
403 | QQmlPropertyData *property = |
404 | QQmlPropertyCache::property(engine: data->engine, obj, name, context: data, local); |
405 | |
406 | if (property) value = obj->metaObject()->property(index: property->coreIndex()).read(obj); |
407 | } |
408 | if (!value.isValid() && parentContext()) |
409 | value = parentContext()->contextProperty(name); |
410 | } else { |
411 | if (idx >= d->propertyValues.count()) |
412 | value = QVariant::fromValue(value: data->idValues[idx - d->propertyValues.count()].data()); |
413 | else |
414 | value = d->propertyValues[idx]; |
415 | } |
416 | |
417 | return value; |
418 | } |
419 | |
420 | /*! |
421 | Returns the name of \a object in this context, or an empty string if \a object |
422 | is not named in the context. Objects are named by setContextProperty(), or by ids in |
423 | the case of QML created contexts. |
424 | |
425 | If the object has multiple names, the first is returned. |
426 | */ |
427 | QString QQmlContext::nameForObject(QObject *object) const |
428 | { |
429 | Q_D(const QQmlContext); |
430 | |
431 | return d->data->findObjectId(obj: object); |
432 | } |
433 | |
434 | /*! |
435 | Resolves the URL \a src relative to the URL of the |
436 | containing component. |
437 | |
438 | \sa QQmlEngine::baseUrl(), setBaseUrl() |
439 | */ |
440 | QUrl QQmlContext::resolvedUrl(const QUrl &src) |
441 | { |
442 | Q_D(QQmlContext); |
443 | return d->data->resolvedUrl(src); |
444 | } |
445 | |
446 | QUrl QQmlContextData::resolvedUrl(const QUrl &src) |
447 | { |
448 | QUrl resolved; |
449 | if (src.isRelative() && !src.isEmpty()) { |
450 | QQmlContextData *ctxt = this; |
451 | do { |
452 | if (ctxt->url().isValid()) |
453 | break; |
454 | else |
455 | ctxt = ctxt->parent; |
456 | } while (ctxt); |
457 | |
458 | if (ctxt) |
459 | resolved = ctxt->url().resolved(relative: src); |
460 | else if (engine) |
461 | resolved = engine->baseUrl().resolved(relative: src); |
462 | } else { |
463 | resolved = src; |
464 | } |
465 | |
466 | if (resolved.isEmpty()) //relative but no ctxt |
467 | return resolved; |
468 | |
469 | if (engine && engine->urlInterceptor()) |
470 | resolved = engine->urlInterceptor()->intercept(path: resolved, type: QQmlAbstractUrlInterceptor::UrlString); |
471 | return resolved; |
472 | } |
473 | |
474 | |
475 | /*! |
476 | Explicitly sets the url resolvedUrl() will use for relative references to \a baseUrl. |
477 | |
478 | Calling this function will override the url of the containing |
479 | component used by default. |
480 | |
481 | \sa resolvedUrl() |
482 | */ |
483 | void QQmlContext::setBaseUrl(const QUrl &baseUrl) |
484 | { |
485 | Q_D(QQmlContext); |
486 | |
487 | d->data->baseUrl = baseUrl; |
488 | d->data->baseUrlString = baseUrl.toString(); |
489 | } |
490 | |
491 | /*! |
492 | Returns the base url of the component, or the containing component |
493 | if none is set. |
494 | */ |
495 | QUrl QQmlContext::baseUrl() const |
496 | { |
497 | Q_D(const QQmlContext); |
498 | const QQmlContextData* data = d->data; |
499 | while (data && data->url().isEmpty()) |
500 | data = data->parent; |
501 | |
502 | if (data) |
503 | return data->url(); |
504 | else |
505 | return QUrl(); |
506 | } |
507 | |
508 | int QQmlContextPrivate::context_count(QQmlListProperty<QObject> *prop) |
509 | { |
510 | QQmlContext *context = static_cast<QQmlContext*>(prop->object); |
511 | QQmlContextPrivate *d = QQmlContextPrivate::get(context); |
512 | int contextProperty = (int)(quintptr)prop->data; |
513 | |
514 | if (d->propertyValues.at(i: contextProperty).userType() != qMetaTypeId<QList<QObject*> >()) { |
515 | return 0; |
516 | } else { |
517 | return ((const QList<QObject> *)d->propertyValues.at(i: contextProperty).constData())->count(); |
518 | } |
519 | } |
520 | |
521 | QObject *QQmlContextPrivate::context_at(QQmlListProperty<QObject> *prop, int index) |
522 | { |
523 | QQmlContext *context = static_cast<QQmlContext*>(prop->object); |
524 | QQmlContextPrivate *d = QQmlContextPrivate::get(context); |
525 | int contextProperty = (int)(quintptr)prop->data; |
526 | |
527 | if (d->propertyValues.at(i: contextProperty).userType() != qMetaTypeId<QList<QObject*> >()) { |
528 | return nullptr; |
529 | } else { |
530 | return ((const QList<QObject*> *)d->propertyValues.at(i: contextProperty).constData())->at(i: index); |
531 | } |
532 | } |
533 | |
534 | void QQmlContextPrivate::dropDestroyedQObject(const QString &name, QObject *destroyed) |
535 | { |
536 | if (!data->isValid()) |
537 | return; |
538 | |
539 | const int idx = data->propertyNames().value(str: name); |
540 | Q_ASSERT(idx >= 0); |
541 | if (qvariant_cast<QObject *>(v: propertyValues[idx]) != destroyed) |
542 | return; |
543 | |
544 | propertyValues[idx] = QVariant::fromValue<QObject *>(value: nullptr); |
545 | QMetaObject::activate(sender: q_func(), signal_offset: notifyIndex, local_signal_index: idx, argv: nullptr); |
546 | } |
547 | |
548 | |
549 | QQmlContextData::QQmlContextData() |
550 | : QQmlContextData(nullptr) |
551 | { |
552 | } |
553 | |
554 | QQmlContextData::QQmlContextData(QQmlContext *ctxt) |
555 | : engine(nullptr), isInternal(false), isJSContext(false), |
556 | isPragmaLibraryContext(false), unresolvedNames(false), hasEmittedDestruction(false), isRootObjectInCreation(false), |
557 | stronglyReferencedByParent(false), hasExtraObject(false), publicContext(ctxt), incubator(nullptr), componentObjectIndex(-1), |
558 | contextObject(nullptr), nextChild(nullptr), prevChild(nullptr), |
559 | expressions(nullptr), contextObjects(nullptr), idValues(nullptr), idValueCount(0), |
560 | componentAttached(nullptr) |
561 | { |
562 | } |
563 | |
564 | void QQmlContextData::emitDestruction() |
565 | { |
566 | if (!hasEmittedDestruction) { |
567 | hasEmittedDestruction = true; |
568 | |
569 | // Emit the destruction signal - must be emitted before invalidate so that the |
570 | // context is still valid if bindings or resultant expression evaluation requires it |
571 | if (engine) { |
572 | while (componentAttached) { |
573 | QQmlComponentAttached *a = componentAttached; |
574 | componentAttached = a->next; |
575 | if (componentAttached) componentAttached->prev = &componentAttached; |
576 | |
577 | a->next = nullptr; |
578 | a->prev = nullptr; |
579 | |
580 | emit a->destruction(); |
581 | } |
582 | |
583 | QQmlContextDataRef child = childContexts; |
584 | while (!child.isNull()) { |
585 | child->emitDestruction(); |
586 | child = child->nextChild; |
587 | } |
588 | } |
589 | } |
590 | } |
591 | |
592 | void QQmlContextData::invalidate() |
593 | { |
594 | emitDestruction(); |
595 | |
596 | while (childContexts) { |
597 | Q_ASSERT(childContexts != this); |
598 | if (childContexts->stronglyReferencedByParent && !--childContexts->refCount) |
599 | childContexts->destroy(); |
600 | else |
601 | childContexts->invalidate(); |
602 | } |
603 | |
604 | if (prevChild) { |
605 | *prevChild = nextChild; |
606 | if (nextChild) nextChild->prevChild = prevChild; |
607 | nextChild = nullptr; |
608 | prevChild = nullptr; |
609 | } |
610 | |
611 | importedScripts.clear(); |
612 | |
613 | engine = nullptr; |
614 | parent = nullptr; |
615 | } |
616 | |
617 | void QQmlContextData::clearContextRecursively() |
618 | { |
619 | clearContext(); |
620 | |
621 | for (auto ctxIt = childContexts; ctxIt; ctxIt = ctxIt->nextChild) |
622 | ctxIt->clearContextRecursively(); |
623 | } |
624 | |
625 | void QQmlContextData::clearContext() |
626 | { |
627 | emitDestruction(); |
628 | |
629 | QQmlJavaScriptExpression *expression = expressions; |
630 | while (expression) { |
631 | QQmlJavaScriptExpression *nextExpression = expression->m_nextExpression; |
632 | |
633 | expression->m_prevExpression = nullptr; |
634 | expression->m_nextExpression = nullptr; |
635 | |
636 | expression->setContext(nullptr); |
637 | |
638 | expression = nextExpression; |
639 | } |
640 | expressions = nullptr; |
641 | } |
642 | |
643 | void QQmlContextData::destroy() |
644 | { |
645 | Q_ASSERT(refCount == 0); |
646 | |
647 | // avoid recursion |
648 | ++refCount; |
649 | if (engine) |
650 | invalidate(); |
651 | linkedContext = nullptr; |
652 | |
653 | Q_ASSERT(refCount == 1); |
654 | clearContext(); |
655 | Q_ASSERT(refCount == 1); |
656 | |
657 | while (contextObjects) { |
658 | QQmlData *co = contextObjects; |
659 | contextObjects = contextObjects->nextContextObject; |
660 | |
661 | if (co->context == this) |
662 | co->context = nullptr; |
663 | co->outerContext = nullptr; |
664 | co->nextContextObject = nullptr; |
665 | co->prevContextObject = nullptr; |
666 | } |
667 | Q_ASSERT(refCount == 1); |
668 | |
669 | QQmlGuardedContextData *contextGuard = contextGuards; |
670 | while (contextGuard) { |
671 | QQmlGuardedContextData *next = contextGuard->m_next; |
672 | contextGuard->m_next = nullptr; |
673 | contextGuard->m_prev = nullptr; |
674 | contextGuard->m_contextData = nullptr; |
675 | contextGuard = next; |
676 | } |
677 | contextGuards = nullptr; |
678 | Q_ASSERT(refCount == 1); |
679 | |
680 | delete [] idValues; |
681 | idValues = nullptr; |
682 | |
683 | Q_ASSERT(refCount == 1); |
684 | if (publicContext) { |
685 | // the QQmlContext destructor will remove one ref again |
686 | ++refCount; |
687 | delete publicContext; |
688 | } |
689 | |
690 | Q_ASSERT(refCount == 1); |
691 | --refCount; |
692 | Q_ASSERT(refCount == 0); |
693 | |
694 | delete this; |
695 | } |
696 | |
697 | void QQmlContextData::setParent(QQmlContextData *p, bool stronglyReferencedByParent) |
698 | { |
699 | if (p == parent) |
700 | return; |
701 | if (p) { |
702 | Q_ASSERT(!parent); |
703 | parent = p; |
704 | this->stronglyReferencedByParent = stronglyReferencedByParent; |
705 | if (stronglyReferencedByParent) |
706 | ++refCount; // balanced in QQmlContextData::invalidate() |
707 | engine = p->engine; |
708 | nextChild = p->childContexts; |
709 | if (nextChild) nextChild->prevChild = &nextChild; |
710 | prevChild = &p->childContexts; |
711 | p->childContexts = this; |
712 | } |
713 | } |
714 | |
715 | void QQmlContextData::refreshExpressionsRecursive(QQmlJavaScriptExpression *expression) |
716 | { |
717 | QQmlJavaScriptExpression::DeleteWatcher w(expression); |
718 | |
719 | if (expression->m_nextExpression) |
720 | refreshExpressionsRecursive(expression: expression->m_nextExpression); |
721 | |
722 | if (!w.wasDeleted()) |
723 | expression->refresh(); |
724 | } |
725 | |
726 | QQmlContextData::~QQmlContextData() |
727 | { |
728 | } |
729 | |
730 | static inline bool expressions_to_run(QQmlContextData *ctxt, bool isGlobalRefresh) |
731 | { |
732 | return ctxt->expressions && (!isGlobalRefresh || ctxt->unresolvedNames); |
733 | } |
734 | |
735 | void QQmlContextData::refreshExpressionsRecursive(bool isGlobal) |
736 | { |
737 | // For efficiency, we try and minimize the number of guards we have to create |
738 | if (expressions_to_run(ctxt: this, isGlobalRefresh: isGlobal) && (nextChild || childContexts)) { |
739 | QQmlGuardedContextData guard(this); |
740 | |
741 | if (childContexts) |
742 | childContexts->refreshExpressionsRecursive(isGlobal); |
743 | |
744 | if (guard.isNull()) return; |
745 | |
746 | if (nextChild) |
747 | nextChild->refreshExpressionsRecursive(isGlobal); |
748 | |
749 | if (guard.isNull()) return; |
750 | |
751 | if (expressions_to_run(ctxt: this, isGlobalRefresh: isGlobal)) |
752 | refreshExpressionsRecursive(expression: expressions); |
753 | |
754 | } else if (expressions_to_run(ctxt: this, isGlobalRefresh: isGlobal)) { |
755 | |
756 | refreshExpressionsRecursive(expression: expressions); |
757 | |
758 | } else if (nextChild && childContexts) { |
759 | |
760 | QQmlGuardedContextData guard(this); |
761 | |
762 | childContexts->refreshExpressionsRecursive(isGlobal); |
763 | |
764 | if (!guard.isNull() && nextChild) |
765 | nextChild->refreshExpressionsRecursive(isGlobal); |
766 | |
767 | } else if (nextChild) { |
768 | |
769 | nextChild->refreshExpressionsRecursive(isGlobal); |
770 | |
771 | } else if (childContexts) { |
772 | |
773 | childContexts->refreshExpressionsRecursive(isGlobal); |
774 | |
775 | } |
776 | } |
777 | |
778 | // Refreshes all expressions that could possibly depend on this context. Refreshing flushes all |
779 | // context-tree dependent caches in the expressions, and should occur every time the context tree |
780 | // *structure* (not values) changes. |
781 | void QQmlContextData::refreshExpressions() |
782 | { |
783 | bool isGlobal = (parent == nullptr); |
784 | |
785 | // For efficiency, we try and minimize the number of guards we have to create |
786 | if (expressions_to_run(ctxt: this, isGlobalRefresh: isGlobal) && childContexts) { |
787 | QQmlGuardedContextData guard(this); |
788 | |
789 | childContexts->refreshExpressionsRecursive(isGlobal); |
790 | |
791 | if (!guard.isNull() && expressions_to_run(ctxt: this, isGlobalRefresh: isGlobal)) |
792 | refreshExpressionsRecursive(expression: expressions); |
793 | |
794 | } else if (expressions_to_run(ctxt: this, isGlobalRefresh: isGlobal)) { |
795 | |
796 | refreshExpressionsRecursive(expression: expressions); |
797 | |
798 | } else if (childContexts) { |
799 | |
800 | childContexts->refreshExpressionsRecursive(isGlobal); |
801 | |
802 | } |
803 | } |
804 | |
805 | void QQmlContextData::addObject(QQmlData *data) |
806 | { |
807 | if (data->outerContext) { |
808 | if (data->nextContextObject) |
809 | data->nextContextObject->prevContextObject = data->prevContextObject; |
810 | if (data->prevContextObject) |
811 | *data->prevContextObject = data->nextContextObject; |
812 | else if (data->outerContext->contextObjects == data) |
813 | data->outerContext->contextObjects = data->nextContextObject; |
814 | } |
815 | |
816 | data->outerContext = this; |
817 | |
818 | data->nextContextObject = contextObjects; |
819 | if (data->nextContextObject) |
820 | data->nextContextObject->prevContextObject = &data->nextContextObject; |
821 | data->prevContextObject = &contextObjects; |
822 | contextObjects = data; |
823 | } |
824 | |
825 | void QQmlContextData::setIdProperty(int idx, QObject *obj) |
826 | { |
827 | idValues[idx] = obj; |
828 | idValues[idx].context = this; |
829 | } |
830 | |
831 | QString QQmlContextData::findObjectId(const QObject *obj) const |
832 | { |
833 | const QV4::IdentifierHash &properties = propertyNames(); |
834 | if (propertyNameCache.isEmpty()) |
835 | return QString(); |
836 | |
837 | for (int ii = 0; ii < idValueCount; ii++) { |
838 | if (idValues[ii] == obj) |
839 | return properties.findId(value: ii); |
840 | } |
841 | |
842 | if (publicContext) { |
843 | QQmlContextPrivate *p = QQmlContextPrivate::get(context: publicContext); |
844 | for (int ii = 0; ii < p->propertyValues.count(); ++ii) |
845 | if (p->propertyValues.at(i: ii) == QVariant::fromValue(value: const_cast<QObject *>(obj))) |
846 | return properties.findId(value: ii); |
847 | } |
848 | |
849 | if (linkedContext) |
850 | return linkedContext->findObjectId(obj); |
851 | return QString(); |
852 | } |
853 | |
854 | QQmlContext *QQmlContextData::asQQmlContext() |
855 | { |
856 | if (!publicContext) |
857 | publicContext = new QQmlContext(this); |
858 | return publicContext; |
859 | } |
860 | |
861 | QQmlContextPrivate *QQmlContextData::asQQmlContextPrivate() |
862 | { |
863 | return QQmlContextPrivate::get(context: asQQmlContext()); |
864 | } |
865 | |
866 | void QQmlContextData::initFromTypeCompilationUnit(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, int subComponentIndex) |
867 | { |
868 | typeCompilationUnit = unit; |
869 | componentObjectIndex = subComponentIndex == -1 ? /*root object*/0 : subComponentIndex; |
870 | Q_ASSERT(!idValues); |
871 | idValueCount = typeCompilationUnit->objectAt(index: componentObjectIndex)->nNamedObjectsInComponent; |
872 | idValues = new ContextGuard[idValueCount]; |
873 | } |
874 | |
875 | const QV4::IdentifierHash &QQmlContextData::propertyNames() const |
876 | { |
877 | if (propertyNameCache.isEmpty()) { |
878 | if (typeCompilationUnit) |
879 | propertyNameCache = typeCompilationUnit->namedObjectsPerComponent(componentObjectIndex); |
880 | else |
881 | propertyNameCache = QV4::IdentifierHash(engine->handle()); |
882 | } |
883 | return propertyNameCache; |
884 | } |
885 | |
886 | QV4::IdentifierHash &QQmlContextData::detachedPropertyNames() |
887 | { |
888 | propertyNames(); |
889 | propertyNameCache.detach(); |
890 | return propertyNameCache; |
891 | } |
892 | |
893 | QUrl QQmlContextData::url() const |
894 | { |
895 | if (typeCompilationUnit) |
896 | return typeCompilationUnit->finalUrl(); |
897 | return baseUrl; |
898 | } |
899 | |
900 | QString QQmlContextData::urlString() const |
901 | { |
902 | if (typeCompilationUnit) |
903 | return typeCompilationUnit->finalUrlString(); |
904 | return baseUrlString; |
905 | } |
906 | |
907 | QT_END_NAMESPACE |
908 | |
909 | #include "moc_qqmlcontext.cpp" |
910 | |