Reviewed by Darin.
[WebKit-https.git] / JavaScriptCore / bindings / objc / objc_runtime.mm
1 /*
2  * Copyright (C) 2004 Apple Computer, 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 #include "config.h"
26 #include <Foundation/Foundation.h>
27
28
29 #include <JavaScriptCore/internal.h>
30
31 #include <objc_instance.h>
32 #include <WebScriptObjectPrivate.h>
33
34 #include <runtime_array.h>
35 #include <runtime_object.h>
36
37
38 using namespace KJS;
39 using namespace KJS::Bindings;
40
41 // ---------------------- ObjcMethod ----------------------
42
43 ObjcMethod::ObjcMethod(ClassStructPtr aClass, const char *name)
44 {
45     _objcClass = aClass;
46     _selector = name;   // Assume ObjC runtime keeps these around forever.
47     _javaScriptName = 0;
48 }
49
50 const char *ObjcMethod::name() const
51 {
52     return (const char *)_selector;
53 }
54
55 int ObjcMethod::numParameters() const
56 {
57     return [getMethodSignature() numberOfArguments] - 2;
58 }
59
60
61 NSMethodSignature *ObjcMethod::getMethodSignature() const
62 {
63     return [(id)_objcClass instanceMethodSignatureForSelector:(SEL)_selector];
64 }
65
66 void ObjcMethod::setJavaScriptName (CFStringRef n)
67 {
68     if (n != _javaScriptName) {
69         if (_javaScriptName != 0)
70             CFRelease (_javaScriptName);
71         _javaScriptName = (CFStringRef)CFRetain (n);
72     }
73 }
74
75 // ---------------------- ObjcField ----------------------
76
77
78 ObjcField::ObjcField(Ivar ivar) 
79 {
80     _ivar = ivar;    // Assume ObjectiveC runtime will keep this alive forever
81     _name = 0;
82 }
83
84 ObjcField::ObjcField(CFStringRef name) 
85 {
86     _ivar = 0;
87     _name = (CFStringRef)CFRetain(name);
88 }
89
90 const char *ObjcField::name() const 
91 {
92     if (_ivar)
93         return _ivar->ivar_name;
94     return [(NSString *)_name UTF8String];
95 }
96
97 RuntimeType ObjcField::type() const 
98
99     if (_ivar)
100         return _ivar->ivar_type;
101     
102     // Type is irrelevant if we use KV to set/get the value.
103     return "";
104 }
105
106 JSValue *ObjcField::valueFromInstance(ExecState *exec, const Instance *instance) const
107 {
108     id targetObject = (static_cast<const ObjcInstance*>(instance))->getObject();
109
110     @try {
111     
112         NSString *key = [NSString stringWithCString:name()];
113         id objcValue = [targetObject valueForKey:key];
114         if (objcValue)
115             return convertObjcValueToValue (exec, &objcValue, ObjcObjectType);
116
117     } @catch(NSException *localException) {
118         
119         throwError(exec, GeneralError, [localException reason]);
120         
121     }
122
123     return jsUndefined();
124 }
125
126 static id convertValueToObjcObject (ExecState *exec, JSValue *value)
127 {
128     const Bindings::RootObject *root = rootForInterpreter(exec->interpreter());
129     if (!root) {
130         Bindings::RootObject *newRoot = new Bindings::RootObject(0);
131         newRoot->setInterpreter (exec->interpreter());
132         root = newRoot;
133     }
134     return [WebScriptObject _convertValueToObjcValue:value originExecutionContext:root executionContext:root ];
135 }
136
137
138 void ObjcField::setValueToInstance(ExecState *exec, const Instance *instance, JSValue *aValue) const
139 {
140     id targetObject = (static_cast<const ObjcInstance*>(instance))->getObject();
141     id value = convertValueToObjcObject(exec, aValue);
142     
143     @try {
144     
145         NSString *key = [NSString stringWithCString:name()];
146         [targetObject setValue:value forKey:key];
147
148     } @catch(NSException *localException) {
149         
150         throwError(exec, GeneralError, [localException reason]);
151         
152     }
153 }
154
155 // ---------------------- ObjcArray ----------------------
156
157 ObjcArray::ObjcArray (ObjectStructPtr a) 
158 {
159     _array = (id)CFRetain(a);
160 }
161
162 ObjcArray::~ObjcArray () 
163 {
164     CFRelease(_array);
165 }
166
167
168 ObjcArray::ObjcArray (const ObjcArray &other) : Array() 
169 {
170     _array = other._array;
171     CFRetain(_array);
172 }
173
174 ObjcArray &ObjcArray::operator=(const ObjcArray &other)
175 {
176     ObjectStructPtr _oldArray = _array;
177     _array = other._array;
178     CFRetain(_array);
179     CFRelease(_oldArray);
180     return *this;
181 }
182
183 void ObjcArray::setValueAt(ExecState *exec, unsigned int index, JSValue *aValue) const
184 {
185     if (![_array respondsToSelector:@selector(insertObject:atIndex:)]) {
186         throwError(exec, TypeError, "Array is not mutable.");
187         return;
188     }
189
190     if (index > [_array count]) {
191         throwError(exec, RangeError, "Index exceeds array size.");
192         return;
193     }
194     
195     // Always try to convert the value to an ObjC object, so it can be placed in the
196     // array.
197     ObjcValue oValue = convertValueToObjcValue (exec, aValue, ObjcObjectType);
198
199     @try {
200
201         [_array insertObject:oValue.objectValue atIndex:index];
202
203     } @catch(NSException *localException) {
204
205         throwError(exec, GeneralError, "Objective-C exception.");
206
207     }
208 }
209
210
211 JSValue *ObjcArray::valueAt(ExecState *exec, unsigned int index) const
212 {
213     if (index > [_array count])
214         return throwError(exec, RangeError, "Index exceeds array size.");
215
216     @try {
217
218         id obj = [_array objectAtIndex:index];
219         if (obj)
220             return convertObjcValueToValue (exec, &obj, ObjcObjectType);
221
222     } @catch(NSException *localException) {
223
224         return throwError(exec, GeneralError, "Objective-C exception.");
225
226     }
227
228     return jsUndefined();
229 }
230
231 unsigned int ObjcArray::getLength() const
232 {
233     return [_array count];
234 }
235
236
237 const ClassInfo ObjcFallbackObjectImp::info = {"ObjcFallbackObject", 0, 0, 0};
238
239 ObjcFallbackObjectImp::ObjcFallbackObjectImp(JSObject *proto)
240   : JSObject(proto)
241 {
242     _instance = 0;
243 }
244
245 ObjcFallbackObjectImp::ObjcFallbackObjectImp(ObjcInstance *i, const KJS::Identifier propertyName)
246 {
247     _instance = i;
248     _item = propertyName;
249 }
250
251 bool ObjcFallbackObjectImp::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot)
252 {
253     // keep the prototype from getting called instead of just returning false
254     slot.setUndefined(this);
255     return true;
256 }
257
258 void ObjcFallbackObjectImp::put(ExecState *exec, const Identifier &propertyName,
259                  JSValue *value, int attr)
260 {
261 }
262
263 bool ObjcFallbackObjectImp::canPut(ExecState *exec, const Identifier &propertyName) const
264 {
265     return false;
266 }
267
268
269 JSType ObjcFallbackObjectImp::type() const
270 {
271     id targetObject = _instance->getObject();
272     
273     if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
274         return ObjectType;
275     
276     return UndefinedType;
277 }
278
279 bool ObjcFallbackObjectImp::implementsCall() const
280 {
281     id targetObject = _instance->getObject();
282     
283     if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
284         return true;
285     
286     return false;
287 }
288
289 JSValue *ObjcFallbackObjectImp::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
290 {
291     JSValue *result = jsUndefined();
292     
293     RuntimeObjectImp *imp = static_cast<RuntimeObjectImp*>(thisObj);
294     if (imp) {
295         Instance *instance = imp->getInternalInstance();
296         
297         instance->begin();
298
299         ObjcInstance *objcInstance = static_cast<ObjcInstance*>(instance);
300         id targetObject = objcInstance->getObject();
301         
302         if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]){
303             MethodList methodList;
304             ObjcClass *objcClass = static_cast<ObjcClass*>(instance->getClass());
305             ObjcMethod *fallbackMethod = new ObjcMethod (objcClass->isa(), (const char *)@selector(invokeUndefinedMethodFromWebScript:withArguments:));
306             fallbackMethod->setJavaScriptName((CFStringRef)[NSString stringWithCString:_item.ascii()]);
307             methodList.addMethod ((Method *)fallbackMethod);
308             result = instance->invokeMethod(exec, methodList, args);
309             delete fallbackMethod;
310         }
311                 
312         instance->end();
313     }
314
315     return result;
316 }
317
318 bool ObjcFallbackObjectImp::deleteProperty(ExecState *exec,
319                             const Identifier &propertyName)
320 {
321     return false;
322 }
323
324 JSValue *ObjcFallbackObjectImp::defaultValue(ExecState *exec, JSType hint) const
325 {
326     return _instance->getValueOfUndefinedField(exec, _item, hint);
327 }
328
329 bool ObjcFallbackObjectImp::toBoolean(ExecState *exec) const
330 {
331     id targetObject = _instance->getObject();
332     
333     if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
334         return true;
335     
336     return false;
337 }