7080ee4f4d69b1a3ed14cc3ec60ded399101ec7e
[WebKit-https.git] / Source / WebCore / bridge / objc / objc_class.mm
1 /*
2  * Copyright (C) 2004, 2012 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_class.h"
28
29 #include "objc_instance.h"
30 #include "WebScriptObject.h"
31
32 namespace JSC {
33 namespace Bindings {
34
35 ObjcClass::ObjcClass(ClassStructPtr aClass)
36     : _isa(aClass)
37 {
38 }
39
40 static CFMutableDictionaryRef classesByIsA = 0;
41
42 static void _createClassesByIsAIfNecessary()
43 {
44     if (!classesByIsA)
45         classesByIsA = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
46 }
47
48 ObjcClass* ObjcClass::classForIsA(ClassStructPtr isa)
49 {
50     _createClassesByIsAIfNecessary();
51
52     ObjcClass* aClass = (ObjcClass*)CFDictionaryGetValue(classesByIsA, isa);
53     if (!aClass) {
54         aClass = new ObjcClass(isa);
55         CFDictionaryAddValue(classesByIsA, isa, aClass);
56     }
57
58     return aClass;
59 }
60
61 /*
62     By default, a JavaScript method name is produced by concatenating the
63     components of an ObjectiveC method name, replacing ':' with '_', and
64     escaping '_' and '$' with a leading '$', such that '_' becomes "$_" and
65     '$' becomes "$$". For example:
66
67     ObjectiveC name         Default JavaScript name
68         moveTo::                moveTo__
69         moveTo_                 moveTo$_
70         moveTo$_                moveTo$$$_
71
72     This function performs the inverse of that operation.
73
74     @result Fills 'buffer' with the ObjectiveC method name that corresponds to 'JSName'.
75 */
76 typedef Vector<char, 256> JSNameConversionBuffer;
77 static inline void convertJSMethodNameToObjc(const CString& jsName, JSNameConversionBuffer& buffer)
78 {
79     buffer.reserveInitialCapacity(jsName.length() + 1);
80
81     const char* source = jsName.data();
82     while (true) {
83         if (*source == '$') {
84             ++source;
85             buffer.uncheckedAppend(*source);
86         } else if (*source == '_')
87             buffer.uncheckedAppend(':');
88         else
89             buffer.uncheckedAppend(*source);
90
91         if (!*source)
92             return;
93
94         ++source;
95     }
96 }
97
98 MethodList ObjcClass::methodsNamed(PropertyName identifier, Instance*) const
99 {
100     MethodList methodList;
101     if (Method* method = m_methodCache.get(identifier.impl())) {
102         methodList.append(method);
103         return methodList;
104     }
105
106     CString jsName = identifier.ustring().ascii();
107     JSNameConversionBuffer buffer;
108     convertJSMethodNameToObjc(jsName, buffer);
109     RetainPtr<CFStringRef> methodName(AdoptCF, CFStringCreateWithCString(NULL, buffer.data(), kCFStringEncodingASCII));
110
111     ClassStructPtr thisClass = _isa;
112     while (thisClass && methodList.isEmpty()) {
113         unsigned numMethodsInClass = 0;
114         MethodStructPtr* objcMethodList = class_copyMethodList(thisClass, &numMethodsInClass);
115         for (unsigned i = 0; i < numMethodsInClass; i++) {
116             MethodStructPtr objcMethod = objcMethodList[i];
117             SEL objcMethodSelector = method_getName(objcMethod);
118             const char* objcMethodSelectorName = sel_getName(objcMethodSelector);
119             NSString* mappedName = nil;
120
121             // See if the class wants to exclude the selector from visibility in JavaScript.
122             if ([thisClass respondsToSelector:@selector(isSelectorExcludedFromWebScript:)])
123                 if ([thisClass isSelectorExcludedFromWebScript:objcMethodSelector])
124                     continue;
125
126             // See if the class want to provide a different name for the selector in JavaScript.
127             // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
128             // of the class.
129             if ([thisClass respondsToSelector:@selector(webScriptNameForSelector:)])
130                 mappedName = [thisClass webScriptNameForSelector:objcMethodSelector];
131
132             if ((mappedName && [mappedName isEqual:(NSString*)methodName.get()]) || strcmp(objcMethodSelectorName, buffer.data()) == 0) {
133                 OwnPtr<Method> method = adoptPtr(new ObjcMethod(thisClass, objcMethodSelector));
134                 methodList.append(method.get());
135                 m_methodCache.add(identifier.impl(), method.release());
136                 break;
137             }
138         }
139         thisClass = class_getSuperclass(thisClass);
140         free(objcMethodList);
141     }
142
143     return methodList;
144 }
145
146 Field* ObjcClass::fieldNamed(PropertyName identifier, Instance* instance) const
147 {
148     Field* field = m_fieldCache.get(identifier.impl());
149     if (field)
150         return field;
151
152     ClassStructPtr thisClass = _isa;
153
154     CString jsName = identifier.ustring().ascii();
155     RetainPtr<CFStringRef> fieldName(AdoptCF, CFStringCreateWithCString(NULL, jsName.data(), kCFStringEncodingASCII));
156     id targetObject = (static_cast<ObjcInstance*>(instance))->getObject();
157     id attributes = [targetObject attributeKeys];
158     if (attributes) {
159         // Class overrides attributeKeys, use that array of key names.
160         unsigned count = [attributes count];
161         for (unsigned i = 0; i < count; i++) {
162             NSString* keyName = [attributes objectAtIndex:i];
163             const char* UTF8KeyName = [keyName UTF8String]; // ObjC actually only supports ASCII names.
164
165             // See if the class wants to exclude the selector from visibility in JavaScript.
166             if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)])
167                 if ([thisClass isKeyExcludedFromWebScript:UTF8KeyName])
168                     continue;
169
170             // See if the class want to provide a different name for the selector in JavaScript.
171             // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
172             // of the class.
173             NSString* mappedName = nil;
174             if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)])
175                 mappedName = [thisClass webScriptNameForKey:UTF8KeyName];
176
177             if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || [keyName isEqual:(NSString*)fieldName.get()]) {
178                 OwnPtr<Field> newField = adoptPtr(new ObjcField((CFStringRef)keyName));
179                 field = newField.get();
180                 m_fieldCache.add(identifier.impl(), newField.release());
181                 break;
182             }
183         }
184     } else {
185         // Class doesn't override attributeKeys, so fall back on class runtime
186         // introspection.
187
188         while (thisClass) {
189             unsigned numFieldsInClass = 0;
190             IvarStructPtr* ivarsInClass = class_copyIvarList(thisClass, &numFieldsInClass);
191
192             for (unsigned i = 0; i < numFieldsInClass; i++) {
193                 IvarStructPtr objcIVar = ivarsInClass[i];
194                 const char* objcIvarName = ivar_getName(objcIVar);
195                 NSString* mappedName = 0;
196
197                 // See if the class wants to exclude the selector from visibility in JavaScript.
198                 if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)])
199                     if ([thisClass isKeyExcludedFromWebScript:objcIvarName])
200                         continue;
201
202                 // See if the class want to provide a different name for the selector in JavaScript.
203                 // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity
204                 // of the class.
205                 if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)])
206                     mappedName = [thisClass webScriptNameForKey:objcIvarName];
207
208                 if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || strcmp(objcIvarName, jsName.data()) == 0) {
209                     OwnPtr<Field> newField = adoptPtr(new ObjcField(objcIVar));
210                     field = newField.get();
211                     m_fieldCache.add(identifier.impl(), newField.release());
212                     break;
213                 }
214             }
215
216             thisClass = class_getSuperclass(thisClass);
217             free(ivarsInClass);
218         }
219     }
220
221     return field;
222 }
223
224 JSValue ObjcClass::fallbackObject(ExecState* exec, Instance* instance, PropertyName propertyName)
225 {
226     ObjcInstance* objcInstance = static_cast<ObjcInstance*>(instance);
227     id targetObject = objcInstance->getObject();
228     
229     if (![targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
230         return jsUndefined();
231     return ObjcFallbackObjectImp::create(exec, exec->lexicalGlobalObject(), objcInstance, propertyName.impl());
232 }
233
234 }
235 }