Reviewed by Geoff and Sam.
[WebKit-https.git] / WebCore / bridge / qt / qt_instance.cpp
1 /*
2  * Copyright (C) 2006 Trolltech ASA
3  *
4  *  This library is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU Lesser 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  *  Lesser General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Lesser General Public
15  *  License along with this library; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  */
19
20 #include "config.h"
21 #include "qt_instance.h"
22
23 #include "JSGlobalObject.h"
24 #include "list.h"
25 #include "qt_class.h"
26 #include "qt_runtime.h"
27 #include "PropertyNameArray.h"
28 #include "runtime_object.h"
29 #include "object_object.h"
30
31 #include <qmetaobject.h>
32 #include <qdebug.h>
33 #include <qmetatype.h>
34 #include <qhash.h>
35
36 namespace KJS {
37 namespace Bindings {
38
39 // Cache QtInstances
40 typedef QMultiHash<void*, QtInstance*> QObjectInstanceMap;
41 static QObjectInstanceMap cachedInstances;
42
43 // Cache JSObjects
44 typedef QHash<QtInstance*, JSObject*> InstanceJSObjectMap;
45 static InstanceJSObjectMap cachedObjects;
46
47 // Derived RuntimeObject
48 class QtRuntimeObjectImp : public RuntimeObjectImp {
49     public:
50         QtRuntimeObjectImp(PassRefPtr<Instance>);
51         ~QtRuntimeObjectImp();
52         virtual void invalidate();
53
54         // Additions
55         virtual bool implementsConstruct() const {return implementsCall();}
56         virtual JSObject* construct(ExecState* exec, const List& args);
57     protected:
58         void removeFromCache();
59 };
60
61 QtRuntimeObjectImp::QtRuntimeObjectImp(PassRefPtr<Instance> instance)
62     : RuntimeObjectImp(instance)
63 {
64 }
65
66 QtRuntimeObjectImp::~QtRuntimeObjectImp()
67 {
68     removeFromCache();
69 }
70
71 void QtRuntimeObjectImp::invalidate()
72 {
73     removeFromCache();
74     RuntimeObjectImp::invalidate();
75 }
76
77 void QtRuntimeObjectImp::removeFromCache()
78 {
79     JSLock lock;
80     QtInstance* key = cachedObjects.key(this);
81     if (key)
82         cachedObjects.remove(key);
83 }
84
85 JSObject* QtRuntimeObjectImp::construct(ExecState* exec, const List& args)
86 {
87     // ECMA 15.2.2.1 (?)
88     JSValue *val = callAsFunction(exec, this, args);
89
90     if (!val || val->type() == NullType || val->type() == UndefinedType)
91         return new JSObject(exec->lexicalGlobalObject()->objectPrototype());
92     else
93         return val->toObject(exec);
94 }
95
96 // QtInstance
97 QtInstance::QtInstance(QObject* o, PassRefPtr<RootObject> rootObject)
98     : Instance(rootObject)
99     , m_class(0)
100     , m_object(o)
101     , m_hashkey(o)
102     , m_defaultMethod(0)
103     , m_defaultMethodIndex(-2)
104 {
105 }
106
107 QtInstance::~QtInstance()
108 {
109     JSLock lock;
110
111     cachedObjects.remove(this);
112     cachedInstances.remove(m_hashkey);
113
114     // clean up (unprotect from gc) the JSValues we've created
115     foreach(JSValue* val, m_methods.values()) {
116         gcUnprotect(val);
117     }
118     m_methods.clear();
119
120     foreach(QtField* f, m_fields.values()) {
121         delete f;
122     }
123     m_fields.clear();
124
125     if (m_defaultMethod)
126         gcUnprotect(m_defaultMethod);
127 }
128
129 PassRefPtr<QtInstance> QtInstance::getQtInstance(QObject* o, PassRefPtr<RootObject> rootObject)
130 {
131     JSLock lock;
132
133     foreach(QtInstance* instance, cachedInstances.values(o)) {
134         if (instance->rootObject() == rootObject)
135             return instance.get();
136     }
137
138     RefPtr<QtInstance> ret = adoptRef(new QtInstance(o, rootObject));
139     cachedInstances.insert(o, ret.get());
140
141     return ret.release();
142 }
143
144 JSObject* QtInstance::getRuntimeObject(PassRefPtr<QtInstance> instance)
145 {
146     JSLock lock;
147     JSObject* ret = cachedObjects.value(instance.get());
148     if (!ret) {
149         ret = new QtRuntimeObjectImp(instance);
150         cachedObjects.insert(ret->getInternalInstance(), ret);
151     }
152     return ret;
153 }
154
155 Class* QtInstance::getClass() const
156 {
157     if (!m_class)
158         m_class = QtClass::classForObject(m_object);
159     return m_class;
160 }
161
162 void QtInstance::begin()
163 {
164     // Do nothing.
165 }
166
167 void QtInstance::end()
168 {
169     // Do nothing.
170 }
171
172 void QtInstance::getPropertyNames(ExecState* , PropertyNameArray& array)
173 {
174     // This is the enumerable properties, so put:
175     // properties
176     // dynamic properties
177     // slots
178     QObject* obj = getObject();
179     if (obj) {
180         const QMetaObject* meta = obj->metaObject();
181
182         int i;
183         for (i=0; i < meta->propertyCount(); i++) {
184             QMetaProperty prop = meta->property(i);
185             if (prop.isScriptable()) {
186                 array.add(Identifier(prop.name()));
187             }
188         }
189
190         QList<QByteArray> dynProps = obj->dynamicPropertyNames();
191         foreach(QByteArray ba, dynProps) {
192             array.add(Identifier(ba.constData()));
193         }
194
195         for (i=0; i < meta->methodCount(); i++) {
196             QMetaMethod method = meta->method(i);
197             if (method.access() != QMetaMethod::Private) {
198                 array.add(Identifier(method.signature()));
199             }
200         }
201     }
202 }
203
204 JSValue* QtInstance::invokeMethod(ExecState*, const MethodList&, const List&)
205 {
206     // Implemented via fallbackMethod & QtRuntimeMetaMethod::callAsFunction
207     return jsUndefined();
208 }
209
210 bool QtInstance::implementsCall() const
211 {
212     // See if we have qscript_call
213     if (m_defaultMethodIndex == -2) {
214         if (m_object) {
215             const QMetaObject* meta = m_object->metaObject();
216             int count = meta->methodCount();
217             const QByteArray defsig("qscript_call");
218             for (int index = count - 1; index >= 0; --index) {
219                 const QMetaMethod m = meta->method(index);
220
221                 QByteArray signature = m.signature();
222                 signature.truncate(signature.indexOf('('));
223
224                 if (defsig == signature) {
225                     m_defaultMethodIndex = index;
226                     break;
227                 }
228             }
229         }
230
231         if (m_defaultMethodIndex == -2) // Not checked
232             m_defaultMethodIndex = -1; // No qscript_call
233     }
234
235     // typeof object that implements call == function
236     return (m_defaultMethodIndex >= 0);
237 }
238
239 JSValue* QtInstance::invokeDefaultMethod(ExecState* exec, const List& args)
240 {
241     // QtScript tries to invoke a meta method qscript_call
242     if (!getObject())
243         return throwError(exec, GeneralError, "cannot call function of deleted QObject");
244
245     // implementsCall will update our default method cache, if possible
246     if (implementsCall()) {
247         if (!m_defaultMethod) {
248             m_defaultMethod = new QtRuntimeMetaMethod(exec, Identifier("[[Call]]"),this, m_defaultMethodIndex, QByteArray("qscript_call"), true);
249             gcProtect(m_defaultMethod);
250         }
251
252         return m_defaultMethod->callAsFunction(exec, 0, args); // Luckily QtRuntimeMetaMethod ignores the obj parameter
253     } else
254         return throwError(exec, TypeError, "not a function");
255 }
256
257 JSValue* QtInstance::defaultValue(JSType hint) const
258 {
259     if (hint == StringType)
260         return stringValue();
261     if (hint == NumberType)
262         return numberValue();
263     if (hint == BooleanType)
264         return booleanValue();
265     return valueOf();
266 }
267
268 JSValue* QtInstance::stringValue() const
269 {
270     // Hmm.. see if there is a toString defined
271     QByteArray buf;
272     bool useDefault = true;
273     getClass();
274     QObject* obj = getObject();
275     if (m_class && obj) {
276         // Cheat and don't use the full name resolution
277         int index = obj->metaObject()->indexOfMethod("toString()");
278         if (index >= 0) {
279             QMetaMethod m = obj->metaObject()->method(index);
280             // Check to see how much we can call it
281             if (m.access() != QMetaMethod::Private
282                 && m.methodType() != QMetaMethod::Signal
283                 && m.parameterTypes().count() == 0) {
284                 const char* retsig = m.typeName();
285                 if (retsig && *retsig) {
286                     QVariant ret(QMetaType::type(retsig), (void*)0);
287                     void * qargs[1];
288                     qargs[0] = ret.data();
289
290                     if (obj->qt_metacall(QMetaObject::InvokeMetaMethod, index, qargs) < 0) {
291                         if (ret.isValid() && ret.canConvert(QVariant::String)) {
292                             buf = ret.toString().toLatin1().constData(); // ### Latin 1? Ascii?
293                             useDefault = false;
294                         }
295                     }
296                 }
297             }
298         }
299     }
300
301     if (useDefault) {
302         const QMetaObject* meta = obj ? obj->metaObject() : &QObject::staticMetaObject;
303         QString name = obj ? obj->objectName() : QString::fromUtf8("unnamed");
304         QString str = QString::fromUtf8("%0(name = \"%1\")")
305                       .arg(QLatin1String(meta->className())).arg(name);
306
307         buf = str.toLatin1();
308     }
309     return jsString(buf.constData());
310 }
311
312 JSValue* QtInstance::numberValue() const
313 {
314     return jsNumber(0);
315 }
316
317 JSValue* QtInstance::booleanValue() const
318 {
319     // ECMA 9.2
320     return jsBoolean(true);
321 }
322
323 JSValue* QtInstance::valueOf() const
324 {
325     return stringValue();
326 }
327
328 // In qt_runtime.cpp
329 JSValue* convertQVariantToValue(ExecState* exec, PassRefPtr<RootObject> root, const QVariant& variant);
330 QVariant convertValueToQVariant(ExecState* exec, JSValue* value, QMetaType::Type hint, int *distance);
331
332 const char* QtField::name() const
333 {
334     if (m_type == MetaProperty)
335         return m_property.name();
336     else if (m_type == ChildObject && m_childObject)
337         return m_childObject->objectName().toLatin1();
338     else if (m_type == DynamicProperty)
339         return m_dynamicProperty.constData();
340     return ""; // deleted child object
341 }
342
343 JSValue* QtField::valueFromInstance(ExecState* exec, const Instance* inst) const
344 {
345     const QtInstance* instance = static_cast<const QtInstance*>(inst);
346     QObject* obj = instance->getObject();
347
348     if (obj) {
349         QVariant val;
350         if (m_type == MetaProperty) {
351             if (m_property.isReadable())
352                 val = m_property.read(obj);
353             else
354                 return jsUndefined();
355         } else if (m_type == ChildObject)
356             val = QVariant::fromValue((QObject*) m_childObject);
357         else if (m_type == DynamicProperty)
358             val = obj->property(m_dynamicProperty);
359
360         return convertQVariantToValue(exec, inst->rootObject(), val);
361     } else {
362         QString msg = QString("cannot access member `%1' of deleted QObject").arg(name());
363         return throwError(exec, GeneralError, msg.toLatin1().constData());
364     }
365 }
366
367 void QtField::setValueToInstance(ExecState* exec, const Instance* inst, JSValue* aValue) const
368 {
369     if (m_type == ChildObject) // QtScript doesn't allow setting to a named child
370         return;
371
372     const QtInstance* instance = static_cast<const QtInstance*>(inst);
373     QObject* obj = instance->getObject();
374     if (obj) {
375         QMetaType::Type argtype = QMetaType::Void;
376         if (m_type == MetaProperty)
377             argtype = (QMetaType::Type) QMetaType::type(m_property.typeName());
378
379         // dynamic properties just get any QVariant
380         QVariant val = convertValueToQVariant(exec, aValue, argtype, 0);
381         if (m_type == MetaProperty) {
382             if (m_property.isWritable())
383                 m_property.write(obj, val);
384         } else if (m_type == DynamicProperty)
385             obj->setProperty(m_dynamicProperty.constData(), val);
386     } else {
387         QString msg = QString("cannot access member `%1' of deleted QObject").arg(name());
388         throwError(exec, GeneralError, msg.toLatin1().constData());
389     }
390 }
391
392
393 }
394 }