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 test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <qtest.h> |
30 | #include <QDebug> |
31 | #include <QTimer> |
32 | #include <QQmlEngine> |
33 | #include <QQmlContext> |
34 | #include <QQmlComponent> |
35 | #include <QQmlExpression> |
36 | #include <private/qqmlcontext_p.h> |
37 | #include <private/qv4qmlcontext_p.h> |
38 | #include <private/qv4object_p.h> |
39 | #include "../../shared/util.h" |
40 | |
41 | class tst_qqmlcontext : public QQmlDataTest |
42 | { |
43 | Q_OBJECT |
44 | public: |
45 | tst_qqmlcontext() {} |
46 | |
47 | private slots: |
48 | void baseUrl(); |
49 | void resolvedUrl(); |
50 | void engineMethod(); |
51 | void parentContext(); |
52 | void setContextProperty(); |
53 | void setContextProperties(); |
54 | void setContextObject(); |
55 | void destruction(); |
56 | void idAsContextProperty(); |
57 | void readOnlyContexts(); |
58 | void nameForObject(); |
59 | |
60 | void refreshExpressions(); |
61 | void refreshExpressionsCrash(); |
62 | void refreshExpressionsRootContext(); |
63 | void skipExpressionRefresh_qtbug_53431(); |
64 | |
65 | void qtbug_22535(); |
66 | void evalAfterInvalidate(); |
67 | void qobjectDerived(); |
68 | void qtbug_49232(); |
69 | void contextViaClosureAfterDestruction(); |
70 | void contextLeak(); |
71 | |
72 | void outerContextObject(); |
73 | void contextObjectHierarchy(); |
74 | void destroyContextProperty(); |
75 | |
76 | private: |
77 | QQmlEngine engine; |
78 | }; |
79 | |
80 | void tst_qqmlcontext::baseUrl() |
81 | { |
82 | QQmlContext ctxt(&engine); |
83 | |
84 | QCOMPARE(ctxt.baseUrl(), QUrl()); |
85 | |
86 | ctxt.setBaseUrl(QUrl("http://www.qt-project.org/" )); |
87 | |
88 | QCOMPARE(ctxt.baseUrl(), QUrl("http://www.qt-project.org/" )); |
89 | } |
90 | |
91 | void tst_qqmlcontext::resolvedUrl() |
92 | { |
93 | // Relative to the component |
94 | { |
95 | QQmlContext ctxt(&engine); |
96 | ctxt.setBaseUrl(QUrl("http://www.qt-project.org/" )); |
97 | |
98 | QCOMPARE(ctxt.resolvedUrl(QUrl("main.qml" )), QUrl("http://www.qt-project.org/main.qml" )); |
99 | } |
100 | |
101 | // Relative to a parent |
102 | { |
103 | QQmlContext ctxt(&engine); |
104 | ctxt.setBaseUrl(QUrl("http://www.qt-project.org/" )); |
105 | |
106 | QQmlContext ctxt2(&ctxt); |
107 | QCOMPARE(ctxt2.resolvedUrl(QUrl("main2.qml" )), QUrl("http://www.qt-project.org/main2.qml" )); |
108 | } |
109 | |
110 | // Relative to the engine |
111 | { |
112 | QQmlContext ctxt(&engine); |
113 | QCOMPARE(ctxt.resolvedUrl(QUrl("main.qml" )), engine.baseUrl().resolved(QUrl("main.qml" ))); |
114 | } |
115 | |
116 | // Relative to a deleted parent |
117 | { |
118 | QQmlContext *ctxt = new QQmlContext(&engine); |
119 | ctxt->setBaseUrl(QUrl("http://www.qt-project.org/" )); |
120 | |
121 | QQmlContext ctxt2(ctxt); |
122 | QCOMPARE(ctxt2.resolvedUrl(QUrl("main2.qml" )), QUrl("http://www.qt-project.org/main2.qml" )); |
123 | |
124 | delete ctxt; ctxt = nullptr; |
125 | |
126 | QCOMPARE(ctxt2.resolvedUrl(QUrl("main2.qml" )), QUrl()); |
127 | } |
128 | |
129 | // Absolute |
130 | { |
131 | QQmlContext ctxt(&engine); |
132 | |
133 | QCOMPARE(ctxt.resolvedUrl(QUrl("http://www.qt-project.org/main2.qml" )), QUrl("http://www.qt-project.org/main2.qml" )); |
134 | QCOMPARE(ctxt.resolvedUrl(QUrl("file:///main2.qml" )), QUrl("file:///main2.qml" )); |
135 | } |
136 | } |
137 | |
138 | void tst_qqmlcontext::engineMethod() |
139 | { |
140 | QQmlEngine *engine = new QQmlEngine; |
141 | |
142 | QQmlContext ctxt(engine); |
143 | QQmlContext ctxt2(&ctxt); |
144 | QQmlContext ctxt3(&ctxt2); |
145 | QQmlContext ctxt4(&ctxt2); |
146 | |
147 | QCOMPARE(ctxt.engine(), engine); |
148 | QCOMPARE(ctxt2.engine(), engine); |
149 | QCOMPARE(ctxt3.engine(), engine); |
150 | QCOMPARE(ctxt4.engine(), engine); |
151 | |
152 | delete engine; engine = nullptr; |
153 | |
154 | QCOMPARE(ctxt.engine(), engine); |
155 | QCOMPARE(ctxt2.engine(), engine); |
156 | QCOMPARE(ctxt3.engine(), engine); |
157 | QCOMPARE(ctxt4.engine(), engine); |
158 | } |
159 | |
160 | void tst_qqmlcontext::parentContext() |
161 | { |
162 | QQmlEngine *engine = new QQmlEngine; |
163 | |
164 | QCOMPARE(engine->rootContext()->parentContext(), (QQmlContext *)nullptr); |
165 | |
166 | QQmlContext *ctxt = new QQmlContext(engine); |
167 | QQmlContext *ctxt2 = new QQmlContext(ctxt); |
168 | QQmlContext *ctxt3 = new QQmlContext(ctxt2); |
169 | QQmlContext *ctxt4 = new QQmlContext(ctxt2); |
170 | QQmlContext *ctxt5 = new QQmlContext(ctxt); |
171 | QQmlContext *ctxt6 = new QQmlContext(engine); |
172 | QQmlContext *ctxt7 = new QQmlContext(engine->rootContext()); |
173 | |
174 | QCOMPARE(ctxt->parentContext(), engine->rootContext()); |
175 | QCOMPARE(ctxt2->parentContext(), ctxt); |
176 | QCOMPARE(ctxt3->parentContext(), ctxt2); |
177 | QCOMPARE(ctxt4->parentContext(), ctxt2); |
178 | QCOMPARE(ctxt5->parentContext(), ctxt); |
179 | QCOMPARE(ctxt6->parentContext(), engine->rootContext()); |
180 | QCOMPARE(ctxt7->parentContext(), engine->rootContext()); |
181 | |
182 | delete ctxt2; ctxt2 = nullptr; |
183 | |
184 | QCOMPARE(ctxt->parentContext(), engine->rootContext()); |
185 | QCOMPARE(ctxt3->parentContext(), (QQmlContext *)nullptr); |
186 | QCOMPARE(ctxt4->parentContext(), (QQmlContext *)nullptr); |
187 | QCOMPARE(ctxt5->parentContext(), ctxt); |
188 | QCOMPARE(ctxt6->parentContext(), engine->rootContext()); |
189 | QCOMPARE(ctxt7->parentContext(), engine->rootContext()); |
190 | |
191 | delete engine; engine = nullptr; |
192 | |
193 | QCOMPARE(ctxt->parentContext(), (QQmlContext *)nullptr); |
194 | QCOMPARE(ctxt3->parentContext(), (QQmlContext *)nullptr); |
195 | QCOMPARE(ctxt4->parentContext(), (QQmlContext *)nullptr); |
196 | QCOMPARE(ctxt5->parentContext(), (QQmlContext *)nullptr); |
197 | QCOMPARE(ctxt6->parentContext(), (QQmlContext *)nullptr); |
198 | QCOMPARE(ctxt7->parentContext(), (QQmlContext *)nullptr); |
199 | |
200 | delete ctxt7; |
201 | delete ctxt6; |
202 | delete ctxt5; |
203 | delete ctxt4; |
204 | delete ctxt3; |
205 | delete ctxt; |
206 | } |
207 | |
208 | class TestObject : public QObject |
209 | { |
210 | Q_OBJECT |
211 | Q_PROPERTY(int a READ a NOTIFY aChanged) |
212 | Q_PROPERTY(int b READ b NOTIFY bChanged) |
213 | Q_PROPERTY(int c READ c NOTIFY cChanged) |
214 | Q_PROPERTY(char d READ d NOTIFY dChanged) |
215 | Q_PROPERTY(uchar e READ e NOTIFY eChanged) |
216 | |
217 | public: |
218 | TestObject() : _a(10), _b(10), _c(10) {} |
219 | |
220 | int a() const { return _a; } |
221 | void setA(int a) { _a = a; emit aChanged(); } |
222 | |
223 | int b() const { return _b; } |
224 | void setB(int b) { _b = b; emit bChanged(); } |
225 | |
226 | int c() const { return _c; } |
227 | void setC(int c) { _c = c; emit cChanged(); } |
228 | |
229 | char d() const { return _d; } |
230 | void setD(char d) { _d = d; emit dChanged(); } |
231 | |
232 | uchar e() const { return _e; } |
233 | void setE(uchar e) { _e = e; emit eChanged(); } |
234 | |
235 | signals: |
236 | void aChanged(); |
237 | void bChanged(); |
238 | void cChanged(); |
239 | void dChanged(); |
240 | void eChanged(); |
241 | |
242 | private: |
243 | int _a; |
244 | int _b; |
245 | int _c; |
246 | char _d; |
247 | uchar _e; |
248 | }; |
249 | |
250 | #define TEST_CONTEXT_PROPERTY(ctxt, name, value) \ |
251 | { \ |
252 | QQmlComponent component(&engine); \ |
253 | component.setData("import QtQuick 2.0; QtObject { property variant test: " #name " }", QUrl()); \ |
254 | \ |
255 | QObject *obj = component.create(ctxt); \ |
256 | \ |
257 | QCOMPARE(obj->property("test"), value); \ |
258 | \ |
259 | delete obj; \ |
260 | } |
261 | |
262 | void tst_qqmlcontext::setContextProperty() |
263 | { |
264 | QQmlContext ctxt(&engine); |
265 | QQmlContext ctxt2(&ctxt); |
266 | |
267 | TestObject obj1; |
268 | obj1.setA(3345); |
269 | TestObject obj2; |
270 | obj2.setA(-19); |
271 | |
272 | // Static context properties |
273 | ctxt.setContextProperty("a" , QVariant(10)); |
274 | ctxt.setContextProperty("b" , QVariant(9)); |
275 | ctxt2.setContextProperty("d" , &obj2); |
276 | ctxt2.setContextProperty("b" , QVariant(19)); |
277 | ctxt2.setContextProperty("c" , QVariant(QString("Hello World!" ))); |
278 | ctxt.setContextProperty("d" , &obj1); |
279 | ctxt.setContextProperty("e" , &obj1); |
280 | |
281 | TEST_CONTEXT_PROPERTY(&ctxt2, a, QVariant(10)); |
282 | TEST_CONTEXT_PROPERTY(&ctxt2, b, QVariant(19)); |
283 | TEST_CONTEXT_PROPERTY(&ctxt2, c, QVariant(QString("Hello World!" ))); |
284 | TEST_CONTEXT_PROPERTY(&ctxt2, d.a, QVariant(-19)); |
285 | TEST_CONTEXT_PROPERTY(&ctxt2, e.a, QVariant(3345)); |
286 | |
287 | ctxt.setContextProperty("a" , QVariant(13)); |
288 | ctxt.setContextProperty("b" , QVariant(4)); |
289 | ctxt2.setContextProperty("b" , QVariant(8)); |
290 | ctxt2.setContextProperty("c" , QVariant(QString("Hi World!" ))); |
291 | ctxt2.setContextProperty("d" , &obj1); |
292 | obj1.setA(12); |
293 | |
294 | TEST_CONTEXT_PROPERTY(&ctxt2, a, QVariant(13)); |
295 | TEST_CONTEXT_PROPERTY(&ctxt2, b, QVariant(8)); |
296 | TEST_CONTEXT_PROPERTY(&ctxt2, c, QVariant(QString("Hi World!" ))); |
297 | TEST_CONTEXT_PROPERTY(&ctxt2, d.a, QVariant(12)); |
298 | TEST_CONTEXT_PROPERTY(&ctxt2, e.a, QVariant(12)); |
299 | |
300 | // Changes in context properties |
301 | { |
302 | QQmlComponent component(&engine); |
303 | component.setData("import QtQuick 2.0; QtObject { property variant test: a }" , baseUrl: QUrl()); |
304 | |
305 | QObject *obj = component.create(context: &ctxt2); |
306 | |
307 | QCOMPARE(obj->property("test" ), QVariant(13)); |
308 | ctxt.setContextProperty("a" , QVariant(19)); |
309 | QCOMPARE(obj->property("test" ), QVariant(19)); |
310 | |
311 | delete obj; |
312 | } |
313 | { |
314 | QQmlComponent component(&engine); |
315 | component.setData("import QtQuick 2.0; QtObject { property variant test: b }" , baseUrl: QUrl()); |
316 | |
317 | QObject *obj = component.create(context: &ctxt2); |
318 | |
319 | QCOMPARE(obj->property("test" ), QVariant(8)); |
320 | ctxt.setContextProperty("b" , QVariant(5)); |
321 | QCOMPARE(obj->property("test" ), QVariant(8)); |
322 | ctxt2.setContextProperty("b" , QVariant(1912)); |
323 | QCOMPARE(obj->property("test" ), QVariant(1912)); |
324 | |
325 | delete obj; |
326 | } |
327 | { |
328 | QQmlComponent component(&engine); |
329 | component.setData("import QtQuick 2.0; QtObject { property variant test: e.a }" , baseUrl: QUrl()); |
330 | |
331 | QObject *obj = component.create(context: &ctxt2); |
332 | |
333 | QCOMPARE(obj->property("test" ), QVariant(12)); |
334 | obj1.setA(13); |
335 | QCOMPARE(obj->property("test" ), QVariant(13)); |
336 | |
337 | delete obj; |
338 | } |
339 | |
340 | // New context properties |
341 | { |
342 | QQmlComponent component(&engine); |
343 | component.setData("import QtQuick 2.0; QtObject { property variant test: a }" , baseUrl: QUrl()); |
344 | |
345 | QObject *obj = component.create(context: &ctxt2); |
346 | |
347 | QCOMPARE(obj->property("test" ), QVariant(19)); |
348 | ctxt2.setContextProperty("a" , QVariant(1945)); |
349 | QCOMPARE(obj->property("test" ), QVariant(1945)); |
350 | |
351 | delete obj; |
352 | } |
353 | |
354 | // Setting an object-variant context property |
355 | { |
356 | QQmlComponent component(&engine); |
357 | component.setData("import QtQuick 2.0; QtObject { id: root; property int a: 10; property int test: ctxtProp.a; property variant obj: root; }" , baseUrl: QUrl()); |
358 | |
359 | QQmlContext ctxt(engine.rootContext()); |
360 | ctxt.setContextProperty("ctxtProp" , QVariant()); |
361 | |
362 | QTest::ignoreMessage(type: QtWarningMsg, message: "<Unknown File>:1: TypeError: Cannot read property 'a' of undefined" ); |
363 | QObject *obj = component.create(context: &ctxt); |
364 | |
365 | QVariant v = obj->property(name: "obj" ); |
366 | |
367 | ctxt.setContextProperty("ctxtProp" , v); |
368 | |
369 | QCOMPARE(obj->property("test" ), QVariant(10)); |
370 | |
371 | delete obj; |
372 | } |
373 | } |
374 | |
375 | void tst_qqmlcontext::setContextProperties() |
376 | { |
377 | QQmlContext ctxt(&engine); |
378 | |
379 | TestObject obj1; |
380 | obj1.setA(3345); |
381 | TestObject obj2; |
382 | obj2.setA(-19); |
383 | |
384 | QVector<QQmlContext::PropertyPair> properties; |
385 | |
386 | properties.append(t: {.name: QString("a" ), .value: QVariant(10)}); |
387 | properties.append(t: {.name: QString("b" ), .value: QVariant(19)}); |
388 | properties.append(t: {.name: QString("d" ), .value: QVariant::fromValue<TestObject*>(value: &obj2)}); |
389 | properties.append(t: {.name: QString("c" ), .value: QVariant(QString("Hello World!" ))}); |
390 | properties.append(t: {.name: QString("e" ), .value: QVariant::fromValue<TestObject*>(value: &obj1)}); |
391 | |
392 | ctxt.setContextProperties(properties); |
393 | |
394 | TEST_CONTEXT_PROPERTY(&ctxt, a, QVariant(10)); |
395 | TEST_CONTEXT_PROPERTY(&ctxt, b, QVariant(19)); |
396 | TEST_CONTEXT_PROPERTY(&ctxt, c, QVariant(QString("Hello World!" ))); |
397 | TEST_CONTEXT_PROPERTY(&ctxt, d.a, QVariant(-19)); |
398 | TEST_CONTEXT_PROPERTY(&ctxt, e.a, QVariant(3345)); |
399 | } |
400 | |
401 | void tst_qqmlcontext::setContextObject() |
402 | { |
403 | QQmlContext ctxt(&engine); |
404 | |
405 | TestObject to; |
406 | |
407 | to.setA(2); |
408 | to.setB(192); |
409 | to.setC(18); |
410 | |
411 | ctxt.setContextObject(&to); |
412 | ctxt.setContextProperty("c" , QVariant(9)); |
413 | |
414 | // Static context properties |
415 | TEST_CONTEXT_PROPERTY(&ctxt, a, QVariant(2)); |
416 | TEST_CONTEXT_PROPERTY(&ctxt, b, QVariant(192)); |
417 | TEST_CONTEXT_PROPERTY(&ctxt, c, QVariant(9)); |
418 | |
419 | to.setA(12); |
420 | to.setB(100); |
421 | to.setC(7); |
422 | ctxt.setContextProperty("c" , QVariant(3)); |
423 | |
424 | TEST_CONTEXT_PROPERTY(&ctxt, a, QVariant(12)); |
425 | TEST_CONTEXT_PROPERTY(&ctxt, b, QVariant(100)); |
426 | TEST_CONTEXT_PROPERTY(&ctxt, c, QVariant(3)); |
427 | |
428 | // Changes in context properties |
429 | { |
430 | QQmlComponent component(&engine); |
431 | component.setData("import QtQuick 2.0; QtObject { property variant test: a }" , baseUrl: QUrl()); |
432 | |
433 | QObject *obj = component.create(context: &ctxt); |
434 | |
435 | QCOMPARE(obj->property("test" ), QVariant(12)); |
436 | to.setA(14); |
437 | QCOMPARE(obj->property("test" ), QVariant(14)); |
438 | |
439 | delete obj; |
440 | } |
441 | |
442 | // Change of context object |
443 | ctxt.setContextProperty("c" , QVariant(30)); |
444 | TestObject to2; |
445 | to2.setA(10); |
446 | to2.setB(20); |
447 | to2.setC(40); |
448 | ctxt.setContextObject(&to2); |
449 | |
450 | TEST_CONTEXT_PROPERTY(&ctxt, a, QVariant(10)); |
451 | TEST_CONTEXT_PROPERTY(&ctxt, b, QVariant(20)); |
452 | TEST_CONTEXT_PROPERTY(&ctxt, c, QVariant(30)); |
453 | } |
454 | |
455 | void tst_qqmlcontext::destruction() |
456 | { |
457 | QQmlContext *ctxt = new QQmlContext(&engine); |
458 | |
459 | QObject obj; |
460 | QQmlEngine::setContextForObject(&obj, ctxt); |
461 | QQmlExpression expr(ctxt, nullptr, "a" ); |
462 | |
463 | QCOMPARE(ctxt, QQmlEngine::contextForObject(&obj)); |
464 | QCOMPARE(ctxt, expr.context()); |
465 | |
466 | delete ctxt; ctxt = nullptr; |
467 | |
468 | QCOMPARE(ctxt, QQmlEngine::contextForObject(&obj)); |
469 | QCOMPARE(ctxt, expr.context()); |
470 | } |
471 | |
472 | void tst_qqmlcontext::idAsContextProperty() |
473 | { |
474 | QQmlComponent component(&engine); |
475 | component.setData("import QtQuick 2.0; QtObject { property variant a; a: QtObject { id: myObject } }" , baseUrl: QUrl()); |
476 | |
477 | QObject *obj = component.create(); |
478 | QVERIFY(obj); |
479 | |
480 | QVariant a = obj->property(name: "a" ); |
481 | QCOMPARE(a.userType(), int(QMetaType::QObjectStar)); |
482 | |
483 | QVariant ctxt = qmlContext(obj)->contextProperty("myObject" ); |
484 | QCOMPARE(ctxt.userType(), int(QMetaType::QObjectStar)); |
485 | |
486 | QCOMPARE(a, ctxt); |
487 | |
488 | delete obj; |
489 | } |
490 | |
491 | // Internal contexts should be read-only |
492 | void tst_qqmlcontext::readOnlyContexts() |
493 | { |
494 | QQmlComponent component(&engine); |
495 | component.setData("import QtQuick 2.0; QtObject { id: me }" , baseUrl: QUrl()); |
496 | |
497 | QObject *obj = component.create(); |
498 | QVERIFY(obj); |
499 | |
500 | QQmlContext *context = qmlContext(obj); |
501 | QVERIFY(context); |
502 | |
503 | QCOMPARE(qvariant_cast<QObject*>(context->contextProperty("me" )), obj); |
504 | QCOMPARE(context->contextObject(), obj); |
505 | |
506 | QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlContext: Cannot set property on internal context." ); |
507 | context->setContextProperty("hello" , 12); |
508 | QCOMPARE(context->contextProperty("hello" ), QVariant()); |
509 | |
510 | QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlContext: Cannot set property on internal context." ); |
511 | context->setContextProperty("hello" , obj); |
512 | QCOMPARE(context->contextProperty("hello" ), QVariant()); |
513 | |
514 | QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlContext: Cannot set context object for internal context." ); |
515 | context->setContextObject(nullptr); |
516 | QCOMPARE(context->contextObject(), obj); |
517 | |
518 | delete obj; |
519 | } |
520 | |
521 | void tst_qqmlcontext::nameForObject() |
522 | { |
523 | QObject o1; |
524 | QObject o2; |
525 | QObject o3; |
526 | |
527 | QQmlEngine engine; |
528 | |
529 | // As a context property |
530 | engine.rootContext()->setContextProperty("o1" , &o1); |
531 | engine.rootContext()->setContextProperty("o2" , &o2); |
532 | engine.rootContext()->setContextProperty("o1_2" , &o1); |
533 | |
534 | QCOMPARE(engine.rootContext()->nameForObject(&o1), QString("o1" )); |
535 | QCOMPARE(engine.rootContext()->nameForObject(&o2), QString("o2" )); |
536 | QCOMPARE(engine.rootContext()->nameForObject(&o3), QString()); |
537 | |
538 | // As an id |
539 | QQmlComponent component(&engine); |
540 | component.setData("import QtQuick 2.0; QtObject { id: root; property QtObject o: QtObject { id: nested } }" , baseUrl: QUrl()); |
541 | |
542 | QObject *o = component.create(); |
543 | QVERIFY(o != nullptr); |
544 | |
545 | QCOMPARE(qmlContext(o)->nameForObject(o), QString("root" )); |
546 | QCOMPARE(qmlContext(o)->nameForObject(qvariant_cast<QObject*>(o->property("o" ))), QString("nested" )); |
547 | QCOMPARE(qmlContext(o)->nameForObject(&o1), QString()); |
548 | |
549 | delete o; |
550 | } |
551 | |
552 | class DeleteCommand : public QObject |
553 | { |
554 | Q_OBJECT |
555 | public: |
556 | DeleteCommand() : object(nullptr) {} |
557 | |
558 | QObject *object; |
559 | |
560 | public slots: |
561 | void doCommand() { if (object) delete object; object = nullptr; } |
562 | }; |
563 | |
564 | // Calling refresh expressions would crash if an expression or context was deleted during |
565 | // the refreshing |
566 | void tst_qqmlcontext::refreshExpressionsCrash() |
567 | { |
568 | { |
569 | QQmlEngine engine; |
570 | |
571 | DeleteCommand command; |
572 | engine.rootContext()->setContextProperty("deleteCommand" , &command); |
573 | // We use a fresh context here to bypass any root-context optimizations in |
574 | // the engine |
575 | QQmlContext ctxt(engine.rootContext()); |
576 | |
577 | QQmlComponent component(&engine); |
578 | component.setData("import QtQuick 2.0; QtObject { property var binding: deleteCommand.doCommand() }" , baseUrl: QUrl()); |
579 | QVERIFY(component.isReady()); |
580 | |
581 | QObject *o1 = component.create(context: &ctxt); |
582 | QObject *o2 = component.create(context: &ctxt); |
583 | |
584 | command.object = o2; |
585 | |
586 | QQmlContextData::get(context: &ctxt)->refreshExpressions(); |
587 | |
588 | delete o1; |
589 | } |
590 | { |
591 | QQmlEngine engine; |
592 | |
593 | DeleteCommand command; |
594 | engine.rootContext()->setContextProperty("deleteCommand" , &command); |
595 | // We use a fresh context here to bypass any root-context optimizations in |
596 | // the engine |
597 | QQmlContext ctxt(engine.rootContext()); |
598 | |
599 | QQmlComponent component(&engine); |
600 | component.setData("import QtQuick 2.0; QtObject { property var binding: deleteCommand.doCommand() }" , baseUrl: QUrl()); |
601 | QVERIFY(component.isReady()); |
602 | |
603 | QObject *o1 = component.create(context: &ctxt); |
604 | QObject *o2 = component.create(context: &ctxt); |
605 | |
606 | command.object = o1; |
607 | |
608 | QQmlContextData::get(context: &ctxt)->refreshExpressions(); |
609 | |
610 | delete o2; |
611 | } |
612 | } |
613 | |
614 | class CountCommand : public QObject |
615 | { |
616 | Q_OBJECT |
617 | public: |
618 | CountCommand() : count(0) {} |
619 | |
620 | int count; |
621 | |
622 | public slots: |
623 | void doCommand() { ++count; } |
624 | }; |
625 | |
626 | |
627 | // Test that calling refresh expressions causes all the expressions to refresh |
628 | void tst_qqmlcontext::refreshExpressions() |
629 | { |
630 | QQmlEngine engine; |
631 | QQmlComponent component(&engine, testFileUrl(fileName: "refreshExpressions.qml" )); |
632 | QQmlComponent component2(&engine, testFileUrl(fileName: "RefreshExpressionsType.qml" )); |
633 | |
634 | CountCommand command; |
635 | engine.rootContext()->setContextProperty("countCommand" , &command); |
636 | |
637 | // We use a fresh context here to bypass any root-context optimizations in |
638 | // the engine |
639 | QQmlContext context(engine.rootContext()); |
640 | QQmlContext context2(&context); |
641 | |
642 | QObject *o1 = component.create(context: &context); |
643 | QObject *o2 = component.create(context: &context2); |
644 | QObject *o3 = component2.create(context: &context); |
645 | |
646 | QCOMPARE(command.count, 5); |
647 | |
648 | QQmlContextData::get(context: &context)->refreshExpressions(); |
649 | |
650 | QCOMPARE(command.count, 10); |
651 | |
652 | delete o3; |
653 | delete o2; |
654 | delete o1; |
655 | } |
656 | |
657 | // Test that updating the root context, only causes expressions in contexts with an |
658 | // unresolved name to reevaluate |
659 | void tst_qqmlcontext::refreshExpressionsRootContext() |
660 | { |
661 | QQmlEngine engine; |
662 | |
663 | CountCommand command; |
664 | engine.rootContext()->setContextProperty("countCommand" , &command); |
665 | |
666 | QQmlComponent component(&engine, testFileUrl(fileName: "refreshExpressions.qml" )); |
667 | QQmlComponent component2(&engine, testFileUrl(fileName: "refreshExpressionsRootContext.qml" )); |
668 | |
669 | QQmlContext context(engine.rootContext()); |
670 | QQmlContext context2(engine.rootContext()); |
671 | |
672 | QString warning = component2.url().toString() + QLatin1String(":4: ReferenceError: unresolvedName is not defined" ); |
673 | |
674 | QObject *o1 = component.create(context: &context); |
675 | |
676 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
677 | QObject *o2 = component2.create(context: &context2); |
678 | |
679 | QCOMPARE(command.count, 3); |
680 | |
681 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
682 | QQmlContextData::get(context: engine.rootContext())->refreshExpressions(); |
683 | |
684 | QCOMPARE(command.count, 4); |
685 | |
686 | delete o2; |
687 | delete o1; |
688 | } |
689 | |
690 | void tst_qqmlcontext::skipExpressionRefresh_qtbug_53431() |
691 | { |
692 | QQmlEngine engine; |
693 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_53431.qml" )); |
694 | QScopedPointer<QObject> object(component.create(context: nullptr)); |
695 | QVERIFY(!object.isNull()); |
696 | QCOMPARE(object->property("value" ).toInt(), 1); |
697 | object->setProperty(name: "value" , value: 10); |
698 | QCOMPARE(object->property("value" ).toInt(), 10); |
699 | engine.rootContext()->setContextProperty("randomContextProperty" , 42); |
700 | QCOMPARE(object->property("value" ).toInt(), 10); |
701 | } |
702 | |
703 | void tst_qqmlcontext::qtbug_22535() |
704 | { |
705 | QQmlEngine engine; |
706 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_22535.qml" )); |
707 | QQmlContext context(engine.rootContext()); |
708 | |
709 | QObject *o = component.create(context: &context); |
710 | |
711 | // Don't crash! |
712 | delete o; |
713 | } |
714 | |
715 | void tst_qqmlcontext::evalAfterInvalidate() |
716 | { |
717 | QQmlEngine engine; |
718 | QQmlComponent component(&engine, testFileUrl(fileName: "evalAfterInvalidate.qml" )); |
719 | QScopedPointer<QObject> o(component.create()); |
720 | |
721 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
722 | QCoreApplication::processEvents(); |
723 | } |
724 | |
725 | void tst_qqmlcontext::qobjectDerived() |
726 | { |
727 | QQmlEngine engine; |
728 | QQmlComponent component(&engine, testFileUrl(fileName: "refreshExpressions.qml" )); |
729 | |
730 | CountCommand command; |
731 | // This test is similar to refreshExpressions, but with the key difference that |
732 | // we use the QVariant overload of setContextProperty. That way, we test that |
733 | // QVariant knowledge that it contains a QObject derived pointer is used. |
734 | engine.rootContext()->setContextProperty("countCommand" , QVariant::fromValue(value: &command)); |
735 | |
736 | // We use a fresh context here to bypass any root-context optimizations in |
737 | // the engine |
738 | QQmlContext context(engine.rootContext()); |
739 | |
740 | QObject *o1 = component.create(context: &context); |
741 | Q_UNUSED(o1); |
742 | |
743 | QCOMPARE(command.count, 2); |
744 | } |
745 | |
746 | void tst_qqmlcontext::qtbug_49232() |
747 | { |
748 | TestObject testObject; |
749 | testObject.setD('a'); |
750 | testObject.setE(97); |
751 | |
752 | QQmlEngine engine; |
753 | engine.rootContext()->setContextProperty("TestObject" , &testObject); |
754 | QQmlComponent component(&engine); |
755 | component.setData("import QtQuick 2.0; QtObject { property int valueOne: TestObject.d; property int valueTwo: TestObject.e }" , baseUrl: QUrl()); |
756 | QScopedPointer<QObject> obj(component.create()); |
757 | |
758 | QCOMPARE(obj->property("valueOne" ), QVariant('a')); |
759 | QCOMPARE(obj->property("valueTwo" ), QVariant(97)); |
760 | } |
761 | |
762 | void tst_qqmlcontext::contextViaClosureAfterDestruction() |
763 | { |
764 | qmlRegisterSingletonType(url: testFileUrl(fileName: "Singleton.qml" ), uri: "constants" , versionMajor: 1, versionMinor: 0, qmlName: "Sing" ); |
765 | QQmlEngine engine; |
766 | QQmlComponent component(&engine, testFileUrl(fileName: "contextViaClosureAfterDestruction.qml" )); |
767 | QJSValue valueClosure; |
768 | QJSValue componentFactoryClosure; |
769 | { |
770 | QScopedPointer<QObject> obj(component.create()); |
771 | QVERIFY(!obj.isNull()); |
772 | // meta-calls don't support QJSValue return types, so do the call "by hand" |
773 | valueClosure = engine.newQObject(object: obj.data()).property(QStringLiteral("createClosure" )).call(); |
774 | QVERIFY(valueClosure.isCallable()); |
775 | componentFactoryClosure = engine.newQObject(object: obj.data()).property(QStringLiteral("createComponentFactory" )).call(); |
776 | QVERIFY(componentFactoryClosure.isCallable()); |
777 | } |
778 | QCOMPARE(valueClosure.call().toString(), QLatin1String("Highway to Hell" )); |
779 | |
780 | QScopedPointer<QObject> parent(new QObject); |
781 | QJSValue parentWrapper = engine.newQObject(object: parent.data()); |
782 | QQmlEngine::setObjectOwnership(parent.data(), QQmlEngine::CppOwnership); |
783 | |
784 | QJSValue subObject = componentFactoryClosure.callWithInstance(instance: componentFactoryClosure, args: QJSValueList() << parentWrapper); |
785 | QVERIFY(subObject.isError()); |
786 | QCOMPARE(subObject.toString(), QLatin1String("Error: Qt.createQmlObject(): Cannot create a component in an invalid context" )); |
787 | } |
788 | |
789 | void tst_qqmlcontext::contextLeak() |
790 | { |
791 | QQmlEngine engine; |
792 | QQmlComponent component(&engine, testFileUrl(fileName: "contextLeak.qml" )); |
793 | |
794 | QQmlGuardedContextData scriptContext; |
795 | |
796 | { |
797 | QScopedPointer<QObject> obj(component.create()); |
798 | QVERIFY(!obj.isNull()); |
799 | QCOMPARE(obj->property("value" ).toInt(), 42); |
800 | |
801 | QQmlData *ddata = QQmlData::get(object: obj.data()); |
802 | QVERIFY(ddata); |
803 | QQmlContextData *context = ddata->context; |
804 | QVERIFY(context); |
805 | QVERIFY(!context->importedScripts.isNullOrUndefined()); |
806 | QCOMPARE(int(context->importedScripts.valueRef()->as<QV4::Object>()->getLength()), 1); |
807 | |
808 | { |
809 | QV4::Scope scope(ddata->jsWrapper.engine()); |
810 | QV4::ScopedValue scriptContextWrapper(scope); |
811 | scriptContextWrapper = context->importedScripts.valueRef()->as<QV4::Object>()->get(idx: uint(0)); |
812 | scriptContext = scriptContextWrapper->as<QV4::QQmlContextWrapper>()->getContext(); |
813 | } |
814 | } |
815 | |
816 | engine.collectGarbage(); |
817 | |
818 | // Each time a JS file (non-pragma-shared) is imported, we create a QQmlContext(Data) for it. |
819 | // Make sure that context does not leak. |
820 | QVERIFY(scriptContext.isNull()); |
821 | } |
822 | |
823 | |
824 | static bool buildObjectList(QQmlContext *ctxt) |
825 | { |
826 | static QHash<QObject *, QString> deletedObjects; |
827 | QQmlContextData *p = QQmlContextData::get(context: ctxt); |
828 | QObject *object = p->contextObject; |
829 | if (object) { |
830 | // If the object was actually deleted this is likely to crash in one way or another. |
831 | // Either the memory is still intact, then we will probably find the objectName, or it is |
832 | // not, then the connect() below is likely to fail. |
833 | if (deletedObjects.contains(key: object) && deletedObjects[object] == object->objectName()) |
834 | return false; |
835 | QObject::connect(sender: object, signal: &QObject::destroyed, slot: [object]() { |
836 | object->setObjectName(QString::number(deletedObjects.size())); |
837 | deletedObjects.insert(key: object, value: object->objectName()); |
838 | }); |
839 | } |
840 | |
841 | QQmlContextData *child = p->childContexts; |
842 | while (child) { |
843 | if (!buildObjectList(ctxt: child->asQQmlContext())) |
844 | return false; |
845 | child = child->nextChild; |
846 | } |
847 | |
848 | return true; |
849 | } |
850 | |
851 | void tst_qqmlcontext::outerContextObject() |
852 | { |
853 | QQmlEngine engine; |
854 | |
855 | QQmlComponent component(&engine, testFileUrl(fileName: "outerContextObject.qml" )); |
856 | QVERIFY(component.isReady()); |
857 | |
858 | QScopedPointer<QObject> object(component.create()); |
859 | QVERIFY(!object.isNull()); |
860 | |
861 | int iterations = 0; |
862 | QTimer timer; |
863 | timer.setInterval(1); |
864 | QObject::connect(sender: &timer, signal: &QTimer::timeout, context: &engine, slot: [&]() { |
865 | if (!buildObjectList(ctxt: engine.rootContext())) { |
866 | iterations = 100; |
867 | timer.stop(); |
868 | QFAIL("Deleted object found as context object" ); |
869 | } else { |
870 | ++iterations; |
871 | } |
872 | }); |
873 | timer.start(); |
874 | |
875 | QTRY_VERIFY(iterations >= 100); |
876 | } |
877 | |
878 | void tst_qqmlcontext::contextObjectHierarchy() |
879 | { |
880 | QQmlEngine engine; |
881 | QQmlComponent component(&engine); |
882 | component.loadUrl(url: testFileUrl(fileName: "contextObjectHierarchy.qml" )); |
883 | QVERIFY(component.isReady()); |
884 | QScopedPointer<QObject> root(component.create()); |
885 | QVERIFY(!root.isNull()); |
886 | |
887 | for (const QObject *child : root->children()) |
888 | QVERIFY(QQmlData::get(child)->outerContext != nullptr); |
889 | |
890 | connect(sender: root.data(), signal: &QObject::destroyed, slot: [&root]() { |
891 | for (const QObject *child : root->children()) |
892 | QCOMPARE(QQmlData::get(child)->outerContext, nullptr); |
893 | }); |
894 | } |
895 | |
896 | void tst_qqmlcontext::destroyContextProperty() |
897 | { |
898 | QScopedPointer<QQmlContext> context; |
899 | QScopedPointer<QObject> objectThatOutlivesEngine(new QObject); |
900 | { |
901 | QQmlEngine engine; |
902 | context.reset(other: new QQmlContext(&engine)); |
903 | |
904 | { |
905 | QObject object; |
906 | context->setContextProperty(QLatin1String("a" ), &object); |
907 | QCOMPARE(qvariant_cast<QObject *>(context->contextProperty(QLatin1String("a" ))), &object); |
908 | } |
909 | |
910 | QCOMPARE(qvariant_cast<QObject *>(context->contextProperty(QLatin1String("a" ))), nullptr); |
911 | context->setContextProperty(QLatin1String("b" ), objectThatOutlivesEngine.data()); |
912 | } |
913 | |
914 | // dropDestroyedObject() should not crash, even if the engine is gone. |
915 | objectThatOutlivesEngine.reset(); |
916 | |
917 | // We're not allowed to call context->contextProperty("b") anymore. |
918 | // TODO: Or are we? |
919 | } |
920 | |
921 | QTEST_MAIN(tst_qqmlcontext) |
922 | |
923 | #include "tst_qqmlcontext.moc" |
924 | |