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 | |
4 | #include "qqmlcontextdata_p.h" |
5 | |
6 | #include <QtQml/qqmlengine.h> |
7 | #include <QtQml/private/qqmlcomponentattached_p.h> |
8 | #include <QtQml/private/qqmljavascriptexpression_p.h> |
9 | #include <QtQml/private/qqmlguardedcontextdata_p.h> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | void QQmlContextData::installContext(QQmlData *ddata, QQmlContextData::QmlObjectKind kind) |
14 | { |
15 | Q_ASSERT(ddata); |
16 | if (kind == QQmlContextData::DocumentRoot) { |
17 | if (ddata->context) { |
18 | Q_ASSERT(ddata->context != this); |
19 | Q_ASSERT(ddata->outerContext); |
20 | Q_ASSERT(ddata->outerContext != this); |
21 | QQmlRefPointer<QQmlContextData> c = ddata->context; |
22 | while (QQmlRefPointer<QQmlContextData> linked = c->linkedContext()) |
23 | c = linked; |
24 | c->setLinkedContext(this); |
25 | } else { |
26 | ddata->context = this; |
27 | } |
28 | ddata->ownContext.reset(t: ddata->context); |
29 | } else if (!ddata->context) { |
30 | ddata->context = this; |
31 | } |
32 | |
33 | addOwnedObject(ownedObject: ddata); |
34 | } |
35 | |
36 | QUrl QQmlContextData::resolvedUrl(const QUrl &src) const |
37 | { |
38 | QUrl resolved; |
39 | if (src.isRelative() && !src.isEmpty()) { |
40 | const QUrl ownUrl = url(); |
41 | if (ownUrl.isValid()) { |
42 | resolved = ownUrl.resolved(relative: src); |
43 | } else { |
44 | for (QQmlRefPointer<QQmlContextData> ctxt = parent(); ctxt; ctxt = ctxt->parent()) { |
45 | const QUrl ctxtUrl = ctxt->url(); |
46 | if (ctxtUrl.isValid()) { |
47 | resolved = ctxtUrl.resolved(relative: src); |
48 | break; |
49 | } |
50 | } |
51 | |
52 | if (m_engine && resolved.isEmpty()) |
53 | resolved = m_engine->baseUrl().resolved(relative: src); |
54 | } |
55 | } else { |
56 | resolved = src; |
57 | } |
58 | |
59 | if (resolved.isEmpty()) //relative but no ctxt |
60 | return resolved; |
61 | |
62 | return m_engine ? m_engine->interceptUrl(url: resolved, type: QQmlAbstractUrlInterceptor::UrlString) |
63 | : resolved; |
64 | } |
65 | |
66 | void QQmlContextData::emitDestruction() |
67 | { |
68 | if (!m_hasEmittedDestruction) { |
69 | m_hasEmittedDestruction = true; |
70 | |
71 | // Emit the destruction signal - must be emitted before invalidate so that the |
72 | // context is still valid if bindings or resultant expression evaluation requires it |
73 | if (m_engine) { |
74 | while (m_componentAttacheds) { |
75 | QQmlComponentAttached *attached = m_componentAttacheds; |
76 | attached->removeFromList(); |
77 | emit attached->destruction(); |
78 | } |
79 | |
80 | for (QQmlRefPointer<QQmlContextData> child = m_childContexts; !child.isNull(); child = child->m_nextChild) |
81 | child->emitDestruction(); |
82 | } |
83 | } |
84 | } |
85 | |
86 | void QQmlContextData::invalidate() |
87 | { |
88 | emitDestruction(); |
89 | |
90 | while (m_childContexts) { |
91 | Q_ASSERT(m_childContexts != this); |
92 | m_childContexts->invalidate(); |
93 | } |
94 | |
95 | if (m_prevChild) { |
96 | *m_prevChild = m_nextChild; |
97 | if (m_nextChild) m_nextChild->m_prevChild = m_prevChild; |
98 | m_nextChild = nullptr; |
99 | m_prevChild = nullptr; |
100 | } |
101 | |
102 | m_importedScripts.clear(); |
103 | |
104 | m_engine = nullptr; |
105 | clearParent(); |
106 | } |
107 | |
108 | void QQmlContextData::clearContextRecursively() |
109 | { |
110 | clearContext(); |
111 | |
112 | for (auto ctxIt = m_childContexts; ctxIt; ctxIt = ctxIt->m_nextChild) |
113 | ctxIt->clearContextRecursively(); |
114 | } |
115 | |
116 | void QQmlContextData::clearContext() |
117 | { |
118 | emitDestruction(); |
119 | |
120 | QQmlJavaScriptExpression *expression = m_expressions; |
121 | while (expression) { |
122 | QQmlJavaScriptExpression *nextExpression = expression->m_nextExpression; |
123 | |
124 | expression->m_prevExpression = nullptr; |
125 | expression->m_nextExpression = nullptr; |
126 | |
127 | expression->setContext(nullptr); |
128 | |
129 | expression = nextExpression; |
130 | } |
131 | m_expressions = nullptr; |
132 | } |
133 | |
134 | QQmlContextData::~QQmlContextData() |
135 | { |
136 | Q_ASSERT(refCount() == 0); |
137 | |
138 | // avoid recursion |
139 | addref(); |
140 | if (m_engine) |
141 | invalidate(); |
142 | m_linkedContext.reset(); |
143 | |
144 | Q_ASSERT(refCount() == 1); |
145 | clearContext(); |
146 | Q_ASSERT(refCount() == 1); |
147 | |
148 | while (m_ownedObjects) { |
149 | QQmlData *co = m_ownedObjects; |
150 | m_ownedObjects = m_ownedObjects->nextContextObject; |
151 | |
152 | if (co->context == this) |
153 | co->context = nullptr; |
154 | co->outerContext = nullptr; |
155 | co->nextContextObject = nullptr; |
156 | co->prevContextObject = nullptr; |
157 | } |
158 | Q_ASSERT(refCount() == 1); |
159 | |
160 | QQmlGuardedContextData *contextGuard = m_contextGuards; |
161 | while (contextGuard) { |
162 | // TODO: Is this dead code? Why? |
163 | QQmlGuardedContextData *next = contextGuard->next(); |
164 | contextGuard->setContextData({}); |
165 | contextGuard = next; |
166 | } |
167 | m_contextGuards = nullptr; |
168 | Q_ASSERT(refCount() == 1); |
169 | |
170 | delete [] m_idValues; |
171 | m_idValues = nullptr; |
172 | |
173 | Q_ASSERT(refCount() == 1); |
174 | if (m_publicContext) |
175 | delete m_publicContext; |
176 | |
177 | Q_ASSERT(refCount() == 1); |
178 | } |
179 | |
180 | void QQmlContextData::refreshExpressionsRecursive(QQmlJavaScriptExpression *expression) |
181 | { |
182 | QQmlJavaScriptExpression::DeleteWatcher w(expression); |
183 | |
184 | if (expression->m_nextExpression) |
185 | refreshExpressionsRecursive(expression: expression->m_nextExpression); |
186 | |
187 | if (!w.wasDeleted()) |
188 | expression->refresh(); |
189 | } |
190 | |
191 | void QQmlContextData::refreshExpressionsRecursive(bool isGlobal) |
192 | { |
193 | // For efficiency, we try and minimize the number of guards we have to create |
194 | if (hasExpressionsToRun(isGlobalRefresh: isGlobal) && (m_nextChild || m_childContexts)) { |
195 | QQmlGuardedContextData guard(this); |
196 | |
197 | if (m_childContexts) |
198 | m_childContexts->refreshExpressionsRecursive(isGlobal); |
199 | |
200 | if (guard.isNull()) return; |
201 | |
202 | if (m_nextChild) |
203 | m_nextChild->refreshExpressionsRecursive(isGlobal); |
204 | |
205 | if (guard.isNull()) return; |
206 | |
207 | if (hasExpressionsToRun(isGlobalRefresh: isGlobal)) |
208 | refreshExpressionsRecursive(expression: m_expressions); |
209 | |
210 | } else if (hasExpressionsToRun(isGlobalRefresh: isGlobal)) { |
211 | refreshExpressionsRecursive(expression: m_expressions); |
212 | } else if (m_nextChild && m_childContexts) { |
213 | QQmlGuardedContextData guard(this); |
214 | m_childContexts->refreshExpressionsRecursive(isGlobal); |
215 | if (!guard.isNull() && m_nextChild) |
216 | m_nextChild->refreshExpressionsRecursive(isGlobal); |
217 | } else if (m_nextChild) { |
218 | m_nextChild->refreshExpressionsRecursive(isGlobal); |
219 | } else if (m_childContexts) { |
220 | m_childContexts->refreshExpressionsRecursive(isGlobal); |
221 | } |
222 | } |
223 | |
224 | // Refreshes all expressions that could possibly depend on this context. Refreshing flushes all |
225 | // context-tree dependent caches in the expressions, and should occur every time the context tree |
226 | // *structure* (not values) changes. |
227 | void QQmlContextData::refreshExpressions() |
228 | { |
229 | bool isGlobal = (m_parent == nullptr); |
230 | |
231 | // For efficiency, we try and minimize the number of guards we have to create |
232 | if (hasExpressionsToRun(isGlobalRefresh: isGlobal) && m_childContexts) { |
233 | QQmlGuardedContextData guard(this); |
234 | m_childContexts->refreshExpressionsRecursive(isGlobal); |
235 | if (!guard.isNull() && hasExpressionsToRun(isGlobalRefresh: isGlobal)) |
236 | refreshExpressionsRecursive(expression: m_expressions); |
237 | } else if (hasExpressionsToRun(isGlobalRefresh: isGlobal)) { |
238 | refreshExpressionsRecursive(expression: m_expressions); |
239 | } else if (m_childContexts) { |
240 | m_childContexts->refreshExpressionsRecursive(isGlobal); |
241 | } |
242 | } |
243 | |
244 | void QQmlContextData::addOwnedObject(QQmlData *data) |
245 | { |
246 | if (data->outerContext) { |
247 | if (data->nextContextObject) |
248 | data->nextContextObject->prevContextObject = data->prevContextObject; |
249 | if (data->prevContextObject) |
250 | *data->prevContextObject = data->nextContextObject; |
251 | else if (data->outerContext->m_ownedObjects == data) |
252 | data->outerContext->m_ownedObjects = data->nextContextObject; |
253 | } |
254 | |
255 | data->outerContext = this; |
256 | |
257 | data->nextContextObject = m_ownedObjects; |
258 | if (data->nextContextObject) |
259 | data->nextContextObject->prevContextObject = &data->nextContextObject; |
260 | data->prevContextObject = &m_ownedObjects; |
261 | m_ownedObjects = data; |
262 | } |
263 | |
264 | void QQmlContextData::setIdValue(int idx, QObject *obj) |
265 | { |
266 | m_idValues[idx] = obj; |
267 | m_idValues[idx].setContext(this); |
268 | } |
269 | |
270 | QString QQmlContextData::findObjectId(const QObject *obj) const |
271 | { |
272 | for (int ii = 0; ii < m_idValueCount; ii++) { |
273 | if (m_idValues[ii] == obj) |
274 | return propertyName(index: ii); |
275 | } |
276 | |
277 | const QVariant objVariant = QVariant::fromValue(value: obj); |
278 | if (m_publicContext) { |
279 | QQmlContextPrivate *p = QQmlContextPrivate::get(context: m_publicContext); |
280 | for (int ii = 0; ii < p->numPropertyValues(); ++ii) |
281 | if (p->propertyValue(index: ii) == objVariant) |
282 | return propertyName(index: ii); |
283 | } |
284 | |
285 | if (m_contextObject) { |
286 | // This is expensive, but nameForObject should really mirror contextProperty() |
287 | for (const QMetaObject *metaObject = m_contextObject->metaObject(); |
288 | metaObject; metaObject = metaObject->superClass()) { |
289 | for (int i = metaObject->propertyOffset(), end = metaObject->propertyCount(); |
290 | i != end; ++i) { |
291 | const QMetaProperty prop = metaObject->property(index: i); |
292 | if (prop.metaType().flags() & QMetaType::PointerToQObject |
293 | && prop.read(obj: m_contextObject) == objVariant) { |
294 | return QString::fromUtf8(utf8: prop.name()); |
295 | } |
296 | } |
297 | } |
298 | } |
299 | |
300 | return QString(); |
301 | } |
302 | |
303 | void QQmlContextData::initFromTypeCompilationUnit(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, int subComponentIndex) |
304 | { |
305 | m_typeCompilationUnit = unit; |
306 | m_componentObjectIndex = subComponentIndex == -1 ? /*root object*/0 : subComponentIndex; |
307 | Q_ASSERT(!m_idValues); |
308 | m_idValueCount = m_typeCompilationUnit->objectAt(index: m_componentObjectIndex) |
309 | ->nNamedObjectsInComponent; |
310 | m_idValues = new ContextGuard[m_idValueCount]; |
311 | } |
312 | |
313 | void QQmlContextData::addComponentAttached(QQmlComponentAttached *attached) |
314 | { |
315 | attached->insertIntoList(listHead: &m_componentAttacheds); |
316 | } |
317 | |
318 | void QQmlContextData::addExpression(QQmlJavaScriptExpression *expression) |
319 | { |
320 | expression->insertIntoList(listHead: &m_expressions); |
321 | } |
322 | |
323 | void QQmlContextData::initPropertyNames() const |
324 | { |
325 | if (m_typeCompilationUnit) |
326 | m_propertyNameCache = m_typeCompilationUnit->namedObjectsPerComponent(componentObjectIndex: m_componentObjectIndex); |
327 | else |
328 | m_propertyNameCache = QV4::IdentifierHash(m_engine->handle()); |
329 | Q_ASSERT(!m_propertyNameCache.isEmpty()); |
330 | } |
331 | |
332 | QUrl QQmlContextData::url() const |
333 | { |
334 | if (m_typeCompilationUnit) |
335 | return m_typeCompilationUnit->finalUrl(); |
336 | return m_baseUrl; |
337 | } |
338 | |
339 | QString QQmlContextData::urlString() const |
340 | { |
341 | if (m_typeCompilationUnit) |
342 | return m_typeCompilationUnit->finalUrlString(); |
343 | return m_baseUrlString; |
344 | } |
345 | |
346 | QT_END_NAMESPACE |
347 |