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 "qqmlexpression.h"
5#include "qqmlexpression_p.h"
6
7#include "qqmlengine_p.h"
8#include "qqmlscriptstring_p.h"
9#include "qqmlbinding_p.h"
10#include <private/qqmlsourcecoordinate_p.h>
11#include <private/qv4qmlcontext_p.h>
12
13#include <QtCore/qdebug.h>
14
15QT_BEGIN_NAMESPACE
16
17QQmlExpressionPrivate::QQmlExpressionPrivate()
18: QQmlJavaScriptExpression(),
19 expressionFunctionValid(true),
20 line(0), column(0)
21{
22}
23
24QQmlExpressionPrivate::~QQmlExpressionPrivate()
25{
26}
27
28void QQmlExpressionPrivate::init(const QQmlRefPointer<QQmlContextData> &ctxt, const QString &expr,
29 QObject *me)
30{
31 expression = expr;
32
33 QQmlJavaScriptExpression::setContext(ctxt);
34 setScopeObject(me);
35 expressionFunctionValid = false;
36}
37
38void QQmlExpressionPrivate::init(const QQmlRefPointer<QQmlContextData> &ctxt,
39 QV4::Function *runtimeFunction, QObject *me)
40{
41 expressionFunctionValid = true;
42 QV4::ExecutionEngine *engine = ctxt->engine()->handle();
43 QV4::Scope scope(engine);
44 QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(parent: engine->rootContext(), context: ctxt, scopeObject: me));
45 setupFunction(qmlContext, f: runtimeFunction);
46
47 QQmlJavaScriptExpression::setContext(ctxt);
48 setScopeObject(me);
49}
50
51/*!
52 \class QQmlExpression
53 \since 5.0
54 \inmodule QtQml
55 \brief The QQmlExpression class evaluates JavaScript in a QML context.
56
57 For example, given a file \c main.qml like this:
58
59 \qml
60 import QtQuick 2.0
61
62 Item {
63 width: 200; height: 200
64 }
65 \endqml
66
67 The following code evaluates a JavaScript expression in the context of the
68 above QML:
69
70 \code
71 QQmlEngine *engine = new QQmlEngine;
72 QQmlComponent component(engine, QUrl::fromLocalFile("main.qml"));
73
74 QObject *myObject = component.create();
75 QQmlExpression *expr = new QQmlExpression(engine->rootContext(), myObject, "width * 2");
76 int result = expr->evaluate().toInt(); // result = 400
77 \endcode
78*/
79
80/*!
81 Create an invalid QQmlExpression.
82
83 As the expression will not have an associated QQmlContext, this will be a
84 null expression object and its value will always be an invalid QVariant.
85 */
86QQmlExpression::QQmlExpression()
87: QObject(*new QQmlExpressionPrivate, nullptr)
88{
89}
90
91/*!
92 Create a QQmlExpression object that is a child of \a parent.
93
94 The \a script provides the expression to be evaluated, the context to evaluate it in,
95 and the scope object to evaluate it with. If provided, \a ctxt and \a scope will override
96 the context and scope object provided by \a script.
97
98 \sa QQmlScriptString
99*/
100QQmlExpression::QQmlExpression(const QQmlScriptString &script, QQmlContext *ctxt, QObject *scope, QObject *parent)
101: QObject(*new QQmlExpressionPrivate, parent)
102{
103 Q_D(QQmlExpression);
104 if (ctxt && !ctxt->isValid())
105 return;
106
107 const QQmlScriptStringPrivate *scriptPrivate = script.d.data();
108 if (!scriptPrivate) {
109 // A null QQmlScriptStringPrivate is an empty expression without context.
110 // We may still want the explicitly passed context, though.
111 if (ctxt)
112 d->init(ctxt: QQmlContextData::get(context: ctxt), expr: QString(), me: scope);
113 return;
114 }
115
116 if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid()))
117 return;
118
119 QQmlRefPointer<QQmlContextData> evalCtxtData
120 = QQmlContextData::get(context: ctxt ? ctxt : scriptPrivate->context);
121 QObject *scopeObject = scope ? scope : scriptPrivate->scope;
122 QV4::Function *runtimeFunction = nullptr;
123
124 if (scriptPrivate->context) {
125 QQmlRefPointer<QQmlContextData> ctxtdata = QQmlContextData::get(context: scriptPrivate->context);
126 QQmlEnginePrivate *engine = QQmlEnginePrivate::get(e: scriptPrivate->context->engine());
127 if (engine
128 && ctxtdata
129 && !ctxtdata->urlString().isEmpty()
130 && ctxtdata->typeCompilationUnit()) {
131 d->url = ctxtdata->urlString();
132 d->line = scriptPrivate->lineNumber;
133 d->column = scriptPrivate->columnNumber;
134
135 if (scriptPrivate->bindingId != QQmlBinding::Invalid)
136 runtimeFunction = ctxtdata->typeCompilationUnit()->runtimeFunctions.at(i: scriptPrivate->bindingId);
137 }
138 }
139
140 if (runtimeFunction) {
141 d->expression = scriptPrivate->script;
142 d->init(ctxt: evalCtxtData, runtimeFunction, me: scopeObject);
143 } else
144 d->init(ctxt: evalCtxtData, expr: scriptPrivate->script, me: scopeObject);
145}
146
147/*!
148 Create a QQmlExpression object that is a child of \a parent.
149
150 The \a expression JavaScript will be executed in the \a ctxt QQmlContext.
151 If specified, the \a scope object's properties will also be in scope during
152 the expression's execution.
153*/
154QQmlExpression::QQmlExpression(QQmlContext *ctxt, QObject *scope, const QString &expression,
155 QObject *parent)
156: QObject(*new QQmlExpressionPrivate, parent)
157{
158 Q_D(QQmlExpression);
159 d->init(ctxt: QQmlContextData::get(context: ctxt), expr: expression, me: scope);
160}
161
162/*!
163 \internal
164*/
165QQmlExpression::QQmlExpression(QQmlExpressionPrivate &dd, QObject *parent) : QObject(dd, parent)
166{
167// Q_D(QQmlExpression);
168// d->init(QQmlContextData::get(ctxt), expression, scope);
169}
170
171/*!
172 Destroy the QQmlExpression instance.
173*/
174QQmlExpression::~QQmlExpression()
175{
176}
177
178/*!
179 Returns the QQmlEngine this expression is associated with, or \nullptr if there
180 is no association or the QQmlEngine has been destroyed.
181*/
182QQmlEngine *QQmlExpression::engine() const
183{
184 Q_D(const QQmlExpression);
185 return d->engine();
186}
187
188/*!
189 Returns the QQmlContext this expression is associated with, or \nullptr if there
190 is no association or the QQmlContext has been destroyed.
191*/
192QQmlContext *QQmlExpression::context() const
193{
194 Q_D(const QQmlExpression);
195 return d->publicContext();
196}
197
198/*!
199 Returns the expression string.
200*/
201QString QQmlExpression::expression() const
202{
203 Q_D(const QQmlExpression);
204 return d->expression;
205}
206
207/*!
208 Set the expression to \a expression.
209*/
210void QQmlExpression::setExpression(const QString &expression)
211{
212 Q_D(QQmlExpression);
213
214 d->resetNotifyOnValueChanged();
215 d->expression = expression;
216 d->expressionFunctionValid = false;
217}
218
219// Must be called with a valid handle scope
220QV4::ReturnedValue QQmlExpressionPrivate::v4value(bool *isUndefined)
221{
222 if (!expressionFunctionValid) {
223 createQmlBinding(ctxt: context(), scope: scopeObject(), code: expression, filename: url, line);
224 expressionFunctionValid = true;
225 if (hasError()) {
226 if (isUndefined)
227 *isUndefined = true;
228 return QV4::Encode::undefined();
229 }
230 }
231
232 return evaluate(isUndefined);
233}
234
235QVariant QQmlExpressionPrivate::value(bool *isUndefined)
236{
237 Q_Q(QQmlExpression);
238
239 if (!hasValidContext()) {
240 qWarning(msg: "QQmlExpression: Attempted to evaluate an expression in an invalid context");
241 return QVariant();
242 }
243
244 QQmlEngine *engine = q->engine();
245 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: engine);
246 QVariant rv;
247
248 ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation.
249
250 {
251 QV4::Scope scope(engine->handle());
252 QV4::ScopedValue result(scope, v4value(isUndefined));
253 if (!hasError())
254 rv = QV4::ExecutionEngine::toVariant(value: result, typeHint: QMetaType {});
255 }
256
257 ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete.
258
259 return rv;
260}
261
262/*!
263 Evaulates the expression, returning the result of the evaluation,
264 or an invalid QVariant if the expression is invalid or has an error.
265
266 \a valueIsUndefined is set to true if the expression resulted in an
267 undefined value.
268
269 \sa hasError(), error()
270*/
271QVariant QQmlExpression::evaluate(bool *valueIsUndefined)
272{
273 Q_D(QQmlExpression);
274 return d->value(isUndefined: valueIsUndefined);
275}
276
277/*!
278Returns true if the valueChanged() signal is emitted when the expression's evaluated
279value changes.
280*/
281bool QQmlExpression::notifyOnValueChanged() const
282{
283 Q_D(const QQmlExpression);
284 return d->notifyOnValueChanged();
285}
286
287/*!
288 Sets whether the valueChanged() signal is emitted when the
289 expression's evaluated value changes.
290
291 If \a notifyOnChange is true, the QQmlExpression will
292 monitor properties involved in the expression's evaluation, and emit
293 QQmlExpression::valueChanged() if they have changed. This
294 allows an application to ensure that any value associated with the
295 result of the expression remains up to date.
296
297 If \a notifyOnChange is false (default), the QQmlExpression
298 will not montitor properties involved in the expression's
299 evaluation, and QQmlExpression::valueChanged() will never be
300 emitted. This is more efficient if an application wants a "one off"
301 evaluation of the expression.
302*/
303void QQmlExpression::setNotifyOnValueChanged(bool notifyOnChange)
304{
305 Q_D(QQmlExpression);
306 d->setNotifyOnValueChanged(notifyOnChange);
307}
308
309/*!
310 Returns the source file URL for this expression. The source location must
311 have been previously set by calling setSourceLocation().
312*/
313QString QQmlExpression::sourceFile() const
314{
315 Q_D(const QQmlExpression);
316 return d->url;
317}
318
319/*!
320 Returns the source file line number for this expression. The source location
321 must have been previously set by calling setSourceLocation().
322*/
323int QQmlExpression::lineNumber() const
324{
325 Q_D(const QQmlExpression);
326 return qmlConvertSourceCoordinate<quint16, int>(n: d->line);
327}
328
329/*!
330 Returns the source file column number for this expression. The source location
331 must have been previously set by calling setSourceLocation().
332*/
333int QQmlExpression::columnNumber() const
334{
335 Q_D(const QQmlExpression);
336 return qmlConvertSourceCoordinate<quint16, int>(n: d->column);
337}
338
339/*!
340 Set the location of this expression to \a line and \a column of \a url. This information
341 is used by the script engine.
342*/
343void QQmlExpression::setSourceLocation(const QString &url, int line, int column)
344{
345 Q_D(QQmlExpression);
346 d->url = url;
347 d->line = qmlConvertSourceCoordinate<int, quint16>(n: line);
348 d->column = qmlConvertSourceCoordinate<int, quint16>(n: column);
349}
350
351/*!
352 Returns the expression's scope object, if provided, otherwise 0.
353
354 In addition to data provided by the expression's QQmlContext, the scope
355 object's properties are also in scope during the expression's evaluation.
356*/
357QObject *QQmlExpression::scopeObject() const
358{
359 Q_D(const QQmlExpression);
360 return d->scopeObject();
361}
362
363/*!
364 Returns true if the last call to evaluate() resulted in an error,
365 otherwise false.
366
367 \sa error(), clearError()
368*/
369bool QQmlExpression::hasError() const
370{
371 Q_D(const QQmlExpression);
372 return d->hasError();
373}
374
375/*!
376 Clear any expression errors. Calls to hasError() following this will
377 return false.
378
379 \sa hasError(), error()
380*/
381void QQmlExpression::clearError()
382{
383 Q_D(QQmlExpression);
384 d->clearError();
385}
386
387/*!
388 Return any error from the last call to evaluate(). If there was no error,
389 this returns an invalid QQmlError instance.
390
391 \sa hasError(), clearError()
392*/
393
394QQmlError QQmlExpression::error() const
395{
396 Q_D(const QQmlExpression);
397 return d->error(engine());
398}
399
400/*!
401 \fn void QQmlExpression::valueChanged()
402
403 Emitted each time the expression value changes from the last time it was
404 evaluated. The expression must have been evaluated at least once (by
405 calling QQmlExpression::evaluate()) before this signal will be emitted.
406*/
407
408void QQmlExpressionPrivate::expressionChanged()
409{
410 Q_Q(QQmlExpression);
411 emit q->valueChanged();
412}
413
414QString QQmlExpressionPrivate::expressionIdentifier() const
415{
416 return QLatin1Char('"') + expression + QLatin1Char('"');
417}
418
419QT_END_NAMESPACE
420
421#include <moc_qqmlexpression.cpp>
422

source code of qtdeclarative/src/qml/qml/qqmlexpression.cpp