Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlcontextdata.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
6
7#include <QtQml/qqmlengine.h>
8#include <QtQml/private/qqmlcomponentattached_p.h>
9#include <QtQml/private/qqmljavascriptexpression_p.h>
10#include <QtQml/private/qqmlguardedcontextdata_p.h>
11
13
14/*!
15 \class QQmlContextData
16 \internal
17
18 \brief Runtime embodiment of a single QML component instance's context.
19
20 A QQmlContextData holds the state that backs a QML context: the parent/child
21 links, the id values, the imports, the compilation unit, the owned objects,
22 the imported scripts and so on.
23
24 Contexts usually form at component boundaries. That is, the root object of
25 a document, an inline component or the instantiation point of a Component.
26 When looking up unqalified names, the engine travels the context hierarchy
27 and considers both context properties and the context object for each
28 context. This is the reason why you can implicitly refer to the properties
29 of root objects (recursively), but not to the properties of intermediate
30 objects in the document tree. You can, however, manually mess with the
31 context hierarchy, and some QML types (e.g. models and views) do that.
32
33 The public \l QQmlContext is a thin shell on top of QQmlContextData. The
34 relationship is deliberately asymmetric:
35
36 \list
37 \li An \e internal context (\c m_isInternal == true) is created by the engine
38 while instantiating a component. The QQmlContextData is the primary object
39 and \e owns its publicContext, which is minted lazily by asQQmlContext().
40 Almost every context created during .qml instantiation is internal.
41 \li An \e external context (\c m_isInternal == false) is created when the user
42 constructs a QQmlContext explicitly; that QQmlContext owns the
43 QQmlContextData.
44 \endlist
45
46 QQmlContextData::get() bridges public to private; asQQmlContext()/
47 publicContext() bridge back. Lifetime is reference counted (\c m_refCount,
48 addref()/release()), with the cycle-avoiding twists described below.
49
50 \section1 The context tree: parent and childContexts
51
52 Contexts form a tree that mirrors the \e{document nesting} of QML, not the
53 QObject parent hierarchy and not the visual item tree:
54
55 \list
56 \li \c m_parent -- up the tree, to the next \e{component root}.
57 \li \c m_childContexts, \c m_nextChild, \c m_prevChild -- an intrusive child
58 list with a back-link to the slot that points at each node, so insert and
59 unlink are O(1) (see the constructor).
60 \endlist
61
62 Parent and child links are \e not reference counted: that would create cyclic
63 references. A parent keeps its children alive through ownership (see below);
64 children point back at the parent weakly. The tree propagates expression
65 refreshes downward (refreshExpressions() and friends walk childContexts())
66 and resolves relative URLs upward (url()/baseUrl() walk the parent chain).
67
68 \section1 Ownership: who keeps whom alive
69
70 Because parent->child links are raw, something else must pin contexts. There
71 are three ownership modes (\c enum \c Ownership):
72
73 \list
74 \li \c RefCounted (createRefCounted()/createBareContext()) - held by whoever
75 holds the QQmlRefPointer.
76 \li \c OwnedByParent (createChild()) - released when the parent drops it via
77 clearParent().
78 \li \c OwnedByPublicContext - released via clearPublicContext().
79 \endlist
80
81 \c m_ownedByParent and \c m_ownedByPublicContext are mutually exclusive bits.
82 Component root contexts created during instantiation are \c RefCounted; the
83 field that actually pins a root context in memory is the object side's
84 \c{QQmlData::ownContext}, which is a QQmlRefPointer (see below).
85
86 \section1 Binding to a type: the compilation unit
87
88 A context created for a compiled QML type remembers:
89
90 \list
91 \li \c m_typeCompilationUnit -- the CU this context belongs to.
92 \li \c m_componentObjectIndex -- the object index within that CU of the
93 component that created the context (0 for the document root, the IC root
94 index for an inline component).
95 \endlist
96
97 initFromTypeCompilationUnit() wires these up and sizes the id table from the
98 component root's \c nNamedObjectsInComponent. So a context is the runtime
99 embodiment of one component instance: it knows its CU, its root object index,
100 how many ids that component declares, and the import set (\c m_imports) used
101 for name resolution.
102
103 \section1 The object side: QQmlData::context, outerContext, ownContext
104
105 Three QQmlData fields tie a QObject into the context world:
106
107 \list
108 \li \c outerContext - the context of the enclosing component instance: where
109 the object lives, where its id (if any) is registered, and where name
110 lookup for its bindings starts. Siblings created by the same component
111 share it. Not refcounted.
112 \li \c ownContext - non-null \e only for component roots (the document root,
113 an inline-component root, or a base-type level in a composite chain). It
114 is the refcounted pointer that keeps the introduced context alive. A plain
115 child object has \c ownContext == nullptr.
116 \li \c context - the effective context for the object's own bindings:
117 \c{== outerContext} for a borrowed child, \c{== ownContext.data()} for a
118 root.
119 \endlist
120
121 installContext() establishes these (called from
122 QQmlObjectCreator::initializeDData). A context also keeps the inverse mapping:
123 a doubly-linked list of the QQmlData it owns (\c m_ownedObjects), severed in
124 clearOwnedObjects() on destruction.
125
126 Now, given this explanation we clearly have a problem: The same component
127 root object can live in and own multiple contexts. It can inherit from
128 another QML type after all, with more inner objects. It can even have
129 different IDs in different outer contexts. That's where the linked contexts
130 come into play.
131
132 \section1 Linked contexts: the composite (base-type) chain
133
134 A QML type may derive from another QML type (MyButton.qml : Button.qml :
135 C++ QQuickButton). Each composite (QML-defined) level is a separate
136 compilation unit and gets its \e own context, yet they all describe the same
137 single QObject. These per-level contexts are chained through
138 \c m_linkedContext (\c{this} owns the next link, so the chain is refcounted
139 derived->base):
140
141 \list
142 \li \c{ddata->outerContext}/\c context/\c ownContext point at the
143 most-derived type the enclosing document instantiated.
144 \li linkedContext() walks the base types. The deepest-base type is first
145 and the most-derived one last.
146 \endlist
147
148 Object creation proceeds deepest-base-first, so installContext() appends each
149 newly installed (more derived) root to the end of the linked chain. Because
150 one QObject spans the whole chain, deepClearContextObject() must sweep every
151 link, not just the head, when detaching the context object.
152
153 \section1 Ids: the id-value table and context guards
154
155 Each context owns an array of \c ContextGuard, one per id declared in its
156 component (\c m_idValues, \c m_idValueCount). The index<->name mapping is the
157 lazily filled \c m_propertyNameCache (propertyIndex()/propertyName()). A
158 ContextGuard is a QQmlGuard plus a QQmlNotifier: assigning or destroying an
159 id'd object fires the notifier so alias and id-referencing bindings
160 re-evaluate. The \c ObjectWasSet tag (wasSet()) distinguishes "slot exists but
161 empty" from "set to null". findObjectId() does the reverse name lookup.
162
163 \section1 The context object
164
165 \c m_contextObject is the scope object whose properties are in unqualified
166 scope for bindings evaluated in this context (for a root context, the root
167 instance). isValid() couples an internal context's validity to the liveness
168 of its context object.
169
170 \section1 The extra slot
171
172 A single union slot, discriminated by \c m_hasExtraObject, serves two
173 mutually exclusive purposes:
174
175 \list
176 \li \c m_incubator - while a context is built asynchronously, the
177 QQmlIncubatorPrivate driving construction.
178 \li \c m_extraObject - repurposed afterward for component-specific side data;
179 currently only QQmlDelegateModel (QQmlDelegateModelItem::dataForObject).
180 \endlist
181
182 \section1 Other per-context state
183
184 \list
185 \li \c m_expressions - intrusive list of QQmlJavaScriptExpressions evaluated
186 in this context; the basis of refreshExpressions().
187 \li \c m_importedScripts - the JS array of .import'ed scripts; downgraded
188 strong->weak on invalidation so closures keep working without pinning.
189 \li \c m_imports - the QQmlTypeNameCache for resolving type names here.
190 \li \c m_componentAttacheds - uses of the Component attached property.
191 \li \c m_contextGuards - external weak references to this context.
192 \li \c m_baseUrl/\c m_baseUrlString - explicit base-URL overrides.
193 \endlist
194*/
195
196void QQmlContextData::installContext(QQmlData *ddata, QQmlContextData::QmlObjectKind kind)
197{
198 Q_ASSERT(ddata);
199 if (kind == QQmlContextData::DocumentRoot) {
200 if (ddata->context) {
201 Q_ASSERT(ddata->context != this);
202 Q_ASSERT(ddata->outerContext);
203 Q_ASSERT(ddata->outerContext != this);
204 QQmlRefPointer<QQmlContextData> c = ddata->context;
205 while (QQmlRefPointer<QQmlContextData> linked = c->linkedContext())
206 c = linked;
207 c->setLinkedContext(this);
208 } else {
209 ddata->context = this;
210 }
211 ddata->ownContext.reset(ddata->context);
212 } else if (!ddata->context) {
213 ddata->context = this;
214 }
215
216 addOwnedObject(ddata);
217}
218
219QUrl QQmlContextData::resolvedUrl(const QUrl &src) const
220{
221 QUrl resolved;
222 if (src.isRelative() && !src.isEmpty()) {
223 const QUrl ownUrl = url();
224 if (ownUrl.isValid()) {
225 resolved = ownUrl.resolved(src);
226 } else {
227 for (QQmlRefPointer<QQmlContextData> ctxt = parent(); ctxt; ctxt = ctxt->parent()) {
228 const QUrl ctxtUrl = ctxt->url();
229 if (ctxtUrl.isValid()) {
230 resolved = ctxtUrl.resolved(src);
231 break;
232 }
233 }
234
235 if (m_engine && resolved.isEmpty())
236 resolved = m_engine->baseUrl().resolved(src);
237 }
238 } else {
239 resolved = src;
240 }
241
242 if (resolved.isEmpty()) //relative but no ctxt
243 return resolved;
244
245 return m_engine ? m_engine->interceptUrl(resolved, QQmlAbstractUrlInterceptor::UrlString)
246 : resolved;
247}
248
249void QQmlContextData::emitDestruction()
250{
251 if (!m_hasEmittedDestruction) {
252 m_hasEmittedDestruction = true;
253
254 // Emit the destruction signal - must be emitted before invalidate so that the
255 // context is still valid if bindings or resultant expression evaluation requires it
256 if (m_engine) {
257 while (m_componentAttacheds) {
258 QQmlComponentAttached *attached = m_componentAttacheds;
259 attached->removeFromList();
260 emit attached->destruction();
261 }
262
263 for (QQmlRefPointer<QQmlContextData> child = m_childContexts; !child.isNull(); child = child->m_nextChild)
264 child->emitDestruction();
265 }
266 }
267}
268
269void QQmlContextData::invalidate()
270{
271 emitDestruction();
272
273 clearChildrenAndSiblings();
274 clearImportedScripts();
275
276 m_engine = nullptr;
277 clearParent();
278}
279
280void QQmlContextData::clearContextRecursively()
281{
282 emitDestruction();
283 clearExpressions();
284
285 for (auto ctxIt = m_childContexts; ctxIt; ctxIt = ctxIt->m_nextChild)
286 ctxIt->clearContextRecursively();
287
288 m_engine = nullptr;
289}
290
291void QQmlContextData::clearChildrenAndSiblings()
292{
293 while (m_childContexts) {
294 Q_ASSERT(m_childContexts != this);
295 m_childContexts->invalidate();
296 }
297
298 if (m_prevChild) {
299 *m_prevChild = m_nextChild;
300 if (m_nextChild) m_nextChild->m_prevChild = m_prevChild;
301 m_nextChild = nullptr;
302 m_prevChild = nullptr;
303 }
304}
305
306void QQmlContextData::clearImportedScripts()
307{
308 if (!m_hasWeakImportedScripts) { // might be called multiple times
309 if (m_engine && !m_importedScripts.isNullOrUndefined()) {
310 QV4::Scope scope(m_engine->handle());
311 QV4::ScopedValue val(scope, m_importedScripts.value());
312 m_importedScripts.~PersistentValue();
313 new (&m_weakImportedScripts) QV4::WeakValue();
314 m_weakImportedScripts.set(m_engine->handle(), val);
315 m_hasWeakImportedScripts = true;
316 } else {
317 // clear even if the value is null/undefined, in case it was set to explicit null/undefined
318 m_importedScripts.clear();
319 }
320 }
321}
322
323void QQmlContextData::clearOwnedObjects()
324{
325 while (m_ownedObjects) {
326 QQmlData *co = m_ownedObjects;
327 m_ownedObjects = m_ownedObjects->nextContextObject;
328
329 if (co->context == this)
330 co->context = nullptr;
331 co->outerContext = nullptr;
332 co->nextContextObject = nullptr;
333 co->prevContextObject = nullptr;
334 }
335}
336
337void QQmlContextData::clearContextGuards()
338{
339 for (QQmlGuardedContextData *contextGuard = m_contextGuards; contextGuard;) {
340 // TODO: Is this dead code? Why?
341 QQmlGuardedContextData *next = contextGuard->next();
342 contextGuard->setContextData({});
343 contextGuard = next;
344 }
345 m_contextGuards = nullptr;
346}
347
348void QQmlContextData::clearIdValues()
349{
350 delete[] std::exchange(m_idValues, nullptr);
351 m_idValueCount = 0;
352}
353
354void QQmlContextData::clearExpressions()
355{
356 QQmlJavaScriptExpression *expression = m_expressions;
357 while (expression) {
358 QQmlJavaScriptExpression *nextExpression = expression->m_nextExpression;
359
360 expression->m_prevExpression = nullptr;
361 expression->m_nextExpression = nullptr;
362
363 expression->setContext(nullptr);
364
365 expression = nextExpression;
366 }
367 m_expressions = nullptr;
368}
369
370QQmlContextData::~QQmlContextData()
371{
372 Q_ASSERT(refCount() == 0);
373
374 // avoid recursion
375 addref();
376 if (!m_hasWeakImportedScripts) {
377 // avoid busy work in invalidate – we don't want to construct a weak value
378 // just to throw it away afterwards
379 m_importedScripts.clear();
380 }
381 invalidate();
382 if (m_hasWeakImportedScripts)
383 m_weakImportedScripts.~WeakValue();
384 else
385 m_importedScripts.~PersistentValue();
386 m_linkedContext.reset();
387
388 Q_ASSERT(refCount() == 1);
389 emitDestruction();
390 clearExpressions();
391 Q_ASSERT(refCount() == 1);
392
393 clearOwnedObjects();
394 Q_ASSERT(refCount() == 1);
395
396 clearContextGuards();
397 Q_ASSERT(refCount() == 1);
398
399 clearIdValues();
400
401 Q_ASSERT(refCount() == 1);
402 if (m_publicContext)
403 delete m_publicContext;
404
405 Q_ASSERT(refCount() == 1);
406}
407
408void QQmlContextData::refreshExpressionsRecursive(QQmlJavaScriptExpression *expression)
409{
410 QQmlJavaScriptExpression::DeleteWatcher w(expression);
411
412 if (expression->m_nextExpression)
413 refreshExpressionsRecursive(expression->m_nextExpression);
414
415 if (!w.wasDeleted())
416 expression->refresh();
417}
418
419void QQmlContextData::refreshExpressionsRecursive(bool isGlobal)
420{
421 // For efficiency, we try and minimize the number of guards we have to create
422 if (hasExpressionsToRun(isGlobal) && (m_nextChild || m_childContexts)) {
423 QQmlGuardedContextData guard(this);
424
425 if (m_childContexts)
426 m_childContexts->refreshExpressionsRecursive(isGlobal);
427
428 if (guard.isNull()) return;
429
430 if (m_nextChild)
431 m_nextChild->refreshExpressionsRecursive(isGlobal);
432
433 if (guard.isNull()) return;
434
435 if (hasExpressionsToRun(isGlobal))
436 refreshExpressionsRecursive(m_expressions);
437
438 } else if (hasExpressionsToRun(isGlobal)) {
439 refreshExpressionsRecursive(m_expressions);
440 } else if (m_nextChild && m_childContexts) {
441 QQmlGuardedContextData guard(this);
442 m_childContexts->refreshExpressionsRecursive(isGlobal);
443 if (!guard.isNull() && m_nextChild)
444 m_nextChild->refreshExpressionsRecursive(isGlobal);
445 } else if (m_nextChild) {
446 m_nextChild->refreshExpressionsRecursive(isGlobal);
447 } else if (m_childContexts) {
448 m_childContexts->refreshExpressionsRecursive(isGlobal);
449 }
450}
451
452// Refreshes all expressions that could possibly depend on this context. Refreshing flushes all
453// context-tree dependent caches in the expressions, and should occur every time the context tree
454// *structure* (not values) changes.
455void QQmlContextData::refreshExpressions()
456{
457 bool isGlobal = (m_parent == nullptr);
458
459 // For efficiency, we try and minimize the number of guards we have to create
460 if (hasExpressionsToRun(isGlobal) && m_childContexts) {
461 QQmlGuardedContextData guard(this);
462 m_childContexts->refreshExpressionsRecursive(isGlobal);
463 if (!guard.isNull() && hasExpressionsToRun(isGlobal))
464 refreshExpressionsRecursive(m_expressions);
465 } else if (hasExpressionsToRun(isGlobal)) {
466 refreshExpressionsRecursive(m_expressions);
467 } else if (m_childContexts) {
468 m_childContexts->refreshExpressionsRecursive(isGlobal);
469 }
470}
471
472void QQmlContextData::addOwnedObject(QQmlData *data)
473{
474 if (data->outerContext) {
475 if (data->nextContextObject)
476 data->nextContextObject->prevContextObject = data->prevContextObject;
477 if (data->prevContextObject)
478 *data->prevContextObject = data->nextContextObject;
479 else if (data->outerContext->m_ownedObjects == data)
480 data->outerContext->m_ownedObjects = data->nextContextObject;
481 }
482
483 data->outerContext = this;
484
485 data->nextContextObject = m_ownedObjects;
486 if (data->nextContextObject)
487 data->nextContextObject->prevContextObject = &data->nextContextObject;
488 data->prevContextObject = &m_ownedObjects;
489 m_ownedObjects = data;
490}
491
492void QQmlContextData::setIdValue(int idx, QObject *obj)
493{
494 m_idValues[idx] = obj;
495 m_idValues[idx].setContext(this);
496}
497
498QString QQmlContextData::findObjectId(const QObject *obj) const
499{
500 for (int ii = 0; ii < m_idValueCount; ii++) {
501 if (m_idValues[ii] == obj)
502 return propertyName(ii);
503 }
504
505 const QVariant objVariant = QVariant::fromValue(obj);
506 if (m_publicContext) {
507 QQmlContextPrivate *p = QQmlContextPrivate::get(m_publicContext);
508 for (int ii = 0; ii < p->numPropertyValues(); ++ii)
509 if (p->propertyValue(ii) == objVariant)
510 return propertyName(ii);
511 }
512
513 if (m_contextObject) {
514 // This is expensive, but nameForObject should really mirror contextProperty()
515 for (const QMetaObject *metaObject = m_contextObject->metaObject();
516 metaObject; metaObject = metaObject->superClass()) {
517 for (int i = metaObject->propertyOffset(), end = metaObject->propertyCount();
518 i != end; ++i) {
519 const QMetaProperty prop = metaObject->property(i);
520 if (prop.metaType().flags() & QMetaType::PointerToQObject
521 && prop.read(m_contextObject) == objVariant) {
522 return QString::fromUtf8(prop.name());
523 }
524 }
525 }
526 }
527
528 return QString();
529}
530
531void QQmlContextData::setTypeCompilationUnit(
532 const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit)
533{
534 m_propertyNameCache = QV4::IdentifierHash();
535 delete[] std::exchange(m_idValues, nullptr);
536 initFromTypeCompilationUnit(unit, m_componentObjectIndex);
537}
538
539void QQmlContextData::initFromTypeCompilationUnit(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, int subComponentIndex)
540{
541 m_typeCompilationUnit = unit;
542 m_componentObjectIndex = subComponentIndex == -1 ? /*root object*/0 : subComponentIndex;
543 Q_ASSERT(!m_idValues);
544 m_idValueCount = m_typeCompilationUnit->objectAt(m_componentObjectIndex)
545 ->nNamedObjectsInComponent;
546 if (m_idValueCount > 0)
547 m_idValues = new ContextGuard[m_idValueCount];
548}
549
550void QQmlContextData::addComponentAttached(QQmlComponentAttached *attached)
551{
552 attached->insertIntoList(&m_componentAttacheds);
553}
554
555void QQmlContextData::addExpression(QQmlJavaScriptExpression *expression)
556{
557 expression->insertIntoList(&m_expressions);
558}
559
560void QQmlContextData::initPropertyNames() const
561{
562 if (m_typeCompilationUnit) {
563 m_propertyNameCache = m_typeCompilationUnit->namedObjectsPerComponent(m_componentObjectIndex);
564 } else {
565 auto engine = m_engine;
566 if (!engine) {
567 // in some circumstances, we run into an invalidated context. In that case, we have no engine
568 // obviously, there's also no names to be found. Ideally, we'd have a special empty IdentifierHash
569 // for this which doesn't depend on an engine being available, but that currently doesn't exist.
570 // If we're evaluating, we should however still be able to find a parent context with an engine
571 for (auto ctxt = parent(); ctxt; ctxt = ctxt->parent()) {
572 if ((engine = ctxt->engine()))
573 break;
574 }
575 }
576 Q_ASSERT(engine);
577 m_propertyNameCache = QV4::IdentifierHash(engine->handle());
578 }
579 Q_ASSERT(m_propertyNameCache.isValid());
580}
581
582QUrl QQmlContextData::url() const
583{
584 if (m_typeCompilationUnit)
585 return m_typeCompilationUnit->finalUrl();
586 return m_baseUrl;
587}
588
589QString QQmlContextData::urlString() const
590{
591 if (m_typeCompilationUnit)
592 return m_typeCompilationUnit->finalUrlString();
593 return m_baseUrlString;
594}
595
596QT_END_NAMESPACE
Combined button and popup list for selecting options.