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 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 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 32namespace JSC { 33namespace Bindings { 34 35ObjcClass::ObjcClass(ClassStructPtr aClass) 36 : _isa(aClass) 37{ 38} 39 40static CFMutableDictionaryRef classesByIsA = 0; 41 42static void _createClassesByIsAIfNecessary() 43{ 44 if (!classesByIsA) 45 classesByIsA = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); 46} 47 48ObjcClass* 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*/ 76typedef Vector<char, 256> JSNameConversionBuffer; 77static 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 98Method* ObjcClass::methodNamed(PropertyName propertyName, Instance*) const 99{ 100 String name(propertyName.publicName()); 101 if (name.isNull()) 102 return 0; 103 104 if (Method* method = m_methodCache.get(name.impl())) 105 return method; 106 107 CString jsName = name.ascii(); 108 JSNameConversionBuffer buffer; 109 convertJSMethodNameToObjc(jsName, buffer); 110 RetainPtr<CFStringRef> methodName = adoptCF(CFStringCreateWithCString(NULL, buffer.data(), kCFStringEncodingASCII)); 111 112 Method* methodPtr = 0; 113 ClassStructPtr thisClass = _isa; 114 115 while (thisClass && !methodPtr) { 116 unsigned numMethodsInClass = 0; 117 MethodStructPtr* objcMethodList = class_copyMethodList(thisClass, &numMethodsInClass); 118 for (unsigned i = 0; i < numMethodsInClass; i++) { 119 MethodStructPtr objcMethod = objcMethodList[i]; 120 SEL objcMethodSelector = method_getName(objcMethod); 121 const char* objcMethodSelectorName = sel_getName(objcMethodSelector); 122 NSString* mappedName = nil; 123 124 // See if the class wants to exclude the selector from visibility in JavaScript. 125 if ([thisClass respondsToSelector:@selector(isSelectorExcludedFromWebScript:)]) 126 if ([thisClass isSelectorExcludedFromWebScript:objcMethodSelector]) 127 continue; 128 129 // See if the class want to provide a different name for the selector in JavaScript. 130 // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity 131 // of the class. 132 if ([thisClass respondsToSelector:@selector(webScriptNameForSelector:)]) 133 mappedName = [thisClass webScriptNameForSelector:objcMethodSelector]; 134 135 if ((mappedName && [mappedName isEqual:(NSString*)methodName.get()]) || strcmp(objcMethodSelectorName, buffer.data()) == 0) { 136 OwnPtr<Method> method = adoptPtr(new ObjcMethod(thisClass, objcMethodSelector)); 137 methodPtr = method.get(); 138 m_methodCache.add(name.impl(), method.release()); 139 break; 140 } 141 } 142 thisClass = class_getSuperclass(thisClass); 143 free(objcMethodList); 144 } 145 146 return methodPtr; 147} 148 149Field* ObjcClass::fieldNamed(PropertyName propertyName, Instance* instance) const 150{ 151 String name(propertyName.publicName()); 152 if (name.isNull()) 153 return 0; 154 155 Field* field = m_fieldCache.get(name.impl()); 156 if (field) 157 return field; 158 159 ClassStructPtr thisClass = _isa; 160 161 CString jsName = name.ascii(); 162 RetainPtr<CFStringRef> fieldName = adoptCF(CFStringCreateWithCString(NULL, jsName.data(), kCFStringEncodingASCII)); 163 id targetObject = (static_cast<ObjcInstance*>(instance))->getObject(); 164#if PLATFORM(IOS) 165 id attributes = [targetObject respondsToSelector:@selector(attributeKeys)] ? [targetObject performSelector:@selector(attributeKeys)] : nil; 166#else 167 id attributes = [targetObject attributeKeys]; 168#endif 169 if (attributes) { 170 // Class overrides attributeKeys, use that array of key names. 171 unsigned count = [attributes count]; 172 for (unsigned i = 0; i < count; i++) { 173 NSString* keyName = [attributes objectAtIndex:i]; 174 const char* UTF8KeyName = [keyName UTF8String]; // ObjC actually only supports ASCII names. 175 176 // See if the class wants to exclude the selector from visibility in JavaScript. 177 if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)]) 178 if ([thisClass isKeyExcludedFromWebScript:UTF8KeyName]) 179 continue; 180 181 // See if the class want to provide a different name for the selector in JavaScript. 182 // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity 183 // of the class. 184 NSString* mappedName = nil; 185 if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)]) 186 mappedName = [thisClass webScriptNameForKey:UTF8KeyName]; 187 188 if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || [keyName isEqual:(NSString*)fieldName.get()]) { 189 OwnPtr<Field> newField = adoptPtr(new ObjcField((CFStringRef)keyName)); 190 field = newField.get(); 191 m_fieldCache.add(name.impl(), newField.release()); 192 break; 193 } 194 } 195 } else { 196 // Class doesn't override attributeKeys, so fall back on class runtime 197 // introspection. 198 199 while (thisClass) { 200 unsigned numFieldsInClass = 0; 201 IvarStructPtr* ivarsInClass = class_copyIvarList(thisClass, &numFieldsInClass); 202 203 for (unsigned i = 0; i < numFieldsInClass; i++) { 204 IvarStructPtr objcIVar = ivarsInClass[i]; 205 const char* objcIvarName = ivar_getName(objcIVar); 206 NSString* mappedName = 0; 207 208 // See if the class wants to exclude the selector from visibility in JavaScript. 209 if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)]) 210 if ([thisClass isKeyExcludedFromWebScript:objcIvarName]) 211 continue; 212 213 // See if the class want to provide a different name for the selector in JavaScript. 214 // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity 215 // of the class. 216 if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)]) 217 mappedName = [thisClass webScriptNameForKey:objcIvarName]; 218 219 if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || strcmp(objcIvarName, jsName.data()) == 0) { 220 OwnPtr<Field> newField = adoptPtr(new ObjcField(objcIVar)); 221 field = newField.get(); 222 m_fieldCache.add(name.impl(), newField.release()); 223 break; 224 } 225 } 226 227 thisClass = class_getSuperclass(thisClass); 228 free(ivarsInClass); 229 } 230 } 231 232 return field; 233} 234 235JSValue ObjcClass::fallbackObject(ExecState* exec, Instance* instance, PropertyName propertyName) 236{ 237 ObjcInstance* objcInstance = static_cast<ObjcInstance*>(instance); 238 id targetObject = objcInstance->getObject(); 239 240 if (![targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]) 241 return jsUndefined(); 242 return ObjcFallbackObjectImp::create(exec, exec->lexicalGlobalObject(), objcInstance, propertyName.publicName()); 243} 244 245} 246} 247