2011-01-07 Adam Barth <abarth@webkit.org>
[WebKit-https.git] / WebCore / bridge / objc / objc_class.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
26 #include "config.h"
27 #include "objc_class.h"
28
29 #include "objc_instance.h"
30 #include "WebScriptObject.h"
31
32 namespace JSC {
33 namespace Bindings {
34     
35 static void deleteMethod(CFAllocatorRef, const void* value)
36 {
37     delete static_cast<const Method*>(value);
38 }
39     
40 static void deleteField(CFAllocatorRef, const void* value)
41 {
42     delete static_cast<const Field*>(value);
43 }
44
45 const CFDictionaryValueCallBacks MethodDictionaryValueCallBacks = { 0, 0, &deleteMethod, 0 , 0 };
46 const CFDictionaryValueCallBacks FieldDictionaryValueCallBacks = { 0, 0, &deleteField, 0 , 0 };    
47     
48 ObjcClass::ObjcClass(ClassStructPtr aClass)
49     : _isa(aClass)
50     , _methods(AdoptCF, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &MethodDictionaryValueCallBacks))
51     , _fields(AdoptCF, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &FieldDictionaryValueCallBacks))
52 {
53 }
54
55 static CFMutableDictionaryRef classesByIsA = 0;
56
57 static void _createClassesByIsAIfNecessary()
58 {
59     if (!classesByIsA)
60         classesByIsA = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
61 }
62
63 ObjcClass* ObjcClass::classForIsA(ClassStructPtr isa)
64 {
65     _createClassesByIsAIfNecessary();
66
67     ObjcClass* aClass = (ObjcClass*)CFDictionaryGetValue(classesByIsA, isa);
68     if (!aClass) {
69         aClass = new ObjcClass(isa);
70         CFDictionaryAddValue(classesByIsA, isa, aClass);
71     }
72
73     return aClass;
74 }
75
76 MethodList ObjcClass::methodsNamed(const Identifier& identifier, Instance*) const
77 {
78     MethodList methodList;
79     char fixedSizeBuffer[1024];
80     char* buffer = fixedSizeBuffer;
81     CString jsName = identifier.ascii();
82     if (!convertJSMethodNameToObjc(jsName.data(), buffer, sizeof(fixedSizeBuffer))) {
83         int length = jsName.length() + 1;
84         buffer = new char[length];
85         if (!buffer || !convertJSMethodNameToObjc(jsName.data(), buffer, length))
86             return methodList;
87     }
88
89     
90     RetainPtr<CFStringRef> methodName(AdoptCF, CFStringCreateWithCString(NULL, buffer, kCFStringEncodingASCII));
91     Method* method = (Method*)CFDictionaryGetValue(_methods.get(), methodName.get());
92     if (method) {
93         methodList.append(method);
94         return methodList;
95     }
96
97     ClassStructPtr thisClass = _isa;
98     while (thisClass && methodList.isEmpty()) {
99 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
100         unsigned numMethodsInClass = 0;
101         MethodStructPtr* objcMethodList = class_copyMethodList(thisClass, &numMethodsInClass);
102 #else
103         void* iterator = 0;
104         struct objc_method_list* objcMethodList;
105         while ((objcMethodList = class_nextMethodList(thisClass, &iterator))) {
106             unsigned numMethodsInClass = objcMethodList->method_count;
107 #endif
108             for (unsigned i = 0; i < numMethodsInClass; i++) {
109 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
110                 MethodStructPtr objcMethod = objcMethodList[i];
111                 SEL objcMethodSelector = method_getName(objcMethod);
112 #else
113                 struct objc_method* objcMethod = &objcMethodList->method_list[i];
114                 SEL objcMethodSelector = objcMethod->method_name;
115 #endif
116                 const char* objcMethodSelectorName = sel_getName(objcMethodSelector);
117                 NSString* mappedName = nil;
118
119                 // See if the class wants to exclude the selector from visibility in JavaScript.
120                 if ([thisClass respondsToSelector:@selector(isSelectorExcludedFromWebScript:)])
121                     if ([thisClass isSelectorExcludedFromWebScript:objcMethodSelector])
122                         continue;
123
124                 // See if the class want to provide a different name for the selector in JavaScript.
125                 // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
126                 // of the class.
127                 if ([thisClass respondsToSelector:@selector(webScriptNameForSelector:)])
128                     mappedName = [thisClass webScriptNameForSelector:objcMethodSelector];
129
130                 if ((mappedName && [mappedName isEqual:(NSString*)methodName.get()]) || strcmp(objcMethodSelectorName, buffer) == 0) {
131                     Method* aMethod = new ObjcMethod(thisClass, objcMethodSelector); // deleted when the dictionary is destroyed
132                     CFDictionaryAddValue(_methods.get(), methodName.get(), aMethod);
133                     methodList.append(aMethod);
134                     break;
135                 }
136             }
137 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
138             thisClass = class_getSuperclass(thisClass);
139             free(objcMethodList);
140 #else
141         }
142         thisClass = thisClass->super_class;
143 #endif
144     }
145
146     if (buffer != fixedSizeBuffer)
147         delete [] buffer;
148
149     return methodList;
150 }
151
152 Field* ObjcClass::fieldNamed(const Identifier& identifier, Instance* instance) const
153 {
154     ClassStructPtr thisClass = _isa;
155
156     CString jsName = identifier.ascii();
157     RetainPtr<CFStringRef> fieldName(AdoptCF, CFStringCreateWithCString(NULL, jsName.data(), kCFStringEncodingASCII));
158     Field* aField = (Field*)CFDictionaryGetValue(_fields.get(), fieldName.get());
159     if (aField)
160         return aField;
161
162     id targetObject = (static_cast<ObjcInstance*>(instance))->getObject();
163     id attributes = [targetObject attributeKeys];
164     if (attributes) {
165         // Class overrides attributeKeys, use that array of key names.
166         unsigned count = [attributes count];
167         for (unsigned i = 0; i < count; i++) {
168             NSString* keyName = [attributes objectAtIndex:i];
169             const char* UTF8KeyName = [keyName UTF8String]; // ObjC actually only supports ASCII names.
170
171             // See if the class wants to exclude the selector from visibility in JavaScript.
172             if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)])
173                 if ([thisClass isKeyExcludedFromWebScript:UTF8KeyName])
174                     continue;
175
176             // See if the class want to provide a different name for the selector in JavaScript.
177             // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
178             // of the class.
179             NSString* mappedName = nil;
180             if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)])
181                 mappedName = [thisClass webScriptNameForKey:UTF8KeyName];
182
183             if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || [keyName isEqual:(NSString*)fieldName.get()]) {
184                 aField = new ObjcField((CFStringRef)keyName); // deleted when the dictionary is destroyed
185                 CFDictionaryAddValue(_fields.get(), fieldName.get(), aField);
186                 break;
187             }
188         }
189     } else {
190         // Class doesn't override attributeKeys, so fall back on class runtime
191         // introspection.
192
193         while (thisClass) {
194 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
195             unsigned numFieldsInClass = 0;
196             IvarStructPtr* ivarsInClass = class_copyIvarList(thisClass, &numFieldsInClass);
197 #else
198             struct objc_ivar_list* fieldsInClass = thisClass->ivars;
199             if (fieldsInClass) {
200                 unsigned numFieldsInClass = fieldsInClass->ivar_count;
201 #endif
202                 for (unsigned i = 0; i < numFieldsInClass; i++) {
203 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
204                     IvarStructPtr objcIVar = ivarsInClass[i];
205                     const char* objcIvarName = ivar_getName(objcIVar);
206 #else
207                     IvarStructPtr objcIVar = &fieldsInClass->ivar_list[i];
208                     const char* objcIvarName = objcIVar->ivar_name;
209 #endif
210                     NSString* mappedName = 0;
211
212                     // See if the class wants to exclude the selector from visibility in JavaScript.
213                     if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)])
214                         if ([thisClass isKeyExcludedFromWebScript:objcIvarName])
215                             continue;
216
217                     // See if the class want to provide a different name for the selector in JavaScript.
218                     // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
219                     // of the class.
220                     if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)])
221                         mappedName = [thisClass webScriptNameForKey:objcIvarName];
222
223                     if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || strcmp(objcIvarName, jsName.data()) == 0) {
224                         aField = new ObjcField(objcIVar); // deleted when the dictionary is destroyed
225                         CFDictionaryAddValue(_fields.get(), fieldName.get(), aField);
226                         break;
227                     }
228                 }
229 #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2
230             thisClass = class_getSuperclass(thisClass);
231             free(ivarsInClass);
232 #else
233             }
234             thisClass = thisClass->super_class;
235 #endif
236         }
237     }
238
239     return aField;
240 }
241
242 JSValue ObjcClass::fallbackObject(ExecState* exec, Instance* instance, const Identifier &propertyName)
243 {
244     ObjcInstance* objcInstance = static_cast<ObjcInstance*>(instance);
245     id targetObject = objcInstance->getObject();
246     
247     if (![targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
248         return jsUndefined();
249     return new (exec) ObjcFallbackObjectImp(exec, exec->lexicalGlobalObject(), objcInstance, propertyName);
250 }
251
252 }
253 }