1/*
2    Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
3
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18*/
19
20#ifndef qscriptoriginalglobalobject_p_h
21#define qscriptoriginalglobalobject_p_h
22
23#include <JavaScriptCore/JavaScript.h>
24#include <JavaScriptCore/JSRetainPtr.h>
25#include <QtCore/qvector.h>
26
27/*!
28    \internal
29    This class is a workaround for missing JSC C API functionality. This class keeps all important
30    properties of an original (default) global object, so we can use it even if the global object was
31    changed.
32
33    FIXME this class is a container for workarounds :-) it should be replaced by proper JSC C API calls.
34
35    The class have to be created on the QScriptEnginePrivate creation time (before any change got applied to
36    global object).
37*/
38class QScriptOriginalGlobalObject {
39public:
40    inline QScriptOriginalGlobalObject(JSGlobalContextRef context);
41    inline ~QScriptOriginalGlobalObject();
42
43    inline bool objectHasOwnProperty(JSObjectRef object, JSStringRef property) const;
44    inline QVector<JSStringRef> objectGetOwnPropertyNames(JSObjectRef object) const;
45
46    inline bool isDate(JSValueRef value) const;
47    inline bool isArray(JSValueRef value) const;
48    inline bool isError(JSValueRef value) const;
49
50    inline JSValueRef functionPrototype() const;
51private:
52    inline bool isType(JSValueRef value, JSObjectRef constructor, JSValueRef prototype) const;
53    inline void initializeMember(JSObjectRef globalObject, JSStringRef prototypeName, const char* type, JSObjectRef& constructor, JSValueRef& prototype);
54
55    // Copy of the global context reference (the same as in QScriptEnginePrivate).
56    JSGlobalContextRef m_context;
57
58    // Copy of constructors and prototypes used in isType functions.
59    JSObjectRef m_arrayConstructor;
60    JSValueRef m_arrayPrototype;
61    JSObjectRef m_errorConstructor;
62    JSValueRef m_errorPrototype;
63    JSObjectRef m_functionConstructor;
64    JSValueRef m_functionPrototype;
65    JSObjectRef m_dateConstructor;
66    JSValueRef m_datePrototype;
67
68    // Reference to standard JS functions that are not exposed by JSC C API.
69    JSObjectRef m_hasOwnPropertyFunction;
70    JSObjectRef m_getOwnPropertyNamesFunction;
71};
72
73QScriptOriginalGlobalObject::QScriptOriginalGlobalObject(JSGlobalContextRef context)
74    : m_context(JSGlobalContextRetain(context))
75{
76    JSObjectRef globalObject = JSContextGetGlobalObject(m_context);
77    JSValueRef exception = 0;
78    JSRetainPtr<JSStringRef> propertyName;
79
80    propertyName.adopt(JSStringCreateWithUTF8CString("prototype"));
81    initializeMember(globalObject, propertyName.get(), "Array", m_arrayConstructor, m_arrayPrototype);
82    initializeMember(globalObject, propertyName.get(), "Error", m_errorConstructor, m_errorPrototype);
83    initializeMember(globalObject, propertyName.get(), "Function", m_functionConstructor, m_functionPrototype);
84    initializeMember(globalObject, propertyName.get(), "Date", m_dateConstructor, m_datePrototype);
85
86    propertyName.adopt(JSStringCreateWithUTF8CString("hasOwnProperty"));
87    m_hasOwnPropertyFunction = const_cast<JSObjectRef>(JSObjectGetProperty(m_context, globalObject, propertyName.get(), &exception));
88    JSValueProtect(m_context, m_hasOwnPropertyFunction);
89    Q_ASSERT(JSValueIsObject(m_context, m_hasOwnPropertyFunction));
90    Q_ASSERT(JSObjectIsFunction(m_context, m_hasOwnPropertyFunction));
91    Q_ASSERT(!exception);
92
93    propertyName.adopt(JSStringCreateWithUTF8CString("Object"));
94    JSObjectRef objectConstructor
95            = const_cast<JSObjectRef>(JSObjectGetProperty(m_context, globalObject, propertyName.get(), &exception));
96    propertyName.adopt(JSStringCreateWithUTF8CString("getOwnPropertyNames"));
97    m_getOwnPropertyNamesFunction
98            = const_cast<JSObjectRef>(JSObjectGetProperty(m_context, objectConstructor, propertyName.get(), &exception));
99    JSValueProtect(m_context, m_getOwnPropertyNamesFunction);
100    Q_ASSERT(JSValueIsObject(m_context, m_getOwnPropertyNamesFunction));
101    Q_ASSERT(JSObjectIsFunction(m_context, m_getOwnPropertyNamesFunction));
102    Q_ASSERT(!exception);
103}
104
105inline void QScriptOriginalGlobalObject::initializeMember(JSObjectRef globalObject, JSStringRef prototypeName, const char* type, JSObjectRef& constructor, JSValueRef& prototype)
106{
107    JSRetainPtr<JSStringRef> typeName(Adopt, JSStringCreateWithUTF8CString(type));
108    JSValueRef exception = 0;
109
110    // Save references to the Type constructor and prototype.
111    JSValueRef typeConstructor = JSObjectGetProperty(m_context, globalObject, typeName.get(), &exception);
112    Q_ASSERT(JSValueIsObject(m_context, typeConstructor));
113    constructor = JSValueToObject(m_context, typeConstructor, &exception);
114    JSValueProtect(m_context, constructor);
115
116    // Note that this is not the [[Prototype]] internal property (which we could
117    // get via JSObjectGetPrototype), but the Type.prototype, that will be set
118    // as [[Prototype]] of Type instances.
119    prototype = JSObjectGetProperty(m_context, constructor, prototypeName, &exception);
120    Q_ASSERT(JSValueIsObject(m_context, prototype));
121    JSValueProtect(m_context, prototype);
122    Q_ASSERT(!exception);
123}
124
125QScriptOriginalGlobalObject::~QScriptOriginalGlobalObject()
126{
127    JSValueUnprotect(m_context, m_arrayConstructor);
128    JSValueUnprotect(m_context, m_arrayPrototype);
129    JSValueUnprotect(m_context, m_errorConstructor);
130    JSValueUnprotect(m_context, m_errorPrototype);
131    JSValueUnprotect(m_context, m_functionConstructor);
132    JSValueUnprotect(m_context, m_functionPrototype);
133    JSValueUnprotect(m_context, m_dateConstructor);
134    JSValueUnprotect(m_context, m_datePrototype);
135    JSValueUnprotect(m_context, m_hasOwnPropertyFunction);
136    JSValueUnprotect(m_context, m_getOwnPropertyNamesFunction);
137    JSGlobalContextRelease(m_context);
138}
139
140inline bool QScriptOriginalGlobalObject::objectHasOwnProperty(JSObjectRef object, JSStringRef property) const
141{
142    // FIXME This function should be replaced by JSC C API.
143    JSValueRef exception = 0;
144    JSValueRef propertyName[] = { JSValueMakeString(m_context, property) };
145    JSValueRef result = JSObjectCallAsFunction(m_context, m_hasOwnPropertyFunction, object, 1, propertyName, &exception);
146    return exception ? false : JSValueToBoolean(m_context, result);
147}
148
149/*!
150    \internal
151    This method gives ownership of all JSStringRefs.
152*/
153inline QVector<JSStringRef> QScriptOriginalGlobalObject::objectGetOwnPropertyNames(JSObjectRef object) const
154{
155    JSValueRef exception = 0;
156    JSObjectRef propertyNames
157            = const_cast<JSObjectRef>(JSObjectCallAsFunction(m_context,
158                                                            m_getOwnPropertyNamesFunction,
159                                                            /* thisObject */ 0,
160                                                            /* argumentCount */ 1,
161                                                            &object,
162                                                            &exception));
163    Q_ASSERT(JSValueIsObject(m_context, propertyNames));
164    Q_ASSERT(!exception);
165    JSStringRef lengthName = QScriptConverter::toString("length");
166    int count = JSValueToNumber(m_context, JSObjectGetProperty(m_context, propertyNames, lengthName, &exception), &exception);
167
168    Q_ASSERT(!exception);
169    QVector<JSStringRef> names;
170    names.reserve(count);
171    for (int i = 0; i < count; ++i) {
172        JSValueRef tmp = JSObjectGetPropertyAtIndex(m_context, propertyNames, i, &exception);
173        names.append(JSValueToStringCopy(m_context, tmp, &exception));
174        Q_ASSERT(!exception);
175    }
176    return names;
177}
178
179inline bool QScriptOriginalGlobalObject::isDate(JSValueRef value) const
180{
181    return isType(value, m_dateConstructor, m_datePrototype);
182}
183
184inline bool QScriptOriginalGlobalObject::isArray(JSValueRef value) const
185{
186    return isType(value, m_arrayConstructor, m_arrayPrototype);
187}
188
189inline bool QScriptOriginalGlobalObject::isError(JSValueRef value) const
190{
191    return isType(value, m_errorConstructor, m_errorPrototype);
192}
193
194inline JSValueRef QScriptOriginalGlobalObject::functionPrototype() const
195{
196    return m_functionPrototype;
197}
198
199inline bool QScriptOriginalGlobalObject::isType(JSValueRef value, JSObjectRef constructor, JSValueRef prototype) const
200{
201    // JSC API doesn't export the [[Class]] information for the builtins. But we know that a value
202    // is an object of the Type if it was created with the Type constructor or if it is the Type.prototype.
203    JSValueRef exception = 0;
204    bool result = JSValueIsInstanceOfConstructor(m_context, value, constructor, &exception) || JSValueIsStrictEqual(m_context, value, prototype);
205    Q_ASSERT(!exception);
206    return result;
207}
208
209#endif // qscriptoriginalglobalobject_p_h
210