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
41class tst_qqmlcontext : public QQmlDataTest
42{
43 Q_OBJECT
44public:
45 tst_qqmlcontext() {}
46
47private 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
76private:
77 QQmlEngine engine;
78};
79
80void 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
91void 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
138void 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
160void 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
208class 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
217public:
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
235signals:
236 void aChanged();
237 void bChanged();
238 void cChanged();
239 void dChanged();
240 void eChanged();
241
242private:
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
262void 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
375void 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
401void 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
455void 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
472void 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
492void 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
521void 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
552class DeleteCommand : public QObject
553{
554Q_OBJECT
555public:
556 DeleteCommand() : object(nullptr) {}
557
558 QObject *object;
559
560public 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
566void 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
614class CountCommand : public QObject
615{
616Q_OBJECT
617public:
618 CountCommand() : count(0) {}
619
620 int count;
621
622public slots:
623 void doCommand() { ++count; }
624};
625
626
627// Test that calling refresh expressions causes all the expressions to refresh
628void 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
659void 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
690void 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
703void 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
715void 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
725void 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
746void 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
762void 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
789void 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
824static 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
851void 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
878void 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
896void 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
921QTEST_MAIN(tst_qqmlcontext)
922
923#include "tst_qqmlcontext.moc"
924

source code of qtdeclarative/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp