1/*
2 * Copyright (C) 2008 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 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 "Error.h"
24#include "JSDOMBinding.h"
25#include "JSGlobalObject.h"
26#include "JSLock.h"
27#include "ObjectPrototype.h"
28#include "PropertyNameArray.h"
29#include "qt_class.h"
30#include "qt_runtime.h"
31#include "runtime_object.h"
32#include "runtime/FunctionPrototype.h"
33
34#include <qdebug.h>
35#include <qhash.h>
36#include <qmetaobject.h>
37#include <qmetatype.h>
38#include <qwebelement.h>
39
40namespace JSC {
41namespace Bindings {
42
43// Cache QtInstances
44typedef QMultiHash<void*, QtInstance*> QObjectInstanceMap;
45static QObjectInstanceMap cachedInstances;
46
47// Derived RuntimeObject
48class QtRuntimeObject : public RuntimeObject {
49public:
50    QtRuntimeObject(ExecState*, JSGlobalObject*, PassRefPtr<Instance>);
51
52    static const ClassInfo s_info;
53
54    virtual void markChildren(MarkStack& markStack)
55    {
56        RuntimeObject::markChildren(markStack);
57        QtInstance* instance = static_cast<QtInstance*>(getInternalInstance());
58        if (instance)
59            instance->markAggregate(markStack);
60    }
61
62    static Structure* createStructure(JSGlobalData& globalData, JSValue prototype)
63    {
64        return Structure::create(globalData, prototype, TypeInfo(ObjectType,  StructureFlags), AnonymousSlotCount, &s_info);
65    }
66
67protected:
68    static const unsigned StructureFlags = RuntimeObject::StructureFlags | OverridesMarkChildren;
69};
70
71const ClassInfo QtRuntimeObject::s_info = { "QtRuntimeObject", &RuntimeObject::s_info, 0, 0 };
72
73QtRuntimeObject::QtRuntimeObject(ExecState* exec, JSGlobalObject* globalObject, PassRefPtr<Instance> instance)
74    : RuntimeObject(exec, globalObject, WebCore::deprecatedGetDOMStructure<QtRuntimeObject>(exec), instance)
75{
76}
77
78// QtInstance
79QtInstance::QtInstance(QObject* o, PassRefPtr<RootObject> rootObject, QScriptEngine::ValueOwnership ownership)
80    : Instance(rootObject)
81    , m_class(0)
82    , m_object(o)
83    , m_hashkey(o)
84    , m_ownership(ownership)
85{
86    // This is a good place to register Qt metatypes that are in the QtWebKit module, as this is class will initialize if we have a QObject bridge.
87    qRegisterMetaType<QWebElement>();
88}
89
90QtInstance::~QtInstance()
91{
92    JSLock lock(SilenceAssertionsOnly);
93
94    cachedInstances.remove(m_hashkey);
95
96    // clean up (unprotect from gc) the JSValues we've created
97    m_methods.clear();
98
99    qDeleteAll(m_fields);
100    m_fields.clear();
101
102    if (m_object) {
103        switch (m_ownership) {
104        case QScriptEngine::QtOwnership:
105            break;
106        case QScriptEngine::AutoOwnership:
107            if (m_object->parent())
108                break;
109            // fall through!
110        case QScriptEngine::ScriptOwnership:
111            delete m_object;
112            break;
113        }
114    }
115}
116
117PassRefPtr<QtInstance> QtInstance::getQtInstance(QObject* o, PassRefPtr<RootObject> rootObject, QScriptEngine::ValueOwnership ownership)
118{
119    JSLock lock(SilenceAssertionsOnly);
120
121    foreach (QtInstance* instance, cachedInstances.values(o))
122        if (instance->rootObject() == rootObject) {
123            // The garbage collector removes instances, but it may happen that the wrapped
124            // QObject dies before the gc kicks in. To handle that case we have to do an additional
125            // check if to see if the instance's wrapped object is still alive. If it isn't, then
126            // we have to create a new wrapper.
127            if (!instance->getObject())
128                cachedInstances.remove(instance->hashKey());
129            else
130                return instance;
131        }
132
133    RefPtr<QtInstance> ret = QtInstance::create(o, rootObject, ownership);
134    cachedInstances.insert(o, ret.get());
135
136    return ret.release();
137}
138
139bool QtInstance::getOwnPropertySlot(JSObject* object, ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
140{
141    return object->JSObject::getOwnPropertySlot(exec, propertyName, slot);
142}
143
144void QtInstance::put(JSObject* object, ExecState* exec, const Identifier& propertyName, JSValue value, PutPropertySlot& slot)
145{
146    object->JSObject::put(exec, propertyName, value, slot);
147}
148
149void QtInstance::removeCachedMethod(JSObject* method)
150{
151    if (m_defaultMethod.get() == method)
152        m_defaultMethod.clear();
153
154    for (QHash<QByteArray, WriteBarrier<JSObject> >::Iterator it = m_methods.begin(), end = m_methods.end(); it != end; ++it) {
155        if (it.value().get() == method) {
156            m_methods.erase(it);
157            return;
158        }
159    }
160}
161
162QtInstance* QtInstance::getInstance(JSObject* object)
163{
164    if (!object)
165        return 0;
166    if (!object->inherits(&QtRuntimeObject::s_info))
167        return 0;
168    return static_cast<QtInstance*>(static_cast<RuntimeObject*>(object)->getInternalInstance());
169}
170
171Class* QtInstance::getClass() const
172{
173    if (!m_class) {
174        if (!m_object)
175            return 0;
176        m_class = QtClass::classForObject(m_object);
177    }
178    return m_class;
179}
180
181RuntimeObject* QtInstance::newRuntimeObject(ExecState* exec)
182{
183    JSLock lock(SilenceAssertionsOnly);
184    m_methods.clear();
185    return new (exec) QtRuntimeObject(exec, exec->lexicalGlobalObject(), this);
186}
187
188void QtInstance::markAggregate(MarkStack& markStack)
189{
190    if (m_defaultMethod)
191        markStack.append(&m_defaultMethod);
192    for (QHash<QByteArray, WriteBarrier<JSObject> >::Iterator it = m_methods.begin(), end = m_methods.end(); it != end; ++it)
193        markStack.append(&it.value());
194}
195
196void QtInstance::begin()
197{
198    // Do nothing.
199}
200
201void QtInstance::end()
202{
203    // Do nothing.
204}
205
206void QtInstance::getPropertyNames(ExecState* exec, PropertyNameArray& array)
207{
208    // This is the enumerable properties, so put:
209    // properties
210    // dynamic properties
211    // slots
212    QObject* obj = getObject();
213    if (obj) {
214        const QMetaObject* meta = obj->metaObject();
215
216        int i;
217        for (i = 0; i < meta->propertyCount(); i++) {
218            QMetaProperty prop = meta->property(i);
219            if (prop.isScriptable())
220                array.add(Identifier(exec, prop.name()));
221        }
222
223#ifndef QT_NO_PROPERTIES
224        QList<QByteArray> dynProps = obj->dynamicPropertyNames();
225        foreach (const QByteArray& ba, dynProps)
226            array.add(Identifier(exec, ba.constData()));
227#endif
228
229        const int methodCount = meta->methodCount();
230        for (i = 0; i < methodCount; i++) {
231            QMetaMethod method = meta->method(i);
232            if (method.access() != QMetaMethod::Private)
233                array.add(Identifier(exec, method.signature()));
234        }
235    }
236}
237
238JSValue QtInstance::getMethod(ExecState* exec, const Identifier& propertyName)
239{
240    if (!getClass())
241        return jsNull();
242    MethodList methodList = m_class->methodsNamed(propertyName, this);
243    return new (exec) RuntimeMethod(exec, exec->lexicalGlobalObject(), WebCore::deprecatedGetDOMStructure<RuntimeMethod>(exec), propertyName, methodList);
244}
245
246JSValue QtInstance::invokeMethod(ExecState*, RuntimeMethod*)
247{
248    // Implemented via fallbackMethod & QtRuntimeMetaMethod::callAsFunction
249    return jsUndefined();
250}
251
252JSValue QtInstance::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const
253{
254    if (hint == PreferString)
255        return stringValue(exec);
256    if (hint == PreferNumber)
257        return numberValue(exec);
258    return valueOf(exec);
259}
260
261JSValue QtInstance::stringValue(ExecState* exec) const
262{
263    QObject* obj = getObject();
264    if (!obj)
265        return jsNull();
266
267    // Hmm.. see if there is a toString defined
268    QByteArray buf;
269    bool useDefault = true;
270    getClass();
271    if (m_class) {
272        // Cheat and don't use the full name resolution
273        int index = obj->metaObject()->indexOfMethod("toString()");
274        if (index >= 0) {
275            QMetaMethod m = obj->metaObject()->method(index);
276            // Check to see how much we can call it
277            if (m.access() != QMetaMethod::Private
278                && m.methodType() != QMetaMethod::Signal
279                && m.parameterTypes().isEmpty()) {
280                const char* retsig = m.typeName();
281                if (retsig && *retsig) {
282                    QVariant ret(QMetaType::type(retsig), (void*)0);
283                    void * qargs[1];
284                    qargs[0] = ret.data();
285
286                    if (QMetaObject::metacall(obj, QMetaObject::InvokeMetaMethod, index, qargs) < 0) {
287                        if (ret.isValid() && ret.canConvert(QVariant::String)) {
288                            buf = ret.toString().toLatin1().constData(); // ### Latin 1? Ascii?
289                            useDefault = false;
290                        }
291                    }
292                }
293            }
294        }
295    }
296
297    if (useDefault) {
298        const QMetaObject* meta = obj ? obj->metaObject() : &QObject::staticMetaObject;
299        QString name = obj ? obj->objectName() : QString::fromUtf8("unnamed");
300        QString str = QString::fromUtf8("%0(name = \"%1\")")
301                      .arg(QLatin1String(meta->className())).arg(name);
302
303        buf = str.toLatin1();
304    }
305    return jsString(exec, buf.constData());
306}
307
308JSValue QtInstance::numberValue(ExecState*) const
309{
310    return jsNumber(0);
311}
312
313JSValue QtInstance::booleanValue() const
314{
315    // ECMA 9.2
316    return jsBoolean(getObject());
317}
318
319JSValue QtInstance::valueOf(ExecState* exec) const
320{
321    return stringValue(exec);
322}
323
324// In qt_runtime.cpp
325JSValue convertQVariantToValue(ExecState*, PassRefPtr<RootObject> root, const QVariant& variant);
326QVariant convertValueToQVariant(ExecState*, JSValue, QMetaType::Type hint, int *distance);
327
328QByteArray QtField::name() const
329{
330    if (m_type == MetaProperty)
331        return m_property.name();
332    if (m_type == ChildObject && m_childObject)
333        return m_childObject->objectName().toLatin1();
334#ifndef QT_NO_PROPERTIES
335    if (m_type == DynamicProperty)
336        return m_dynamicProperty;
337#endif
338    return QByteArray(); // deleted child object
339}
340
341JSValue QtField::valueFromInstance(ExecState* exec, const Instance* inst) const
342{
343    const QtInstance* instance = static_cast<const QtInstance*>(inst);
344    QObject* obj = instance->getObject();
345
346    if (obj) {
347        QVariant val;
348        if (m_type == MetaProperty) {
349            if (m_property.isReadable())
350                val = m_property.read(obj);
351            else
352                return jsUndefined();
353        } else if (m_type == ChildObject)
354            val = QVariant::fromValue((QObject*) m_childObject);
355#ifndef QT_NO_PROPERTIES
356        else if (m_type == DynamicProperty)
357            val = obj->property(m_dynamicProperty);
358#endif
359        return convertQVariantToValue(exec, inst->rootObject(), val);
360    }
361    QString msg = QString(QLatin1String("cannot access member `%1' of deleted QObject")).arg(QLatin1String(name()));
362    return throwError(exec, createError(exec, msg.toLatin1().constData()));
363}
364
365void QtField::setValueToInstance(ExecState* exec, const Instance* inst, JSValue aValue) const
366{
367    if (m_type == ChildObject) // QtScript doesn't allow setting to a named child
368        return;
369
370    const QtInstance* instance = static_cast<const QtInstance*>(inst);
371    QObject* obj = instance->getObject();
372    if (obj) {
373        QMetaType::Type argtype = QMetaType::Void;
374        if (m_type == MetaProperty)
375            argtype = (QMetaType::Type) QMetaType::type(m_property.typeName());
376
377        // dynamic properties just get any QVariant
378        QVariant val = convertValueToQVariant(exec, aValue, argtype, 0);
379        if (m_type == MetaProperty) {
380            if (m_property.isWritable())
381                m_property.write(obj, val);
382        }
383#ifndef QT_NO_PROPERTIES
384        else if (m_type == DynamicProperty)
385            obj->setProperty(m_dynamicProperty.constData(), val);
386#endif
387    } else {
388        QString msg = QString(QLatin1String("cannot access member `%1' of deleted QObject")).arg(QLatin1String(name()));
389        throwError(exec, createError(exec, msg.toLatin1().constData()));
390    }
391}
392
393
394}
395}
396