Docs - add missing images and code, clean up sections
[profile/ivi/qtdeclarative.git] / src / qml / qml / qqmljavascriptexpression.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qqmljavascriptexpression_p.h"
43
44 #include <private/qqmlexpression_p.h>
45
46 QT_BEGIN_NAMESPACE
47
48 bool QQmlDelayedError::addError(QQmlEnginePrivate *e)
49 {
50     if (!e) return false;
51
52     if (e->inProgressCreations == 0) return false; // Not in construction
53
54     if (prevError) return true; // Already in error chain
55
56     prevError = &e->erroredBindings;
57     nextError = e->erroredBindings;
58     e->erroredBindings = this;
59     if (nextError) nextError->prevError = &nextError;
60
61     return true;
62 }
63
64 void QQmlDelayedError::setMessage(v8::Handle<v8::Message> message)
65 {
66     qPersistentDispose(m_message);
67     m_message = qPersistentNew<v8::Message>(message);
68 }
69
70 void QQmlDelayedError::setErrorLocation(const QUrl &url, quint16 line, quint16 column)
71 {
72     m_error.setUrl(url);
73     m_error.setLine(line);
74     m_error.setColumn(column);
75 }
76
77 void QQmlDelayedError::setErrorDescription(const QString &description)
78 {
79     m_error.setDescription(description);
80 }
81
82 /*
83     Converting from a message to an error is relatively expensive.
84
85     We don't want to do this work for transient exceptions (exceptions
86     that occur during startup because of the order of binding
87     execution, but have gone away by the time startup has finished), so we
88     delay conversion until it is required for displaying the error.
89 */
90 void QQmlDelayedError::convertMessageToError(QQmlEngine *engine) const
91 {
92     if (!m_message.IsEmpty() && engine) {
93         v8::HandleScope handle_scope;
94         v8::Context::Scope context_scope(QQmlEnginePrivate::getV8Engine(engine)->context());
95         QQmlExpressionPrivate::exceptionToError(m_message, m_error);
96         qPersistentDispose(m_message);
97     }
98 }
99
100 QQmlJavaScriptExpression::QQmlJavaScriptExpression(VTable *v)
101 : m_vtable(v)
102 {
103 }
104
105 QQmlJavaScriptExpression::~QQmlJavaScriptExpression()
106 {
107     clearGuards();
108     if (m_scopeObject.isT2()) // notify DeleteWatcher of our deletion.
109         m_scopeObject.asT2()->_s = 0;
110 }
111
112 void QQmlJavaScriptExpression::setNotifyOnValueChanged(bool v)
113 {
114     activeGuards.setFlagValue(v);
115     if (!v) clearGuards();
116 }
117
118 void QQmlJavaScriptExpression::resetNotifyOnValueChanged()
119 {
120     clearGuards();
121 }
122
123 v8::Local<v8::Value>
124 QQmlJavaScriptExpression::evaluate(QQmlContextData *context,
125                                    v8::Handle<v8::Function> function, bool *isUndefined)
126 {
127     return evaluate(context, function, 0, 0, isUndefined);
128 }
129
130 v8::Local<v8::Value>
131 QQmlJavaScriptExpression::evaluate(QQmlContextData *context,
132                                    v8::Handle<v8::Function> function,
133                                    int argc, v8::Handle<v8::Value> args[],
134                                    bool *isUndefined)
135 {
136     Q_ASSERT(context && context->engine);
137
138     if (function.IsEmpty() || function->IsUndefined()) {
139         if (isUndefined) *isUndefined = true;
140         return v8::Local<v8::Value>();
141     }
142
143     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context->engine);
144
145     Q_ASSERT(notifyOnValueChanged() || activeGuards.isEmpty());
146     GuardCapture capture(context->engine, this);
147
148     QQmlEnginePrivate::PropertyCapture *lastPropertyCapture = ep->propertyCapture;
149     ep->propertyCapture = notifyOnValueChanged()?&capture:0;
150
151
152     if (notifyOnValueChanged())
153         capture.guards.copyAndClearPrepend(activeGuards);
154
155     QQmlContextData *lastSharedContext = 0;
156     QObject *lastSharedScope = 0;
157
158     bool sharedContext = useSharedContext();
159
160     // All code that follows must check with watcher before it accesses data members
161     // incase we have been deleted.
162     DeleteWatcher watcher(this);
163
164     if (sharedContext) {
165         lastSharedContext = ep->sharedContext;
166         lastSharedScope = ep->sharedScope;
167         ep->sharedContext = context;
168         ep->sharedScope = scopeObject();
169     }
170
171     v8::Local<v8::Value> result;
172     {
173         v8::TryCatch try_catch;
174         v8::Handle<v8::Object> This = ep->v8engine()->global();
175         if (scopeObject() && requiresThisObject()) {
176             v8::Handle<v8::Value> value = ep->v8engine()->newQObject(scopeObject());
177             if (value->IsObject()) This = v8::Handle<v8::Object>::Cast(value);
178         }
179
180         result = function->Call(This, argc, args);
181
182         if (isUndefined)
183             *isUndefined = try_catch.HasCaught() || result->IsUndefined();
184
185         if (watcher.wasDeleted()) {
186         } else if (try_catch.HasCaught()) {
187             v8::Context::Scope scope(ep->v8engine()->context());
188             v8::Local<v8::Message> message = try_catch.Message();
189             if (!message.IsEmpty()) {
190                 delayedError()->setMessage(message);
191             } else {
192                 if (hasDelayedError()) delayedError()->clearError();
193             }
194         } else {
195             if (hasDelayedError()) delayedError()->clearError();
196         }
197     }
198
199     if (sharedContext) {
200         ep->sharedContext = lastSharedContext;
201         ep->sharedScope = lastSharedScope;
202     }
203
204     if (capture.errorString) {
205         for (int ii = 0; ii < capture.errorString->count(); ++ii)
206             qWarning("%s", qPrintable(capture.errorString->at(ii)));
207         delete capture.errorString;
208         capture.errorString = 0;
209     }
210
211     while (Guard *g = capture.guards.takeFirst())
212         g->Delete();
213
214     ep->propertyCapture = lastPropertyCapture;
215
216     return result;
217 }
218
219 void QQmlJavaScriptExpression::GuardCapture::captureProperty(QQmlNotifier *n)
220 {
221     if (expression) {
222
223         // Try and find a matching guard
224         while (!guards.isEmpty() && !guards.first()->isConnected(n))
225             guards.takeFirst()->Delete();
226
227         Guard *g = 0;
228         if (!guards.isEmpty()) {
229             g = guards.takeFirst();
230             g->cancelNotify();
231             Q_ASSERT(g->isConnected(n));
232         } else {
233             g = Guard::New(expression, engine);
234             g->connect(n);
235         }
236
237         expression->activeGuards.prepend(g);
238     }
239 }
240
241 /*! \internal
242     \reimp
243
244     \a n is in the signal index range (see QObjectPrivate::signalIndex()).
245 */
246 void QQmlJavaScriptExpression::GuardCapture::captureProperty(QObject *o, int c, int n)
247 {
248     if (expression) {
249         if (n == -1) {
250             if (!errorString) {
251                 errorString = new QStringList;
252                 QString preamble = QLatin1String("QQmlExpression: Expression ") +
253                                    expression->m_vtable->expressionIdentifier(expression) +
254                                    QLatin1String(" depends on non-NOTIFYable properties:");
255                 errorString->append(preamble);
256             }
257
258             const QMetaObject *metaObj = o->metaObject();
259             QMetaProperty metaProp = metaObj->property(c);
260
261             QString error = QLatin1String("    ") +
262                             QString::fromUtf8(metaObj->className()) +
263                             QLatin1String("::") +
264                             QString::fromUtf8(metaProp.name());
265             errorString->append(error);
266         } else {
267
268             // Try and find a matching guard
269             while (!guards.isEmpty() && !guards.first()->isConnected(o, n))
270                 guards.takeFirst()->Delete();
271
272             Guard *g = 0;
273             if (!guards.isEmpty()) {
274                 g = guards.takeFirst();
275                 g->cancelNotify();
276                 Q_ASSERT(g->isConnected(o, n));
277             } else {
278                 g = Guard::New(expression, engine);
279                 g->connect(o, n, engine);
280             }
281
282             expression->activeGuards.prepend(g);
283         }
284     }
285 }
286
287 void QQmlJavaScriptExpression::clearError()
288 {
289     if (m_vtable.hasValue()) {
290         m_vtable.value().clearError();
291         m_vtable.value().removeError();
292     }
293 }
294
295 QQmlError QQmlJavaScriptExpression::error(QQmlEngine *engine) const
296 {
297     if (m_vtable.hasValue()) return m_vtable.constValue()->error(engine);
298     else return QQmlError();
299 }
300
301 QQmlDelayedError *QQmlJavaScriptExpression::delayedError()
302 {
303     return &m_vtable.value();
304 }
305
306 void QQmlJavaScriptExpression::exceptionToError(v8::Handle<v8::Message> message, QQmlError &error)
307 {
308     Q_ASSERT(!message.IsEmpty());
309
310     v8::Handle<v8::Value> name = message->GetScriptResourceName();
311     v8::Handle<v8::String> description = message->Get();
312     int lineNumber = message->GetLineNumber();
313
314     v8::Local<v8::String> file = name->IsString()?name->ToString():v8::Local<v8::String>();
315     if (file.IsEmpty() || file->Length() == 0)
316         error.setUrl(QUrl());
317     else
318         error.setUrl(QUrl(QV8Engine::toStringStatic(file)));
319
320     error.setLine(lineNumber);
321     error.setColumn(-1);
322
323     QString qDescription = QV8Engine::toStringStatic(description);
324     if (qDescription.startsWith(QLatin1String("Uncaught ")))
325         qDescription = qDescription.mid(9 /* strlen("Uncaught ") */);
326
327     error.setDescription(qDescription);
328 }
329
330 // Callee owns the persistent handle
331 v8::Persistent<v8::Function>
332 QQmlJavaScriptExpression::evalFunction(QQmlContextData *ctxt, QObject *scope,
333                                        const char *code, int codeLength,
334                                        const QString &filename, quint16 line,
335                                        v8::Persistent<v8::Object> *qmlscope)
336 {
337     QQmlEngine *engine = ctxt->engine;
338     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
339
340     v8::HandleScope handle_scope;
341     v8::Context::Scope ctxtscope(ep->v8engine()->context());
342
343     v8::TryCatch tc;
344     v8::Local<v8::Object> scopeobject = ep->v8engine()->qmlScope(ctxt, scope);
345     v8::Local<v8::Script> script = ep->v8engine()->qmlModeCompile(code, codeLength, filename, line);
346     if (tc.HasCaught()) {
347         QQmlError error;
348         error.setDescription(QLatin1String("Exception occurred during function compilation"));
349         error.setLine(line);
350         error.setUrl(QUrl::fromLocalFile(filename));
351         v8::Local<v8::Message> message = tc.Message();
352         if (!message.IsEmpty())
353             QQmlExpressionPrivate::exceptionToError(message, error);
354         ep->warning(error);
355         return v8::Persistent<v8::Function>();
356     }
357     v8::Local<v8::Value> result = script->Run(scopeobject);
358     if (tc.HasCaught()) {
359         QQmlError error;
360         error.setDescription(QLatin1String("Exception occurred during function evaluation"));
361         error.setLine(line);
362         error.setUrl(QUrl::fromLocalFile(filename));
363         v8::Local<v8::Message> message = tc.Message();
364         if (!message.IsEmpty())
365             QQmlExpressionPrivate::exceptionToError(message, error);
366         ep->warning(error);
367         return v8::Persistent<v8::Function>();
368     }
369     if (qmlscope) *qmlscope = qPersistentNew<v8::Object>(scopeobject);
370     return qPersistentNew<v8::Function>(v8::Local<v8::Function>::Cast(result));
371 }
372
373 // Callee owns the persistent handle
374 v8::Persistent<v8::Function>
375 QQmlJavaScriptExpression::evalFunction(QQmlContextData *ctxt, QObject *scope,
376                                        const QString &code, const QString &filename, quint16 line,
377                                        v8::Persistent<v8::Object> *qmlscope)
378 {
379     QQmlEngine *engine = ctxt->engine;
380     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
381
382     v8::HandleScope handle_scope;
383     v8::Context::Scope ctxtscope(ep->v8engine()->context());
384
385     v8::TryCatch tc;
386     v8::Local<v8::Object> scopeobject = ep->v8engine()->qmlScope(ctxt, scope);
387     v8::Local<v8::Script> script = ep->v8engine()->qmlModeCompile(code, filename, line);
388     if (tc.HasCaught()) {
389         QQmlError error;
390         error.setDescription(QLatin1String("Exception occurred during function compilation"));
391         error.setLine(line);
392         error.setUrl(QUrl::fromLocalFile(filename));
393         v8::Local<v8::Message> message = tc.Message();
394         if (!message.IsEmpty())
395             QQmlExpressionPrivate::exceptionToError(message, error);
396         ep->warning(error);
397         return v8::Persistent<v8::Function>();
398     }
399     v8::Local<v8::Value> result = script->Run(scopeobject);
400     if (tc.HasCaught()) {
401         QQmlError error;
402         error.setDescription(QLatin1String("Exception occurred during function evaluation"));
403         error.setLine(line);
404         error.setUrl(QUrl::fromLocalFile(filename));
405         v8::Local<v8::Message> message = tc.Message();
406         if (!message.IsEmpty())
407             QQmlExpressionPrivate::exceptionToError(message, error);
408         ep->warning(error);
409         return v8::Persistent<v8::Function>();
410     }
411     if (qmlscope) *qmlscope = qPersistentNew<v8::Object>(scopeobject);
412     return qPersistentNew<v8::Function>(v8::Local<v8::Function>::Cast(result));
413 }
414
415 void QQmlJavaScriptExpression::clearGuards()
416 {
417     while (Guard *g = activeGuards.takeFirst())
418         g->Delete();
419 }
420
421 void QQmlJavaScriptExpressionGuard_callback(QQmlNotifierEndpoint *e, void **)
422 {
423     QQmlJavaScriptExpression *expression =
424         static_cast<QQmlJavaScriptExpressionGuard *>(e)->expression;
425
426     expression->m_vtable->expressionChanged(expression);
427 }
428
429 QT_END_NAMESPACE