Merge branch 'qtquick2' into v8
[profile/ivi/qtdeclarative.git] / src / declarative / qml / v8 / qv8contextwrapper.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qv8contextwrapper_p.h"
43 #include "qv8engine_p.h"
44
45 #include <private/qdeclarativeengine_p.h>
46 #include <private/qdeclarativecontext_p.h>
47
48 QT_BEGIN_NAMESPACE
49
50 static QString internal(QLatin1String("You've stumbled onto an internal implementation detail "
51                                       "that should never have been exposed."));
52
53 class QV8ContextResource : public QV8ObjectResource
54 {
55     V8_RESOURCE_TYPE(ContextType);
56
57 public:
58     QV8ContextResource(QV8Engine *engine, QDeclarativeContextData *context, QObject *scopeObject);
59     ~QV8ContextResource();
60
61     inline QDeclarativeContextData *getContext() const;
62
63     QDeclarativeGuard<QObject> scopeObject;
64
65     quint32 hasSubContexts:1;
66     quint32 ownsContext:1;
67     quint32 readOnly:1;
68     quint32 dummy:29;
69
70     QObject *secondaryScope;
71
72     // This is a pretty horrible hack, and an abuse of external strings.  When we create a 
73     // sub-context (a context created by a Qt.include() in an external javascript file),
74     // we pass a specially crafted SubContext external string as the v8::Script::Data() to
75     // the script, which contains a pointer to the context.  We can then access the 
76     // v8::Script::Data() later on to resolve names and URLs against the sub-context instead
77     // of the main outer context.
78     struct SubContext : public v8::String::ExternalStringResource {
79         SubContext(QDeclarativeContextData *context) : context(context) {}
80         QDeclarativeGuardedContextData context;
81
82         virtual const uint16_t* data() const { return (const uint16_t *)internal.constData(); }
83         virtual size_t length() const { return internal.length(); }
84     };
85
86 private:
87     QDeclarativeGuardedContextData context;
88 };
89
90 QV8ContextResource::QV8ContextResource(QV8Engine *engine, QDeclarativeContextData *context, QObject *scopeObject)
91 : QV8ObjectResource(engine), scopeObject(scopeObject), hasSubContexts(false), ownsContext(false),
92   readOnly(true), secondaryScope(0), context(context)
93 {
94 }
95
96 QV8ContextResource::~QV8ContextResource()
97 {
98     if (ownsContext && context)
99         context->destroy();
100 }
101
102 // Returns the context, including resolving a subcontext
103 QDeclarativeContextData *QV8ContextResource::getContext() const
104 {
105     if (!hasSubContexts)
106         return context;
107
108     v8::Local<v8::Value> callingdata = v8::Context::GetCallingScriptData();
109     if (callingdata.IsEmpty() || !callingdata->IsString())
110         return context;
111
112     v8::Local<v8::String> callingstring = callingdata->ToString();
113     Q_ASSERT(callingstring->IsExternal());
114     Q_ASSERT(callingstring->GetExternalStringResource());
115
116     SubContext *sc = static_cast<SubContext *>(callingstring->GetExternalStringResource());
117     return sc->context;
118 }
119
120 QV8ContextWrapper::QV8ContextWrapper()
121 : m_engine(0)
122 {
123 }
124
125 QV8ContextWrapper::~QV8ContextWrapper()
126 {
127 }
128
129 void QV8ContextWrapper::destroy()
130 {
131     qPersistentDispose(m_urlConstructor);
132     qPersistentDispose(m_constructor);
133 }
134
135 void QV8ContextWrapper::init(QV8Engine *engine)
136 {
137     m_engine = engine;
138     {
139     v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
140     ft->InstanceTemplate()->SetHasExternalResource(true);
141     ft->InstanceTemplate()->SetFallbackPropertyHandler(Getter, Setter);
142     m_constructor = qPersistentNew<v8::Function>(ft->GetFunction());
143     }
144     {
145     v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
146     ft->InstanceTemplate()->SetHasExternalResource(true);
147     ft->InstanceTemplate()->SetFallbackPropertyHandler(NullGetter, NullSetter);
148     m_urlConstructor = qPersistentNew<v8::Function>(ft->GetFunction());
149     }
150 }
151
152 v8::Local<v8::Object> QV8ContextWrapper::qmlScope(QDeclarativeContextData *ctxt, QObject *scope)
153 {
154     // XXX NewInstance() should be optimized
155     v8::Local<v8::Object> rv = m_constructor->NewInstance(); 
156     QV8ContextResource *r = new QV8ContextResource(m_engine, ctxt, scope);
157     rv->SetExternalResource(r);
158     return rv;
159 }
160
161 v8::Local<v8::Object> QV8ContextWrapper::urlScope(const QUrl &url)
162 {
163     QDeclarativeContextData *context = new QDeclarativeContextData;
164     context->url = url;
165     context->isInternal = true;
166     context->isJSContext = true;
167
168     // XXX NewInstance() should be optimized
169     v8::Local<v8::Object> rv = m_urlConstructor->NewInstance(); 
170     QV8ContextResource *r = new QV8ContextResource(m_engine, context, 0);
171     r->ownsContext = true;
172     rv->SetExternalResource(r);
173     return rv;
174 }
175
176 void QV8ContextWrapper::setReadOnly(v8::Handle<v8::Object> qmlglobal, bool readOnly)
177 {
178     QV8ContextResource *resource = v8_resource_cast<QV8ContextResource>(qmlglobal);
179     Q_ASSERT(resource);
180     resource->readOnly = readOnly;
181 }
182
183 void QV8ContextWrapper::addSubContext(v8::Handle<v8::Object> qmlglobal, v8::Handle<v8::Script> script, 
184                                       QDeclarativeContextData *ctxt)
185 {
186     QV8ContextResource *resource = v8_resource_cast<QV8ContextResource>(qmlglobal);
187     Q_ASSERT(resource);
188     resource->hasSubContexts = true;
189     script->SetData(v8::String::NewExternal(new QV8ContextResource::SubContext(ctxt)));
190 }
191
192 QObject *QV8ContextWrapper::setSecondaryScope(v8::Handle<v8::Object> ctxt, QObject *scope)
193 {
194     QV8ContextResource *resource = v8_resource_cast<QV8ContextResource>(ctxt);
195     if (!resource) return 0;
196
197     QObject *rv = resource->secondaryScope;
198     resource->secondaryScope = scope;
199     return rv;
200 }
201
202 QDeclarativeContextData *QV8ContextWrapper::callingContext()
203 {
204     v8::Local<v8::Object> qmlglobal = v8::Context::GetCallingQmlGlobal();
205     if (qmlglobal.IsEmpty()) return 0;
206
207     QV8ContextResource *r = v8_resource_cast<QV8ContextResource>(qmlglobal);
208     return r?r->getContext():0;
209 }
210
211 QDeclarativeContextData *QV8ContextWrapper::context(v8::Handle<v8::Value> value)
212 {
213     if (!value->IsObject())
214         return 0;
215
216     v8::Handle<v8::Object> qmlglobal = v8::Handle<v8::Object>::Cast(value);
217     QV8ContextResource *r = v8_resource_cast<QV8ContextResource>(qmlglobal);
218     return r?r->getContext():0;
219 }
220
221 v8::Handle<v8::Value> QV8ContextWrapper::NullGetter(v8::Local<v8::String> property, 
222                                                     const v8::AccessorInfo &info)
223 {
224     QV8ContextResource *resource = v8_resource_cast<QV8ContextResource>(info.This());
225
226     if (!resource)
227         return v8::Undefined();
228
229     QV8Engine *engine = resource->engine;
230
231     QString error = QLatin1String("Can't find variable: ") + engine->toString(property);
232     v8::ThrowException(v8::Exception::ReferenceError(engine->toString(error)));
233     return v8::Undefined();
234 }
235
236 v8::Handle<v8::Value> QV8ContextWrapper::Getter(v8::Local<v8::String> property, 
237                                                 const v8::AccessorInfo &info)
238 {
239     QV8ContextResource *resource = v8_resource_cast<QV8ContextResource>(info.This());
240
241     if (!resource)
242         return v8::Undefined();
243
244     // Its possible we could delay the calculation of the "actual" context (in the case
245     // of sub contexts) until it is definately needed.
246     QDeclarativeContextData *context = resource->getContext();
247
248     if (!context)
249         return v8::Undefined();
250
251     // Search type (attached property/enum/imported scripts) names
252     // Secondary scope object
253     // while (context) {
254     //     Search context properties
255     //     Search scope object
256     //     Search context object
257     //     context = context->parent
258     // }
259
260     QV8Engine *engine = resource->engine;
261     QObject *scopeObject = resource->scopeObject;
262
263     if (context->imports && QV8Engine::startsWithUpper(property)) {
264         // Search for attached properties, enums and imported scripts
265         QDeclarativeTypeNameCache::Data *data = context->imports->data(property);
266
267         if (data) {
268             if (data->importedScriptIndex != -1) {
269                 int index = data->importedScriptIndex;
270                 if (index < context->importedScripts.count())
271                     return context->importedScripts.at(index);
272                 else
273                     return v8::Undefined();
274             } else if (data->type) {
275                 return engine->typeWrapper()->newObject(scopeObject, data->type);
276             } else if (data->typeNamespace) {
277                 return engine->typeWrapper()->newObject(scopeObject, data->typeNamespace);
278             }
279             Q_ASSERT(!"Unreachable");
280         }
281
282         // Fall through
283     }
284
285     QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine->engine());
286     QV8QObjectWrapper *qobjectWrapper = engine->qobjectWrapper();
287
288     if (resource->secondaryScope) {
289         v8::Handle<v8::Value> result = qobjectWrapper->getProperty(resource->secondaryScope, property, 
290                                                                    QV8QObjectWrapper::IgnoreRevision);
291         if (!result.IsEmpty()) return result;
292     }
293
294     while (context) {
295         // Search context properties
296         if (context->propertyNames) {
297             int propertyIdx = context->propertyNames->value(property);
298
299             if (propertyIdx != -1) {
300                 typedef QDeclarativeEnginePrivate::CapturedProperty CapturedProperty;
301
302                 if (propertyIdx < context->idValueCount) {
303
304                     if (ep->captureProperties)
305                         ep->capturedProperties << CapturedProperty(&context->idValues[propertyIdx].bindings);
306
307                     return engine->newQObject(context->idValues[propertyIdx]);
308                 } else {
309
310                     QDeclarativeContextPrivate *cp = context->asQDeclarativeContextPrivate();
311
312                     if (ep->captureProperties)
313                         ep->capturedProperties << CapturedProperty(context->asQDeclarativeContext(), -1, 
314                                                                    propertyIdx + cp->notifyIndex);
315
316                     const QVariant &value = cp->propertyValues.at(propertyIdx);
317                     if (value.userType() == qMetaTypeId<QList<QObject*> >()) {
318                         QDeclarativeListProperty<QObject> prop(context->asQDeclarativeContext(), (void*)propertyIdx,
319                                                                0,
320                                                                QDeclarativeContextPrivate::context_count,
321                                                                QDeclarativeContextPrivate::context_at);
322                         return engine->listWrapper()->newList(prop, qMetaTypeId<QDeclarativeListProperty<QObject> >());
323                     } else {
324                         return engine->fromVariant(cp->propertyValues.at(propertyIdx));
325                     }
326                 }
327             }
328         }
329
330         // Search scope object
331         if (scopeObject) {
332             v8::Handle<v8::Value> result = qobjectWrapper->getProperty(scopeObject, property, 
333                                                                        QV8QObjectWrapper::CheckRevision);
334             if (!result.IsEmpty()) return result;
335         }
336         scopeObject = 0;
337
338
339         // Search context object
340         if (context->contextObject) {
341             v8::Handle<v8::Value> result = qobjectWrapper->getProperty(context->contextObject, property,
342                                                                        QV8QObjectWrapper::CheckRevision);
343             if (!result.IsEmpty()) return result;
344         }
345
346         context = context->parent;
347     }
348
349     QString error = QLatin1String("Can't find variable: ") + engine->toString(property);
350     v8::ThrowException(v8::Exception::ReferenceError(engine->toString(error)));
351     return v8::Undefined();
352 }
353
354 v8::Handle<v8::Value> QV8ContextWrapper::NullSetter(v8::Local<v8::String> property, 
355                                                     v8::Local<v8::Value>,
356                                                     const v8::AccessorInfo &info)
357 {
358     QV8ContextResource *resource = v8_resource_cast<QV8ContextResource>(info.This());
359
360     if (!resource)
361         return v8::Handle<v8::Value>();
362
363     QV8Engine *engine = resource->engine;
364
365     if (!resource->readOnly) {
366         return v8::Handle<v8::Value>();
367     } else {
368         QString error = QLatin1String("Invalid write to global property \"") + engine->toString(property) + 
369                         QLatin1String("\"");
370         v8::ThrowException(v8::Exception::Error(engine->toString(error)));
371         return v8::Handle<v8::Value>();
372     }
373 }
374
375 v8::Handle<v8::Value> QV8ContextWrapper::Setter(v8::Local<v8::String> property, 
376                                                 v8::Local<v8::Value> value,
377                                                 const v8::AccessorInfo &info)
378 {
379     QV8ContextResource *resource = v8_resource_cast<QV8ContextResource>(info.This());
380
381     if (!resource)
382         return v8::Undefined();
383
384     // Its possible we could delay the calculation of the "actual" context (in the case
385     // of sub contexts) until it is definately needed.
386     QDeclarativeContextData *context = resource->getContext();
387
388     if (!context)
389         return v8::Undefined();
390
391     // See QV8ContextWrapper::Getter for resolution order
392
393     QV8Engine *engine = resource->engine;
394     QObject *scopeObject = resource->scopeObject;
395
396     QV8QObjectWrapper *qobjectWrapper = engine->qobjectWrapper();
397
398     // Search scope object
399     if (resource->secondaryScope && qobjectWrapper->setProperty(resource->secondaryScope, property, value, 
400                                                                 QV8QObjectWrapper::IgnoreRevision))
401         return value;
402
403     while (context) {
404         // Search context properties
405         if (context->propertyNames && -1 != context->propertyNames->value(property))
406             return value;
407
408         // Search scope object
409         if (scopeObject && 
410             qobjectWrapper->setProperty(scopeObject, property, value, QV8QObjectWrapper::CheckRevision))
411             return value;
412         scopeObject = 0;
413
414         // Search context object
415         if (context->contextObject &&
416             qobjectWrapper->setProperty(context->contextObject, property, value, QV8QObjectWrapper::CheckRevision))
417             return value;
418
419         context = context->parent;
420     }
421
422     if (!resource->readOnly) {
423         return v8::Handle<v8::Value>();
424     } else {
425         QString error = QLatin1String("Invalid write to global property \"") + engine->toString(property) + 
426                         QLatin1String("\"");
427         v8::ThrowException(v8::Exception::Error(engine->toString(error)));
428         return v8::Undefined();
429     }
430 }
431
432 QT_END_NAMESPACE