1/*
2 * Copyright (C) 2004, 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "objc_runtime.h"
28
29#include "JSDOMBinding.h"
30#include "ObjCRuntimeObject.h"
31#include "WebScriptObject.h"
32#include "objc_instance.h"
33#include "runtime_array.h"
34#include "runtime_object.h"
35#include <runtime/Error.h>
36#include <runtime/JSGlobalObject.h>
37#include <runtime/JSLock.h>
38#include <runtime/ObjectPrototype.h>
39#include <wtf/RetainPtr.h>
40
41using namespace WebCore;
42
43namespace JSC {
44namespace Bindings {
45
46ClassStructPtr webScriptObjectClass()
47{
48    static ClassStructPtr<WebScriptObject> webScriptObjectClass = NSClassFromString(@"WebScriptObject");
49    return webScriptObjectClass;
50}
51
52ClassStructPtr webUndefinedClass()
53{
54    static ClassStructPtr<WebUndefined> webUndefinedClass = NSClassFromString(@"WebUndefined");
55    return webUndefinedClass;
56}
57
58// ---------------------- ObjcMethod ----------------------
59
60ObjcMethod::ObjcMethod(ClassStructPtr aClass, SEL selector)
61    : _objcClass(aClass)
62    , _selector(selector)
63{
64}
65
66int ObjcMethod::numParameters() const
67{
68    return [getMethodSignature() numberOfArguments] - 2;
69}
70
71NSMethodSignature* ObjcMethod::getMethodSignature() const
72{
73    return [_objcClass instanceMethodSignatureForSelector:_selector];
74}
75
76// ---------------------- ObjcField ----------------------
77
78ObjcField::ObjcField(Ivar ivar)
79    : _ivar(ivar)
80#if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
81    , _name(AdoptCF, CFStringCreateWithCString(0, ivar_getName(_ivar), kCFStringEncodingASCII))
82#else
83    , _name(AdoptCF, CFStringCreateWithCString(0, _ivar->ivar_name, kCFStringEncodingASCII))
84#endif
85{
86}
87
88ObjcField::ObjcField(CFStringRef name)
89    : _ivar(0)
90    , _name(name)
91{
92}
93
94JSValue ObjcField::valueFromInstance(ExecState* exec, const Instance* instance) const
95{
96    JSValue result = jsUndefined();
97
98    id targetObject = (static_cast<const ObjcInstance*>(instance))->getObject();
99
100    JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly); // Can't put this inside the @try scope because it unwinds incorrectly.
101
102    @try {
103        if (id objcValue = [targetObject valueForKey:(NSString *)_name.get()])
104            result = convertObjcValueToValue(exec, &objcValue, ObjcObjectType, instance->rootObject());
105    } @catch(NSException* localException) {
106        JSLock::lock(SilenceAssertionsOnly);
107        throwError(exec, [localException reason]);
108        JSLock::unlock(SilenceAssertionsOnly);
109    }
110
111    // Work around problem in some versions of GCC where result gets marked volatile and
112    // it can't handle copying from a volatile to non-volatile.
113    return const_cast<JSValue&>(result);
114}
115
116static id convertValueToObjcObject(ExecState* exec, JSValue value)
117{
118    RefPtr<RootObject> rootObject = findRootObject(exec->dynamicGlobalObject());
119    if (!rootObject)
120        return nil;
121    return [webScriptObjectClass() _convertValueToObjcValue:value originRootObject:rootObject.get() rootObject:rootObject.get()];
122}
123
124void ObjcField::setValueToInstance(ExecState* exec, const Instance* instance, JSValue aValue) const
125{
126    id targetObject = (static_cast<const ObjcInstance*>(instance))->getObject();
127    id value = convertValueToObjcObject(exec, aValue);
128
129    JSLock::DropAllLocks dropAllLocks(SilenceAssertionsOnly); // Can't put this inside the @try scope because it unwinds incorrectly.
130
131    @try {
132        [targetObject setValue:value forKey:(NSString *)_name.get()];
133    } @catch(NSException* localException) {
134        JSLock::lock(SilenceAssertionsOnly);
135        throwError(exec, [localException reason]);
136        JSLock::unlock(SilenceAssertionsOnly);
137    }
138}
139
140// ---------------------- ObjcArray ----------------------
141
142ObjcArray::ObjcArray(ObjectStructPtr a, PassRefPtr<RootObject> rootObject)
143    : Array(rootObject)
144    , _array(a)
145{
146}
147
148void ObjcArray::setValueAt(ExecState* exec, unsigned int index, JSValue aValue) const
149{
150    if (![_array.get() respondsToSelector:@selector(insertObject:atIndex:)]) {
151        throwError(exec, createTypeError(exec, "Array is not mutable."));
152        return;
153    }
154
155    if (index > [_array.get() count]) {
156        throwError(exec, createRangeError(exec, "Index exceeds array size."));
157        return;
158    }
159
160    // Always try to convert the value to an ObjC object, so it can be placed in the
161    // array.
162    ObjcValue oValue = convertValueToObjcValue (exec, aValue, ObjcObjectType);
163
164    @try {
165        [_array.get() insertObject:oValue.objectValue atIndex:index];
166    } @catch(NSException* localException) {
167        throwError(exec, createError(exec, "Objective-C exception."));
168    }
169}
170
171JSValue ObjcArray::valueAt(ExecState* exec, unsigned int index) const
172{
173    if (index > [_array.get() count])
174        return throwError(exec, createRangeError(exec, "Index exceeds array size."));
175    @try {
176        id obj = [_array.get() objectAtIndex:index];
177        if (obj)
178            return convertObjcValueToValue (exec, &obj, ObjcObjectType, m_rootObject.get());
179    } @catch(NSException* localException) {
180        return throwError(exec, createError(exec, "Objective-C exception."));
181    }
182    return jsUndefined();
183}
184
185unsigned int ObjcArray::getLength() const
186{
187    return [_array.get() count];
188}
189
190const ClassInfo ObjcFallbackObjectImp::s_info = { "ObjcFallbackObject", &JSObjectWithGlobalObject::s_info, 0, 0 };
191
192ObjcFallbackObjectImp::ObjcFallbackObjectImp(ExecState* exec, JSGlobalObject* globalObject, ObjcInstance* i, const Identifier& propertyName)
193    // FIXME: deprecatedGetDOMStructure uses the prototype off of the wrong global object
194    : JSObjectWithGlobalObject(globalObject, deprecatedGetDOMStructure<ObjcFallbackObjectImp>(exec))
195    , _instance(i)
196    , _item(propertyName)
197{
198    ASSERT(inherits(&s_info));
199}
200
201bool ObjcFallbackObjectImp::getOwnPropertySlot(ExecState*, const Identifier&, PropertySlot& slot)
202{
203    // keep the prototype from getting called instead of just returning false
204    slot.setUndefined();
205    return true;
206}
207
208bool ObjcFallbackObjectImp::getOwnPropertyDescriptor(ExecState*, const Identifier&, PropertyDescriptor& descriptor)
209{
210    // keep the prototype from getting called instead of just returning false
211    descriptor.setUndefined();
212    return true;
213}
214
215void ObjcFallbackObjectImp::put(ExecState*, const Identifier&, JSValue, PutPropertySlot&)
216{
217}
218
219static EncodedJSValue JSC_HOST_CALL callObjCFallbackObject(ExecState* exec)
220{
221    JSValue thisValue = exec->hostThisValue();
222    if (!thisValue.inherits(&ObjCRuntimeObject::s_info))
223        return throwVMTypeError(exec);
224
225    JSValue result = jsUndefined();
226
227    ObjCRuntimeObject* runtimeObject = static_cast<ObjCRuntimeObject*>(asObject(thisValue));
228    ObjcInstance* objcInstance = runtimeObject->getInternalObjCInstance();
229
230    if (!objcInstance)
231        return JSValue::encode(RuntimeObject::throwInvalidAccessError(exec));
232
233    objcInstance->begin();
234
235    id targetObject = objcInstance->getObject();
236
237    if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]){
238        ObjcClass* objcClass = static_cast<ObjcClass*>(objcInstance->getClass());
239        OwnPtr<ObjcMethod> fallbackMethod(new ObjcMethod(objcClass->isa(), @selector(invokeUndefinedMethodFromWebScript:withArguments:)));
240        const Identifier& nameIdentifier = static_cast<ObjcFallbackObjectImp*>(exec->callee())->propertyName();
241        RetainPtr<CFStringRef> name(AdoptCF, CFStringCreateWithCharacters(0, nameIdentifier.characters(), nameIdentifier.length()));
242        fallbackMethod->setJavaScriptName(name.get());
243        result = objcInstance->invokeObjcMethod(exec, fallbackMethod.get());
244    }
245
246    objcInstance->end();
247
248    return JSValue::encode(result);
249}
250
251CallType ObjcFallbackObjectImp::getCallData(CallData& callData)
252{
253    id targetObject = _instance->getObject();
254    if (![targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
255        return CallTypeNone;
256    callData.native.function = callObjCFallbackObject;
257    return CallTypeHost;
258}
259
260bool ObjcFallbackObjectImp::deleteProperty(ExecState*, const Identifier&)
261{
262    return false;
263}
264
265JSValue ObjcFallbackObjectImp::defaultValue(ExecState* exec, PreferredPrimitiveType) const
266{
267    return _instance->getValueOfUndefinedField(exec, _item);
268}
269
270bool ObjcFallbackObjectImp::toBoolean(ExecState *) const
271{
272    id targetObject = _instance->getObject();
273
274    if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
275        return true;
276
277    return false;
278}
279
280}
281}
282