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