/* * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "objc_class.h" #include "objc_instance.h" #include "WebScriptObject.h" namespace KJS { namespace Bindings { static void deleteMethod(CFAllocatorRef, const void* value) { delete static_cast(value); } static void deleteField(CFAllocatorRef, const void* value) { delete static_cast(value); } const CFDictionaryValueCallBacks MethodDictionaryValueCallBacks = { 0, 0, &deleteMethod, 0 , 0 }; const CFDictionaryValueCallBacks FieldDictionaryValueCallBacks = { 0, 0, &deleteField, 0 , 0 }; ObjcClass::ObjcClass(ClassStructPtr aClass) : _isa(aClass) , _methods(AdoptCF, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &MethodDictionaryValueCallBacks)) , _fields(AdoptCF, CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &FieldDictionaryValueCallBacks)) { } static CFMutableDictionaryRef classesByIsA = 0; static void _createClassesByIsAIfNecessary() { if (!classesByIsA) classesByIsA = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); } ObjcClass* ObjcClass::classForIsA(ClassStructPtr isa) { _createClassesByIsAIfNecessary(); ObjcClass* aClass = (ObjcClass*)CFDictionaryGetValue(classesByIsA, isa); if (!aClass) { aClass = new ObjcClass(isa); CFDictionaryAddValue(classesByIsA, isa, aClass); } return aClass; } const char* ObjcClass::name() const { return object_getClassName(_isa); } MethodList ObjcClass::methodsNamed(const Identifier& identifier, Instance*) const { MethodList methodList; char fixedSizeBuffer[1024]; char* buffer = fixedSizeBuffer; const char* JSName = identifier.ascii(); if (!convertJSMethodNameToObjc(JSName, buffer, sizeof(fixedSizeBuffer))) { int length = strlen(JSName) + 1; buffer = new char[length]; if (!buffer || !convertJSMethodNameToObjc(JSName, buffer, length)) return methodList; } RetainPtr methodName(AdoptCF, CFStringCreateWithCString(NULL, buffer, kCFStringEncodingASCII)); Method* method = (Method*)CFDictionaryGetValue(_methods.get(), methodName.get()); if (method) { methodList.append(method); return methodList; } ClassStructPtr thisClass = _isa; while (thisClass && methodList.isEmpty()) { #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 unsigned numMethodsInClass = 0; MethodStructPtr* objcMethodList = class_copyMethodList(thisClass, &numMethodsInClass); #else void* iterator = 0; struct objc_method_list* objcMethodList; while ((objcMethodList = class_nextMethodList(thisClass, &iterator))) { unsigned numMethodsInClass = objcMethodList->method_count; #endif for (unsigned i = 0; i < numMethodsInClass; i++) { #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 MethodStructPtr objcMethod = objcMethodList[i]; SEL objcMethodSelector = method_getName(objcMethod); #else struct objc_method* objcMethod = &objcMethodList->method_list[i]; SEL objcMethodSelector = objcMethod->method_name; #endif const char* objcMethodSelectorName = sel_getName(objcMethodSelector); NSString* mappedName = nil; // See if the class wants to exclude the selector from visibility in JavaScript. if ([thisClass respondsToSelector:@selector(isSelectorExcludedFromWebScript:)]) if ([thisClass isSelectorExcludedFromWebScript:objcMethodSelector]) continue; // See if the class want to provide a different name for the selector in JavaScript. // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity // of the class. if ([thisClass respondsToSelector:@selector(webScriptNameForSelector:)]) mappedName = [thisClass webScriptNameForSelector:objcMethodSelector]; if ((mappedName && [mappedName isEqual:(NSString*)methodName.get()]) || strcmp(objcMethodSelectorName, buffer) == 0) { Method* aMethod = new ObjcMethod(thisClass, objcMethodSelectorName); // deleted when the dictionary is destroyed CFDictionaryAddValue(_methods.get(), methodName.get(), aMethod); methodList.append(aMethod); break; } } #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 thisClass = class_getSuperclass(thisClass); free(objcMethodList); #else } thisClass = thisClass->super_class; #endif } if (buffer != fixedSizeBuffer) delete [] buffer; return methodList; } Field* ObjcClass::fieldNamed(const Identifier& identifier, Instance* instance) const { ClassStructPtr thisClass = _isa; const char* name = identifier.ascii(); RetainPtr fieldName(AdoptCF, CFStringCreateWithCString(NULL, name, kCFStringEncodingASCII)); Field* aField = (Field*)CFDictionaryGetValue(_fields.get(), fieldName.get()); if (aField) return aField; id targetObject = (static_cast(instance))->getObject(); id attributes = [targetObject attributeKeys]; if (attributes) { // Class overrides attributeKeys, use that array of key names. unsigned count = [attributes count]; for (unsigned i = 0; i < count; i++) { NSString* keyName = [attributes objectAtIndex:i]; const char* UTF8KeyName = [keyName UTF8String]; // ObjC actually only supports ASCII names. // See if the class wants to exclude the selector from visibility in JavaScript. if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)]) if ([thisClass isKeyExcludedFromWebScript:UTF8KeyName]) continue; // See if the class want to provide a different name for the selector in JavaScript. // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity // of the class. NSString* mappedName = nil; if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)]) mappedName = [thisClass webScriptNameForKey:UTF8KeyName]; if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || [keyName isEqual:(NSString*)fieldName.get()]) { aField = new ObjcField((CFStringRef)keyName); // deleted when the dictionary is destroyed CFDictionaryAddValue(_fields.get(), fieldName.get(), aField); break; } } } else { // Class doesn't override attributeKeys, so fall back on class runtime // introspection. while (thisClass) { #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 unsigned numFieldsInClass = 0; IvarStructPtr* ivarsInClass = class_copyIvarList(thisClass, &numFieldsInClass); #else struct objc_ivar_list* fieldsInClass = thisClass->ivars; if (fieldsInClass) { unsigned numFieldsInClass = fieldsInClass->ivar_count; #endif for (unsigned i = 0; i < numFieldsInClass; i++) { #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 IvarStructPtr objcIVar = ivarsInClass[i]; const char* objcIvarName = ivar_getName(objcIVar); #else IvarStructPtr objcIVar = &fieldsInClass->ivar_list[i]; const char* objcIvarName = objcIVar->ivar_name; #endif NSString* mappedName = 0; // See if the class wants to exclude the selector from visibility in JavaScript. if ([thisClass respondsToSelector:@selector(isKeyExcludedFromWebScript:)]) if ([thisClass isKeyExcludedFromWebScript:objcIvarName]) continue; // See if the class want to provide a different name for the selector in JavaScript. // Note that we do not do any checks to guarantee uniqueness. That's the responsiblity // of the class. if ([thisClass respondsToSelector:@selector(webScriptNameForKey:)]) mappedName = [thisClass webScriptNameForKey:objcIvarName]; if ((mappedName && [mappedName isEqual:(NSString*)fieldName.get()]) || strcmp(objcIvarName, name) == 0) { aField = new ObjcField(objcIVar); // deleted when the dictionary is destroyed CFDictionaryAddValue(_fields.get(), fieldName.get(), aField); break; } } #if defined(OBJC_API_VERSION) && OBJC_API_VERSION >= 2 thisClass = class_getSuperclass(thisClass); free(ivarsInClass); #else } thisClass = thisClass->super_class; #endif } } return aField; } JSValue* ObjcClass::fallbackObject(ExecState*, Instance* instance, const Identifier &propertyName) { ObjcInstance* objcInstance = static_cast(instance); id targetObject = objcInstance->getObject(); if (![targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]) return jsUndefined(); return new ObjcFallbackObjectImp(objcInstance, propertyName); } } }