Delay conversion of v8 exceptions to QQmlErrors.
[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, int line, int 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     Q_ASSERT(context && context->engine);
128
129     if (function.IsEmpty() || function->IsUndefined()) {
130         if (isUndefined) *isUndefined = true;
131         return v8::Local<v8::Value>();
132     }
133
134     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context->engine);
135
136     Q_ASSERT(notifyOnValueChanged() || activeGuards.isEmpty());
137     GuardCapture capture(context->engine, this);
138
139     QQmlEnginePrivate::PropertyCapture *lastPropertyCapture = ep->propertyCapture;
140     ep->propertyCapture = notifyOnValueChanged()?&capture:0;
141
142
143     if (notifyOnValueChanged())
144         capture.guards.copyAndClearPrepend(activeGuards);
145
146     QQmlContextData *lastSharedContext = 0;
147     QObject *lastSharedScope = 0;
148
149     bool sharedContext = useSharedContext();
150
151     // All code that follows must check with watcher before it accesses data members
152     // incase we have been deleted.
153     DeleteWatcher watcher(this);
154
155     if (sharedContext) {
156         lastSharedContext = ep->sharedContext;
157         lastSharedScope = ep->sharedScope;
158         ep->sharedContext = context;
159         ep->sharedScope = scopeObject();
160     }
161
162     v8::Local<v8::Value> result;
163     {
164         v8::TryCatch try_catch;
165         v8::Handle<v8::Object> This = ep->v8engine()->global();
166         if (scopeObject() && requiresThisObject()) {
167             v8::Handle<v8::Value> value = ep->v8engine()->newQObject(scopeObject());
168             if (value->IsObject()) This = v8::Handle<v8::Object>::Cast(value);
169         }
170
171         result = function->Call(This, 0, 0);
172
173         if (isUndefined)
174             *isUndefined = try_catch.HasCaught() || result->IsUndefined();
175
176         if (watcher.wasDeleted()) {
177         } else if (try_catch.HasCaught()) {
178             v8::Context::Scope scope(ep->v8engine()->context());
179             v8::Local<v8::Message> message = try_catch.Message();
180             if (!message.IsEmpty()) {
181                 delayedError()->setMessage(message);
182             } else {
183                 if (hasDelayedError()) delayedError()->clearError();
184             }
185         } else {
186             if (hasDelayedError()) delayedError()->clearError();
187         }
188     }
189
190     if (sharedContext) {
191         ep->sharedContext = lastSharedContext;
192         ep->sharedScope = lastSharedScope;
193     }
194
195     if (capture.errorString) {
196         for (int ii = 0; ii < capture.errorString->count(); ++ii)
197             qWarning("%s", qPrintable(capture.errorString->at(ii)));
198         delete capture.errorString;
199         capture.errorString = 0;
200     }
201
202     while (Guard *g = capture.guards.takeFirst())
203         g->Delete();
204
205     ep->propertyCapture = lastPropertyCapture;
206
207     return result;
208 }
209
210 void QQmlJavaScriptExpression::GuardCapture::captureProperty(QQmlNotifier *n)
211 {
212     if (expression) {
213
214         // Try and find a matching guard
215         while (!guards.isEmpty() && !guards.first()->isConnected(n))
216             guards.takeFirst()->Delete();
217
218         Guard *g = 0;
219         if (!guards.isEmpty()) {
220             g = guards.takeFirst();
221             g->cancelNotify();
222             Q_ASSERT(g->isConnected(n));
223         } else {
224             g = Guard::New(expression, engine);
225             g->connect(n);
226         }
227
228         expression->activeGuards.prepend(g);
229     }
230 }
231
232 void QQmlJavaScriptExpression::GuardCapture::captureProperty(QObject *o, int c, int n)
233 {
234     if (expression) {
235         if (n == -1) {
236             if (!errorString) {
237                 errorString = new QStringList;
238                 QString preamble = QLatin1String("QQmlExpression: Expression ") +
239                                    expression->m_vtable->expressionIdentifier(expression) +
240                                    QLatin1String(" depends on non-NOTIFYable properties:");
241                 errorString->append(preamble);
242             }
243
244             const QMetaObject *metaObj = o->metaObject();
245             QMetaProperty metaProp = metaObj->property(c);
246
247             QString error = QLatin1String("    ") +
248                             QString::fromUtf8(metaObj->className()) +
249                             QLatin1String("::") +
250                             QString::fromUtf8(metaProp.name());
251             errorString->append(error);
252         } else {
253
254             // Try and find a matching guard
255             while (!guards.isEmpty() && !guards.first()->isConnected(o, n))
256                 guards.takeFirst()->Delete();
257
258             Guard *g = 0;
259             if (!guards.isEmpty()) {
260                 g = guards.takeFirst();
261                 g->cancelNotify();
262                 Q_ASSERT(g->isConnected(o, n));
263             } else {
264                 g = Guard::New(expression, engine);
265                 g->connect(o, n, engine);
266             }
267
268             expression->activeGuards.prepend(g);
269         }
270     }
271 }
272
273 void QQmlJavaScriptExpression::clearError()
274 {
275     if (m_vtable.hasValue()) {
276         m_vtable.value().clearError();
277         m_vtable.value().removeError();
278     }
279 }
280
281 QQmlError QQmlJavaScriptExpression::error(QQmlEngine *engine) const
282 {
283     if (m_vtable.hasValue()) return m_vtable.constValue()->error(engine);
284     else return QQmlError();
285 }
286
287 QQmlDelayedError *QQmlJavaScriptExpression::delayedError()
288 {
289     return &m_vtable.value();
290 }
291
292 void QQmlJavaScriptExpression::exceptionToError(v8::Handle<v8::Message> message, QQmlError &error)
293 {
294     Q_ASSERT(!message.IsEmpty());
295
296     v8::Handle<v8::Value> name = message->GetScriptResourceName();
297     v8::Handle<v8::String> description = message->Get();
298     int lineNumber = message->GetLineNumber();
299
300     v8::Local<v8::String> file = name->IsString()?name->ToString():v8::Local<v8::String>();
301     if (file.IsEmpty() || file->Length() == 0)
302         error.setUrl(QUrl());
303     else
304         error.setUrl(QUrl(QV8Engine::toStringStatic(file)));
305
306     error.setLine(lineNumber);
307     error.setColumn(-1);
308
309     QString qDescription = QV8Engine::toStringStatic(description);
310     if (qDescription.startsWith(QLatin1String("Uncaught ")))
311         qDescription = qDescription.mid(9 /* strlen("Uncaught ") */);
312
313     error.setDescription(qDescription);
314 }
315
316 // Callee owns the persistent handle
317 v8::Persistent<v8::Function>
318 QQmlJavaScriptExpression::evalFunction(QQmlContextData *ctxt, QObject *scope,
319                                        const char *code, int codeLength,
320                                        const QString &filename, int line,
321                                        v8::Persistent<v8::Object> *qmlscope)
322 {
323     QQmlEngine *engine = ctxt->engine;
324     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
325
326     v8::HandleScope handle_scope;
327     v8::Context::Scope ctxtscope(ep->v8engine()->context());
328
329     v8::TryCatch tc;
330     v8::Local<v8::Object> scopeobject = ep->v8engine()->qmlScope(ctxt, scope);
331     v8::Local<v8::Script> script = ep->v8engine()->qmlModeCompile(code, codeLength, filename, line);
332     if (tc.HasCaught()) {
333         QQmlError error;
334         error.setDescription(QLatin1String("Exception occurred during function compilation"));
335         error.setLine(line);
336         error.setUrl(QUrl::fromLocalFile(filename));
337         v8::Local<v8::Message> message = tc.Message();
338         if (!message.IsEmpty())
339             QQmlExpressionPrivate::exceptionToError(message, error);
340         ep->warning(error);
341         return v8::Persistent<v8::Function>();
342     }
343     v8::Local<v8::Value> result = script->Run(scopeobject);
344     if (tc.HasCaught()) {
345         QQmlError error;
346         error.setDescription(QLatin1String("Exception occurred during function evaluation"));
347         error.setLine(line);
348         error.setUrl(QUrl::fromLocalFile(filename));
349         v8::Local<v8::Message> message = tc.Message();
350         if (!message.IsEmpty())
351             QQmlExpressionPrivate::exceptionToError(message, error);
352         ep->warning(error);
353         return v8::Persistent<v8::Function>();
354     }
355     if (qmlscope) *qmlscope = qPersistentNew<v8::Object>(scopeobject);
356     return qPersistentNew<v8::Function>(v8::Local<v8::Function>::Cast(result));
357 }
358
359 // Callee owns the persistent handle
360 v8::Persistent<v8::Function>
361 QQmlJavaScriptExpression::evalFunction(QQmlContextData *ctxt, QObject *scope,
362                                        const QString &code, const QString &filename, int line,
363                                        v8::Persistent<v8::Object> *qmlscope)
364 {
365     QQmlEngine *engine = ctxt->engine;
366     QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
367
368     v8::HandleScope handle_scope;
369     v8::Context::Scope ctxtscope(ep->v8engine()->context());
370
371     v8::TryCatch tc;
372     v8::Local<v8::Object> scopeobject = ep->v8engine()->qmlScope(ctxt, scope);
373     v8::Local<v8::Script> script = ep->v8engine()->qmlModeCompile(code, filename, line);
374     if (tc.HasCaught()) {
375         QQmlError error;
376         error.setDescription(QLatin1String("Exception occurred during function compilation"));
377         error.setLine(line);
378         error.setUrl(QUrl::fromLocalFile(filename));
379         v8::Local<v8::Message> message = tc.Message();
380         if (!message.IsEmpty())
381             QQmlExpressionPrivate::exceptionToError(message, error);
382         ep->warning(error);
383         return v8::Persistent<v8::Function>();
384     }
385     v8::Local<v8::Value> result = script->Run(scopeobject);
386     if (tc.HasCaught()) {
387         QQmlError error;
388         error.setDescription(QLatin1String("Exception occurred during function evaluation"));
389         error.setLine(line);
390         error.setUrl(QUrl::fromLocalFile(filename));
391         v8::Local<v8::Message> message = tc.Message();
392         if (!message.IsEmpty())
393             QQmlExpressionPrivate::exceptionToError(message, error);
394         ep->warning(error);
395         return v8::Persistent<v8::Function>();
396     }
397     if (qmlscope) *qmlscope = qPersistentNew<v8::Object>(scopeobject);
398     return qPersistentNew<v8::Function>(v8::Local<v8::Function>::Cast(result));
399 }
400
401 void QQmlJavaScriptExpression::clearGuards()
402 {
403     while (Guard *g = activeGuards.takeFirst())
404         g->Delete();
405 }
406
407 void QQmlJavaScriptExpressionGuard_callback(QQmlNotifierEndpoint *e, void **)
408 {
409     QQmlJavaScriptExpression *expression =
410         static_cast<QQmlJavaScriptExpressionGuard *>(e)->expression;
411
412     expression->m_vtable->expressionChanged(expression);
413 }
414
415 QT_END_NAMESPACE