1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qv4qmlcontext_p.h" |
5 | |
6 | #include <private/qjsvalue_p.h> |
7 | #include <private/qqmlcontext_p.h> |
8 | #include <private/qqmlengine_p.h> |
9 | #include <private/qqmlglobal_p.h> |
10 | #include <private/qqmljavascriptexpression_p.h> |
11 | #include <private/qqmllistwrapper_p.h> |
12 | #include <private/qqmltypewrapper_p.h> |
13 | #include <private/qv4compileddata_p.h> |
14 | #include <private/qv4engine_p.h> |
15 | #include <private/qv4function_p.h> |
16 | #include <private/qv4identifiertable_p.h> |
17 | #include <private/qv4lookup_p.h> |
18 | #include <private/qv4mm_p.h> |
19 | #include <private/qv4module_p.h> |
20 | #include <private/qv4objectproto_p.h> |
21 | #include <private/qv4qobjectwrapper_p.h> |
22 | #include <private/qv4stackframe_p.h> |
23 | #include <private/qv4value_p.h> |
24 | |
25 | #include <QtCore/qloggingcategory.h> |
26 | |
27 | QT_BEGIN_NAMESPACE |
28 | |
29 | Q_LOGGING_CATEGORY(lcQmlContext, "qt.qml.context" ); |
30 | |
31 | using namespace QV4; |
32 | |
33 | DEFINE_OBJECT_VTABLE(QQmlContextWrapper); |
34 | DEFINE_MANAGED_VTABLE(QmlContext); |
35 | |
36 | void Heap::QQmlContextWrapper::init(QQmlRefPointer<QQmlContextData> context, QObject *scopeObject) |
37 | { |
38 | Object::init(); |
39 | this->context = context.take(); |
40 | this->scopeObject.init(o: scopeObject); |
41 | } |
42 | |
43 | void Heap::QQmlContextWrapper::destroy() |
44 | { |
45 | context->release(); |
46 | context = nullptr; |
47 | scopeObject.destroy(); |
48 | Object::destroy(); |
49 | } |
50 | |
51 | static OptionalReturnedValue searchContextProperties( |
52 | QV4::ExecutionEngine *v4, const QQmlRefPointer<QQmlContextData> &context, String *name, |
53 | bool *hasProperty, Value *base, QV4::Lookup *lookup, QV4::Lookup *originalLookup, |
54 | QQmlEnginePrivate *ep) |
55 | { |
56 | const int propertyIdx = context->propertyIndex(name); |
57 | |
58 | if (propertyIdx == -1) |
59 | return OptionalReturnedValue(); |
60 | |
61 | if (propertyIdx < context->numIdValues()) { |
62 | if (hasProperty) |
63 | *hasProperty = true; |
64 | |
65 | if (lookup) { |
66 | lookup->qmlContextIdObjectLookup.objectId = propertyIdx; |
67 | lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupIdObject; |
68 | return OptionalReturnedValue(lookup->qmlContextPropertyGetter(lookup, v4, base)); |
69 | } else if (originalLookup) { |
70 | originalLookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupInParentContextHierarchy; |
71 | } |
72 | |
73 | if (ep->propertyCapture) |
74 | ep->propertyCapture->captureProperty(context->idValueBindings(index: propertyIdx)); |
75 | return OptionalReturnedValue(QV4::QObjectWrapper::wrap(engine: v4, object: context->idValue(index: propertyIdx))); |
76 | } |
77 | |
78 | QQmlContextPrivate *cp = context->asQQmlContextPrivate(); |
79 | |
80 | if (ep->propertyCapture) |
81 | ep->propertyCapture->captureProperty(context->asQQmlContext(), -1, propertyIdx + cp->notifyIndex()); |
82 | |
83 | const QVariant &value = cp->propertyValue(index: propertyIdx); |
84 | if (hasProperty) |
85 | *hasProperty = true; |
86 | if (value.userType() == qMetaTypeId<QList<QObject*> >()) { |
87 | QQmlListProperty<QObject> prop(context->asQQmlContext(), (void*) qintptr(propertyIdx), |
88 | QQmlContextPrivate::context_count, |
89 | QQmlContextPrivate::context_at); |
90 | return OptionalReturnedValue(QmlListWrapper::create(engine: v4, prop, propType: QMetaType::fromType<QQmlListProperty<QObject> >())); |
91 | } |
92 | return OptionalReturnedValue(v4->fromVariant(cp->propertyValue(index: propertyIdx))); |
93 | } |
94 | |
95 | template<typename Lookup> |
96 | bool performLookup(ScopedValue *result, bool *hasProperty, const Lookup &lookup) { |
97 | bool hasProp = false; |
98 | *result = lookup(&hasProp); |
99 | if (hasProp) { |
100 | if (hasProperty) |
101 | *hasProperty = hasProp; |
102 | return true; |
103 | } |
104 | return false; |
105 | } |
106 | |
107 | static QV4::QObjectWrapper::Flags getQmlPropertyFlags(const Lookup *l) |
108 | { |
109 | return (l && l->forCall) |
110 | ? QV4::QObjectWrapper::CheckRevision |
111 | : (QV4::QObjectWrapper::CheckRevision | QV4::QObjectWrapper::AttachMethods); |
112 | } |
113 | |
114 | ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *resource, PropertyKey id, const Value *receiver, bool *hasProperty, Value *base, Lookup *lookup) |
115 | { |
116 | if (!id.isString()) |
117 | return Object::virtualGet(m: resource, id, receiver, hasProperty); |
118 | |
119 | QV4::ExecutionEngine *v4 = resource->engine(); |
120 | QV4::Scope scope(v4); |
121 | |
122 | if (v4->callingQmlContext().data() != resource->d()->context) { |
123 | if (resource->d()->module) { |
124 | Scoped<Module> module(scope, resource->d()->module); |
125 | bool hasProp = false; |
126 | ScopedValue value(scope, module->get(id, receiver, hasProperty: &hasProp)); |
127 | if (hasProp) { |
128 | if (hasProperty) |
129 | *hasProperty = hasProp; |
130 | return value->asReturnedValue(); |
131 | } |
132 | } |
133 | |
134 | return Object::virtualGet(m: resource, id, receiver, hasProperty); |
135 | } |
136 | |
137 | ScopedValue result(scope); |
138 | |
139 | // It's possible we could delay the calculation of the "actual" context (in the case |
140 | // of sub contexts) until it is definitely needed. |
141 | QQmlRefPointer<QQmlContextData> context = resource->getContext(); |
142 | QQmlRefPointer<QQmlContextData> expressionContext = context; |
143 | |
144 | if (!context) { |
145 | if (hasProperty) |
146 | *hasProperty = true; |
147 | return result->asReturnedValue(); |
148 | } |
149 | |
150 | // Search type (attached property/enum/imported scripts) names |
151 | // while (context) { |
152 | // Search context properties |
153 | // Search scope object |
154 | // Search context object |
155 | // context = context->parent |
156 | // } |
157 | |
158 | QObject *scopeObject = resource->getScopeObject(); |
159 | |
160 | ScopedString name(scope, id.asStringOrSymbol()); |
161 | |
162 | const auto globalLookup = [v4, &name](bool *hasProp) { |
163 | return v4->globalObject->get(name, hasProperty: hasProp); |
164 | }; |
165 | |
166 | const auto jsLookup = [resource, &id, receiver](bool *hasProp) { |
167 | return Object::virtualGet(m: resource, id, receiver, hasProperty: hasProp); |
168 | }; |
169 | |
170 | const bool isJSContext = context->isJSContext(); |
171 | |
172 | // Do the generic JS lookup early in case of a JavaScript context. |
173 | if (isJSContext && performLookup(result: &result, hasProperty, lookup: jsLookup)) |
174 | return result->asReturnedValue(); |
175 | |
176 | // If the scope object is a QAbstractDynamicMetaObject, then QMetaObject::indexOfProperty |
177 | // will call createProperty() on the QADMO and implicitly create the property. While that |
178 | // is questionable behavior, there are two use-cases that we support in the light of this: |
179 | // |
180 | // (1) The implicit creation of properties is necessary because it will also result in |
181 | // a recorded capture, which will allow a re-evaluation of bindings when the value |
182 | // is populated later. See QTBUG-35233 and the test-case in tst_qqmlpropertymap. |
183 | // |
184 | // (1) Looking up "console" in order to place a console.log() call for example must |
185 | // find the console instead of creating a new property. Therefore we prioritize the |
186 | // lookup in the global object here. |
187 | // |
188 | // Note: The scope object is only a QADMO for example when somebody registers a QQmlPropertyMap |
189 | // sub-class as QML type and then instantiates it in .qml. |
190 | const QMetaObjectPrivate *metaObjectPrivate = scopeObject |
191 | ? reinterpret_cast<const QMetaObjectPrivate *>(scopeObject->metaObject()->d.data) |
192 | : nullptr; |
193 | if (metaObjectPrivate && metaObjectPrivate->flags & DynamicMetaObject) { |
194 | // all bets are off, so don't try to optimize any lookups |
195 | lookup = nullptr; |
196 | if (performLookup(result: &result, hasProperty, lookup: globalLookup)) |
197 | return result->asReturnedValue(); |
198 | } |
199 | |
200 | if (context->imports() && (name->startsWithUpper() || context->valueTypesAreAddressable())) { |
201 | // Search for attached properties, enums and imported scripts |
202 | QQmlTypeNameCache::Result r = context->imports()->query<QQmlImport::AllowRecursion>(key: name); |
203 | |
204 | if (r.isValid()) { |
205 | if (hasProperty) |
206 | *hasProperty = true; |
207 | if (r.scriptIndex != -1) { |
208 | if (lookup) { |
209 | lookup->qmlContextScriptLookup.scriptIndex = r.scriptIndex; |
210 | lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupScript; |
211 | return lookup->qmlContextPropertyGetter(lookup, v4, base); |
212 | } |
213 | QV4::ScopedObject scripts(scope, context->importedScripts().valueRef()); |
214 | if (scripts) |
215 | return scripts->get(idx: r.scriptIndex); |
216 | return QV4::Encode::null(); |
217 | } else if (r.type.isValid()) { |
218 | if (lookup) { |
219 | bool isValueSingleton = false; |
220 | if (r.type.isSingleton()) { |
221 | QQmlEnginePrivate *e = QQmlEnginePrivate::get(e: v4->qmlEngine()); |
222 | if (r.type.isQObjectSingleton() || r.type.isCompositeSingleton()) { |
223 | e->singletonInstance<QObject*>(type: r.type); |
224 | lookup->qmlContextSingletonLookup.singletonObject = |
225 | Value::fromReturnedValue( |
226 | val: QQmlTypeWrapper::create(v4, nullptr, r.type) |
227 | ).heapObject(); |
228 | } else { |
229 | QJSValue singleton = e->singletonInstance<QJSValue>(type: r.type); |
230 | |
231 | // QSrting values should already have been put on the engine heap at this point |
232 | // to manage their memory. We later assume this has already happened. |
233 | Q_ASSERT(!QJSValuePrivate::asQString(&singleton)); |
234 | |
235 | if (QV4::Value *val = QJSValuePrivate::takeManagedValue(jsval: &singleton)) { |
236 | lookup->qmlContextSingletonLookup.singletonObject = val->heapObject(); |
237 | } else { |
238 | lookup->qmlContextSingletonLookup.singletonValue = QJSValuePrivate::asReturnedValue(jsval: &singleton); |
239 | isValueSingleton = true; |
240 | } |
241 | } |
242 | lookup->qmlContextPropertyGetter = isValueSingleton ? QQmlContextWrapper::lookupValueSingleton |
243 | : QQmlContextWrapper::lookupSingleton; |
244 | return lookup->qmlContextPropertyGetter(lookup, v4, base); |
245 | } |
246 | } |
247 | result = QQmlTypeWrapper::create(v4, scopeObject, r.type); |
248 | } else if (r.importNamespace) { |
249 | result = QQmlTypeWrapper::create(v4, scopeObject, context->imports(), r.importNamespace); |
250 | } |
251 | if (lookup) { |
252 | lookup->qmlTypeLookup.qmlTypeWrapper = result->heapObject(); |
253 | lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupType; |
254 | } |
255 | return result->asReturnedValue(); |
256 | } |
257 | |
258 | // Fall through |
259 | } |
260 | |
261 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: v4->qmlEngine()); |
262 | Lookup * const originalLookup = lookup; |
263 | |
264 | decltype(lookup->qmlContextPropertyGetter) contextGetterFunction = QQmlContextWrapper::lookupContextObjectProperty; |
265 | |
266 | // minor optimization so we don't potentially try two property lookups on the same object |
267 | if (scopeObject == context->contextObject()) { |
268 | scopeObject = nullptr; |
269 | contextGetterFunction = QQmlContextWrapper::lookupScopeObjectProperty; |
270 | } |
271 | |
272 | while (context) { |
273 | if (auto property = searchContextProperties(v4, context, name, hasProperty, base, lookup, originalLookup, ep)) |
274 | return *property; |
275 | |
276 | // Search scope object |
277 | if (scopeObject) { |
278 | bool hasProp = false; |
279 | |
280 | const QQmlPropertyData *propertyData = nullptr; |
281 | |
282 | QV4::ScopedObject wrapper(scope, QV4::QObjectWrapper::wrap(engine: v4, object: scopeObject)); |
283 | QV4::ScopedValue result(scope, QV4::QObjectWrapper::getQmlProperty( |
284 | engine: v4, qmlContext: context, wrapper: wrapper->d(), object: scopeObject, name, |
285 | flags: getQmlPropertyFlags(l: lookup), hasProperty: &hasProp, property: &propertyData)); |
286 | if (hasProp) { |
287 | if (hasProperty) |
288 | *hasProperty = true; |
289 | if (base) |
290 | *base = wrapper; |
291 | |
292 | if (lookup && propertyData) { |
293 | QQmlData *ddata = QQmlData::get(object: scopeObject, create: false); |
294 | if (ddata && ddata->propertyCache) { |
295 | ScopedValue val( |
296 | scope, |
297 | base ? *base : Value::fromReturnedValue( |
298 | val: QV4::QObjectWrapper::wrap(engine: v4, object: scopeObject))); |
299 | if (QObjectMethod *method = result->as<QObjectMethod>()) { |
300 | QV4::setupQObjectMethodLookup( |
301 | lookup, ddata, propertyData, self: val->objectValue(), |
302 | method: method->d()); |
303 | lookup->qmlContextPropertyGetter |
304 | = QQmlContextWrapper::lookupScopeObjectMethod; |
305 | } else { |
306 | QV4::setupQObjectLookup( |
307 | lookup, ddata, propertyData, self: val->objectValue()); |
308 | lookup->qmlContextPropertyGetter |
309 | = QQmlContextWrapper::lookupScopeObjectProperty; |
310 | } |
311 | } |
312 | } |
313 | |
314 | return result->asReturnedValue(); |
315 | } |
316 | } |
317 | scopeObject = nullptr; |
318 | |
319 | |
320 | // Search context object |
321 | if (QObject *contextObject = context->contextObject()) { |
322 | bool hasProp = false; |
323 | const QQmlPropertyData *propertyData = nullptr; |
324 | QV4::ScopedObject wrapper(scope, QV4::QObjectWrapper::wrap(engine: v4, object: contextObject)); |
325 | result = QV4::QObjectWrapper::getQmlProperty( |
326 | engine: v4, qmlContext: context, wrapper: wrapper->d(), object: contextObject, name, flags: getQmlPropertyFlags(l: lookup), |
327 | hasProperty: &hasProp, property: &propertyData); |
328 | if (hasProp) { |
329 | if (hasProperty) |
330 | *hasProperty = true; |
331 | if (base) |
332 | *base = wrapper; |
333 | |
334 | if (propertyData) { |
335 | if (lookup) { |
336 | QQmlData *ddata = QQmlData::get(object: contextObject, create: false); |
337 | if (ddata && ddata->propertyCache |
338 | && lookup->qmlContextPropertyGetter != contextGetterFunction) { |
339 | ScopedValue val( |
340 | scope, |
341 | base ? *base : Value::fromReturnedValue( |
342 | val: QV4::QObjectWrapper::wrap(engine: v4, object: contextObject))); |
343 | if (QObjectMethod *method = result->as<QObjectMethod>()) { |
344 | setupQObjectMethodLookup( |
345 | lookup, ddata, propertyData, self: val->objectValue(), |
346 | method: method->d()); |
347 | if (contextGetterFunction == lookupScopeObjectProperty) |
348 | lookup->qmlContextPropertyGetter = lookupScopeObjectMethod; |
349 | else |
350 | lookup->qmlContextPropertyGetter = lookupContextObjectMethod; |
351 | } else { |
352 | setupQObjectLookup( |
353 | lookup, ddata, propertyData, self: val->objectValue()); |
354 | lookup->qmlContextPropertyGetter = contextGetterFunction; |
355 | } |
356 | } |
357 | } else if (originalLookup) { |
358 | originalLookup->qmlContextPropertyGetter = lookupInParentContextHierarchy; |
359 | } |
360 | } |
361 | |
362 | return result->asReturnedValue(); |
363 | } |
364 | } |
365 | |
366 | context = context->parent(); |
367 | |
368 | // As the hierarchy of contexts is not stable, we can't do accelerated lookups beyond |
369 | // the immediate QML context (of the .qml file). |
370 | lookup = nullptr; |
371 | } |
372 | |
373 | // Do the generic JS lookup late in case of a non-JavaScript context. |
374 | // The scope, context, types etc should be able to override it. |
375 | if (!isJSContext && performLookup(result: &result, hasProperty, lookup: jsLookup)) |
376 | return result->asReturnedValue(); |
377 | |
378 | // Do a lookup in the global object here to avoid expressionContext->unresolvedNames becoming |
379 | // true if we access properties of the global object. |
380 | if (originalLookup) { |
381 | // Try a lookup in the global object. It's theoretically possible to first find a property |
382 | // in the global object and then later a context property with the same name is added, but that |
383 | // never really worked as we used to detect access to global properties at type compile time anyway. |
384 | lookup = originalLookup; |
385 | result = lookup->resolveGlobalGetter(engine: v4); |
386 | if (lookup->globalGetter != Lookup::globalGetterGeneric) { |
387 | if (hasProperty) |
388 | *hasProperty = true; |
389 | lookup->qmlContextGlobalLookup.getterTrampoline = lookup->globalGetter; |
390 | lookup->qmlContextPropertyGetter = QQmlContextWrapper::lookupInGlobalObject; |
391 | return result->asReturnedValue(); |
392 | } |
393 | lookup->qmlContextPropertyGetter = QQmlContextWrapper::resolveQmlContextPropertyLookupGetter; |
394 | } else { |
395 | if (performLookup(result: &result, hasProperty, lookup: globalLookup)) |
396 | return result->asReturnedValue(); |
397 | } |
398 | |
399 | expressionContext->setUnresolvedNames(true); |
400 | |
401 | return Encode::undefined(); |
402 | } |
403 | |
404 | ReturnedValue QQmlContextWrapper::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) |
405 | { |
406 | Q_ASSERT(m->as<QQmlContextWrapper>()); |
407 | const QQmlContextWrapper *This = static_cast<const QQmlContextWrapper *>(m); |
408 | return getPropertyAndBase(resource: This, id, receiver, hasProperty, /*base*/nullptr); |
409 | } |
410 | |
411 | bool QQmlContextWrapper::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) |
412 | { |
413 | Q_ASSERT(m->as<QQmlContextWrapper>()); |
414 | |
415 | if (id.isSymbol() || id.isArrayIndex()) |
416 | return Object::virtualPut(m, id, value, receiver); |
417 | |
418 | QQmlContextWrapper *resource = static_cast<QQmlContextWrapper *>(m); |
419 | ExecutionEngine *v4 = resource->engine(); |
420 | QV4::Scope scope(v4); |
421 | if (scope.hasException()) |
422 | return false; |
423 | QV4::Scoped<QQmlContextWrapper> wrapper(scope, resource); |
424 | |
425 | auto member = wrapper->internalClass()->findValueOrSetter(id); |
426 | if (member.index < UINT_MAX) |
427 | return wrapper->putValue(memberIndex: member.index, attrs: member.attrs, value); |
428 | |
429 | // It's possible we could delay the calculation of the "actual" context (in the case |
430 | // of sub contexts) until it is definitely needed. |
431 | QQmlRefPointer<QQmlContextData> context = wrapper->getContext(); |
432 | QQmlRefPointer<QQmlContextData> expressionContext = context; |
433 | |
434 | if (!context) |
435 | return false; |
436 | |
437 | // See QV8ContextWrapper::Getter for resolution order |
438 | |
439 | QObject *scopeObject = wrapper->getScopeObject(); |
440 | ScopedString name(scope, id.asStringOrSymbol()); |
441 | |
442 | while (context) { |
443 | // Search context properties |
444 | if (const int propertyIndex = context->propertyIndex(name); propertyIndex != -1) { |
445 | if (propertyIndex < context->numIdValues()) { |
446 | v4->throwError(message: QLatin1String("left-hand side of assignment operator is not an lvalue" )); |
447 | return false; |
448 | } |
449 | return false; |
450 | } |
451 | |
452 | // Search scope object |
453 | if (scopeObject && |
454 | QV4::QObjectWrapper::setQmlProperty(engine: v4, qmlContext: context, object: scopeObject, name, flags: QV4::QObjectWrapper::CheckRevision, value)) |
455 | return true; |
456 | scopeObject = nullptr; |
457 | |
458 | // Search context object |
459 | if (context->contextObject() && |
460 | QV4::QObjectWrapper::setQmlProperty(engine: v4, qmlContext: context, object: context->contextObject(), name, |
461 | flags: QV4::QObjectWrapper::CheckRevision, value)) |
462 | return true; |
463 | |
464 | context = context->parent(); |
465 | } |
466 | |
467 | expressionContext->setUnresolvedNames(true); |
468 | |
469 | QString error = QLatin1String("Invalid write to global property \"" ) + name->toQString() + |
470 | QLatin1Char('"'); |
471 | v4->throwError(message: error); |
472 | return false; |
473 | } |
474 | |
475 | ReturnedValue QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(Lookup *l, ExecutionEngine *engine, Value *base) |
476 | { |
477 | Scope scope(engine); |
478 | auto *func = engine->currentStackFrame->v4Function; |
479 | PropertyKey name =engine->identifierTable->asPropertyKey( |
480 | str: func->compilationUnit->runtimeStrings[l->nameIndex]); |
481 | |
482 | // Special hack for bounded signal expressions, where the parameters of signals are injected |
483 | // into the handler expression through the locals of the call context. So for onClicked: { ... } |
484 | // the parameters of the clicked signal are injected and we must allow for them to be found here |
485 | // before any other property from the QML context. |
486 | for (Heap::ExecutionContext *ctx = engine->currentContext()->d(); ctx; ctx = ctx->outer) { |
487 | if (ctx->type == Heap::ExecutionContext::Type_CallContext) { |
488 | const uint index = ctx->internalClass->indexOfValueOrGetter(id: name); |
489 | if (index < std::numeric_limits<uint>::max()) { |
490 | if (!func->detectedInjectedParameters) { |
491 | const auto location = func->sourceLocation(); |
492 | qCWarning(lcQmlContext).nospace().noquote() |
493 | << location.sourceFile << ":" << location.line << ":" << location.column |
494 | << " Parameter \"" << name.toQString() << "\" is not declared." |
495 | << " Injection of parameters into signal handlers is deprecated." |
496 | << " Use JavaScript functions with formal parameters instead." ; |
497 | |
498 | // Don't warn over and over for the same function |
499 | func->detectedInjectedParameters = true; |
500 | } |
501 | |
502 | return static_cast<Heap::CallContext *>(ctx)->locals[index].asReturnedValue(); |
503 | } |
504 | } |
505 | |
506 | // Skip only block and call contexts. |
507 | // Other contexts need a regular QML property lookup. See below. |
508 | if (ctx->type != Heap::ExecutionContext::Type_BlockContext && ctx->type != Heap::ExecutionContext::Type_CallContext) |
509 | break; |
510 | } |
511 | |
512 | bool hasProperty = false; |
513 | ScopedValue result(scope); |
514 | |
515 | Scoped<QmlContext> callingQmlContext(scope, engine->qmlContext()); |
516 | if (callingQmlContext) { |
517 | Scoped<QQmlContextWrapper> qmlContextWrapper(scope, callingQmlContext->d()->qml()); |
518 | result = QQmlContextWrapper::getPropertyAndBase( |
519 | resource: qmlContextWrapper, id: name, /*receiver*/nullptr, hasProperty: &hasProperty, base, lookup: l); |
520 | } else { |
521 | // Code path typical to worker scripts, compiled with lookups but no qml context. |
522 | result = l->resolveGlobalGetter(engine); |
523 | if (l->globalGetter != Lookup::globalGetterGeneric) { |
524 | hasProperty = true; |
525 | l->qmlContextGlobalLookup.getterTrampoline = l->globalGetter; |
526 | l->qmlContextPropertyGetter = QQmlContextWrapper::lookupInGlobalObject; |
527 | } |
528 | } |
529 | if (!hasProperty) |
530 | return engine->throwReferenceError(name: name.toQString()); |
531 | return result->asReturnedValue(); |
532 | } |
533 | |
534 | ReturnedValue QQmlContextWrapper::lookupScript(Lookup *l, ExecutionEngine *engine, Value *base) |
535 | { |
536 | Q_UNUSED(base); |
537 | Scope scope(engine); |
538 | Scoped<QmlContext> qmlContext(scope, engine->qmlContext()); |
539 | if (!qmlContext) |
540 | return QV4::Encode::null(); |
541 | |
542 | QQmlRefPointer<QQmlContextData> context = qmlContext->qmlContext(); |
543 | if (!context) |
544 | return QV4::Encode::null(); |
545 | |
546 | QV4::ScopedObject scripts(scope, context->importedScripts().valueRef()); |
547 | if (!scripts) |
548 | return QV4::Encode::null(); |
549 | return scripts->get(idx: l->qmlContextScriptLookup.scriptIndex); |
550 | } |
551 | |
552 | ReturnedValue QQmlContextWrapper::lookupSingleton(Lookup *l, ExecutionEngine *engine, Value *base) |
553 | { |
554 | Q_UNUSED(engine); |
555 | Q_UNUSED(base); |
556 | |
557 | return l->qmlContextSingletonLookup.singletonObject->asReturnedValue(); |
558 | } |
559 | |
560 | ReturnedValue QQmlContextWrapper::lookupValueSingleton(Lookup *l, ExecutionEngine *engine, Value *base) |
561 | { |
562 | Q_UNUSED(engine); |
563 | Q_UNUSED(base); |
564 | |
565 | Q_ASSERT(l->qmlContextSingletonLookup.singletonObject == nullptr); |
566 | return l->qmlContextSingletonLookup.singletonValue; |
567 | } |
568 | |
569 | ReturnedValue QQmlContextWrapper::lookupIdObject(Lookup *l, ExecutionEngine *engine, Value *base) |
570 | { |
571 | Q_UNUSED(base); |
572 | Scope scope(engine); |
573 | Scoped<QmlContext> qmlContext(scope, engine->qmlContext()); |
574 | if (!qmlContext) |
575 | return QV4::Encode::null(); |
576 | |
577 | QQmlRefPointer<QQmlContextData> context = qmlContext->qmlContext(); |
578 | if (!context) |
579 | return QV4::Encode::null(); |
580 | |
581 | QQmlEnginePrivate *qmlEngine = QQmlEnginePrivate::get(e: engine->qmlEngine()); |
582 | const int objectId = l->qmlContextIdObjectLookup.objectId; |
583 | |
584 | if (qmlEngine->propertyCapture) |
585 | qmlEngine->propertyCapture->captureProperty(context->idValueBindings(index: objectId)); |
586 | |
587 | return QV4::QObjectWrapper::wrap(engine, object: context->idValue(index: objectId)); |
588 | } |
589 | |
590 | ReturnedValue QQmlContextWrapper::lookupIdObjectInParentContext( |
591 | Lookup *l, ExecutionEngine *engine, Value *base) |
592 | { |
593 | return QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(l, engine, base); |
594 | } |
595 | |
596 | static ReturnedValue revertObjectPropertyLookup(Lookup *l, ExecutionEngine *engine, Value *base) |
597 | { |
598 | l->qobjectLookup.propertyCache->release(); |
599 | l->qobjectLookup.propertyCache = nullptr; |
600 | l->qmlContextPropertyGetter = QQmlContextWrapper::resolveQmlContextPropertyLookupGetter; |
601 | return QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(l, engine, base); |
602 | } |
603 | |
604 | static ReturnedValue revertObjectMethodLookup(Lookup *l, ExecutionEngine *engine, Value *base) |
605 | { |
606 | l->qobjectMethodLookup.propertyCache->release(); |
607 | l->qobjectMethodLookup.propertyCache = nullptr; |
608 | l->qmlContextPropertyGetter = QQmlContextWrapper::resolveQmlContextPropertyLookupGetter; |
609 | return QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(l, engine, base); |
610 | } |
611 | |
612 | template<typename Call> |
613 | ReturnedValue callWithScopeObject(ExecutionEngine *engine, Value *base, Call c) |
614 | { |
615 | Scope scope(engine); |
616 | Scoped<QmlContext> qmlContext(scope, engine->qmlContext()); |
617 | if (!qmlContext) |
618 | return QV4::Encode::undefined(); |
619 | |
620 | QObject *scopeObject = qmlContext->qmlScope(); |
621 | if (!scopeObject) |
622 | return QV4::Encode::undefined(); |
623 | |
624 | if (QQmlData::wasDeleted(object: scopeObject)) |
625 | return QV4::Encode::undefined(); |
626 | |
627 | ScopedValue obj(scope, QV4::QObjectWrapper::wrap(engine, object: scopeObject)); |
628 | |
629 | if (base) |
630 | *base = obj; |
631 | |
632 | return c(obj); |
633 | } |
634 | |
635 | ReturnedValue QQmlContextWrapper::lookupScopeObjectProperty(Lookup *l, ExecutionEngine *engine, Value *base) |
636 | { |
637 | return callWithScopeObject(engine, base, c: [l, engine, base](const Value &obj) { |
638 | const QObjectWrapper::Flags flags = l->forCall |
639 | ? QObjectWrapper::NoFlag |
640 | : QObjectWrapper::AttachMethods; |
641 | return QObjectWrapper::lookupPropertyGetterImpl(lookup: l, engine, object: obj, flags, revertLookup: [l, engine, base]() { |
642 | return revertObjectPropertyLookup(l, engine, base); |
643 | }); |
644 | }); |
645 | } |
646 | |
647 | ReturnedValue QQmlContextWrapper::lookupScopeObjectMethod(Lookup *l, ExecutionEngine *engine, Value *base) |
648 | { |
649 | return callWithScopeObject(engine, base, c: [l, engine, base](const Value &obj) { |
650 | const QObjectWrapper::Flags flags = l->forCall |
651 | ? QObjectWrapper::NoFlag |
652 | : QObjectWrapper::AttachMethods; |
653 | return QObjectWrapper::lookupMethodGetterImpl(lookup: l, engine, object: obj, flags, revertLookup: [l, engine, base]() { |
654 | return revertObjectMethodLookup(l, engine, base); |
655 | }); |
656 | }); |
657 | } |
658 | |
659 | template<typename Call> |
660 | ReturnedValue callWithContextObject(ExecutionEngine *engine, Value *base, Call c) |
661 | { |
662 | Scope scope(engine); |
663 | Scoped<QmlContext> qmlContext(scope, engine->qmlContext()); |
664 | if (!qmlContext) |
665 | return QV4::Encode::undefined(); |
666 | |
667 | QQmlRefPointer<QQmlContextData> context = qmlContext->qmlContext(); |
668 | if (!context) |
669 | return QV4::Encode::undefined(); |
670 | |
671 | QObject *contextObject = context->contextObject(); |
672 | if (!contextObject) |
673 | return QV4::Encode::undefined(); |
674 | |
675 | if (QQmlData::wasDeleted(object: contextObject)) |
676 | return QV4::Encode::undefined(); |
677 | |
678 | ScopedValue obj(scope, QV4::QObjectWrapper::wrap(engine, object: contextObject)); |
679 | |
680 | if (base) |
681 | *base = obj; |
682 | |
683 | return c(obj); |
684 | } |
685 | |
686 | ReturnedValue QQmlContextWrapper::lookupContextObjectProperty( |
687 | Lookup *l, ExecutionEngine *engine, Value *base) |
688 | { |
689 | return callWithContextObject(engine, base, c: [l, engine, base](const Value &obj) { |
690 | const QObjectWrapper::Flags flags = l->forCall |
691 | ? QObjectWrapper::NoFlag |
692 | : QObjectWrapper::AttachMethods; |
693 | return QObjectWrapper::lookupPropertyGetterImpl(lookup: l, engine, object: obj, flags, revertLookup: [l, engine, base]() { |
694 | return revertObjectPropertyLookup(l, engine, base); |
695 | }); |
696 | }); |
697 | } |
698 | |
699 | ReturnedValue QQmlContextWrapper::lookupContextObjectMethod( |
700 | Lookup *l, ExecutionEngine *engine, Value *base) |
701 | { |
702 | return callWithContextObject(engine, base, c: [l, engine, base](const Value &obj) { |
703 | const QObjectWrapper::Flags flags = l->forCall |
704 | ? QObjectWrapper::NoFlag |
705 | : QObjectWrapper::AttachMethods; |
706 | return QObjectWrapper::lookupMethodGetterImpl(lookup: l, engine, object: obj, flags, revertLookup: [l, engine, base]() { |
707 | return revertObjectMethodLookup(l, engine, base); |
708 | }); |
709 | }); |
710 | } |
711 | |
712 | ReturnedValue QQmlContextWrapper::lookupScopeFallbackProperty(Lookup *l, ExecutionEngine *engine, Value *base) |
713 | { |
714 | return resolveQmlContextPropertyLookupGetter(l, engine, base); |
715 | } |
716 | |
717 | ReturnedValue QQmlContextWrapper::lookupInGlobalObject(Lookup *l, ExecutionEngine *engine, Value *base) |
718 | { |
719 | Q_UNUSED(base); |
720 | ReturnedValue result = l->qmlContextGlobalLookup.getterTrampoline(l, engine); |
721 | // In the unlikely event of mutation of the global object, update the trampoline. |
722 | if (l->qmlContextPropertyGetter != lookupInGlobalObject) { |
723 | l->qmlContextGlobalLookup.getterTrampoline = l->globalGetter; |
724 | l->qmlContextPropertyGetter = QQmlContextWrapper::lookupInGlobalObject; |
725 | } |
726 | return result; |
727 | } |
728 | |
729 | ReturnedValue QQmlContextWrapper::lookupInParentContextHierarchy(Lookup *l, ExecutionEngine *engine, Value *base) |
730 | { |
731 | Scope scope(engine); |
732 | Scoped<QmlContext> qmlContext(scope, engine->qmlContext()); |
733 | if (!qmlContext) |
734 | return QV4::Encode::undefined(); |
735 | |
736 | QQmlRefPointer<QQmlContextData> context = qmlContext->qmlContext(); |
737 | if (!context) |
738 | return QV4::Encode::undefined(); |
739 | |
740 | QQmlRefPointer<QQmlContextData> expressionContext = context; |
741 | |
742 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: engine->qmlEngine()); |
743 | |
744 | PropertyKey id =engine->identifierTable->asPropertyKey(str: engine->currentStackFrame->v4Function->compilationUnit-> |
745 | runtimeStrings[l->nameIndex]); |
746 | ScopedString name(scope, id.asStringOrSymbol()); |
747 | |
748 | ScopedValue result(scope); |
749 | |
750 | for (context = context->parent(); context; context = context->parent()) { |
751 | if (auto property = searchContextProperties(v4: engine, context, name, hasProperty: nullptr, base, lookup: nullptr, originalLookup: nullptr, ep)) |
752 | return *property; |
753 | |
754 | // Search context object |
755 | if (QObject *contextObject = context->contextObject()) { |
756 | bool hasProp = false; |
757 | QV4::ScopedObject wrapper(scope, QV4::QObjectWrapper::wrap(engine, object: contextObject)); |
758 | result = QV4::QObjectWrapper::getQmlProperty( |
759 | engine, qmlContext: context, wrapper: wrapper->d(), object: contextObject, name, flags: getQmlPropertyFlags(l), |
760 | hasProperty: &hasProp); |
761 | if (hasProp) { |
762 | if (base) |
763 | *base = wrapper; |
764 | |
765 | return result->asReturnedValue(); |
766 | } |
767 | } |
768 | } |
769 | |
770 | bool hasProp = false; |
771 | result = engine->globalObject->get(name, hasProperty: &hasProp); |
772 | if (hasProp) |
773 | return result->asReturnedValue(); |
774 | |
775 | expressionContext->setUnresolvedNames(true); |
776 | |
777 | return Encode::undefined(); |
778 | } |
779 | |
780 | ReturnedValue QQmlContextWrapper::lookupType(Lookup *l, ExecutionEngine *engine, Value *base) |
781 | { |
782 | Scope scope(engine); |
783 | Scoped<QmlContext> qmlContext(scope, engine->qmlContext()); |
784 | if (!qmlContext) |
785 | return QV4::Encode::undefined(); |
786 | |
787 | QObject *scopeObject = qmlContext->qmlScope(); |
788 | if (scopeObject && QQmlData::wasDeleted(object: scopeObject)) |
789 | return QV4::Encode::undefined(); |
790 | |
791 | Heap::Base *heapObject = l->qmlTypeLookup.qmlTypeWrapper; |
792 | if (static_cast<Heap::QQmlTypeWrapper *>(heapObject)->object != scopeObject) { |
793 | l->qmlTypeLookup.qmlTypeWrapper = nullptr; |
794 | l->qmlContextPropertyGetter = QQmlContextWrapper::resolveQmlContextPropertyLookupGetter; |
795 | return QQmlContextWrapper::resolveQmlContextPropertyLookupGetter(l, engine, base); |
796 | } |
797 | |
798 | return Value::fromHeapObject(m: heapObject).asReturnedValue(); |
799 | } |
800 | |
801 | void Heap::QmlContext::init(QV4::ExecutionContext *outerContext, QV4::QQmlContextWrapper *qml) |
802 | { |
803 | Heap::ExecutionContext::init(t: Heap::ExecutionContext::Type_QmlContext); |
804 | outer.set(e: internalClass->engine, newVal: outerContext->d()); |
805 | |
806 | this->activation.set(e: internalClass->engine, newVal: qml->d()); |
807 | } |
808 | |
809 | Heap::QmlContext *QmlContext::create( |
810 | ExecutionContext *parent, QQmlRefPointer<QQmlContextData> context, |
811 | QObject *scopeObject) |
812 | { |
813 | Scope scope(parent); |
814 | |
815 | Scoped<QQmlContextWrapper> qml( |
816 | scope, scope.engine->memoryManager->allocate<QQmlContextWrapper>( |
817 | args: std::move(context), args&: scopeObject)); |
818 | Heap::QmlContext *c = scope.engine->memoryManager->alloc<QmlContext>(args&: parent, args&: qml); |
819 | Q_ASSERT(c->vtable() == staticVTable()); |
820 | return c; |
821 | } |
822 | |
823 | QT_END_NAMESPACE |
824 | |