blob: 5f3677e60b16dd9b92d2256a20fd71c7de0859b2 [file] [log] [blame]
/*
* 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 JSC {
namespace Bindings {
static void deleteMethod(CFAllocatorRef, const void* value)
{
delete static_cast<const Method*>(value);
}
static void deleteField(CFAllocatorRef, const void* value)
{
delete static_cast<const Field*>(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;
}
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<CFStringRef> 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, objcMethodSelector); // 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<CFStringRef> fieldName(AdoptCF, CFStringCreateWithCString(NULL, name, kCFStringEncodingASCII));
Field* aField = (Field*)CFDictionaryGetValue(_fields.get(), fieldName.get());
if (aField)
return aField;
id targetObject = (static_cast<ObjcInstance*>(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* exec, Instance* instance, const Identifier &propertyName)
{
ObjcInstance* objcInstance = static_cast<ObjcInstance*>(instance);
id targetObject = objcInstance->getObject();
if (![targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)])
return jsUndefined();
return new (exec) ObjcFallbackObjectImp(exec, objcInstance, propertyName);
}
}
}