/* * Copyright (C) 2004, 2012 Apple 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 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 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" #include "WebScriptObjectProtocol.h" namespace JSC { namespace Bindings { ObjcClass::ObjcClass(ClassStructPtr aClass) : _isa(aClass) { } static CFMutableDictionaryRef classesByIsA = 0; static void _createClassesByIsAIfNecessary() { if (!classesByIsA) classesByIsA = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); } ObjcClass* ObjcClass::classForIsA(ClassStructPtr isa) { _createClassesByIsAIfNecessary(); ObjcClass* aClass = reinterpret_cast<ObjcClass*>(const_cast<void*>(CFDictionaryGetValue(classesByIsA, isa))); if (!aClass) { aClass = new ObjcClass(isa); CFDictionaryAddValue(classesByIsA, isa, aClass); } return aClass; } /* By default, a JavaScript method name is produced by concatenating the components of an ObjectiveC method name, replacing ':' with '_', and escaping '_' and '$' with a leading '$', such that '_' becomes "$_" and '$' becomes "$$". For example: ObjectiveC name Default JavaScript name moveTo:: moveTo__ moveTo_ moveTo$_ moveTo$_ moveTo$$$_ This function performs the inverse of that operation. @result Fills 'buffer' with the ObjectiveC method name that corresponds to 'JSName'. */ typedef Vector<char, 256> JSNameConversionBuffer; static inline void convertJSMethodNameToObjc(const CString& jsName, JSNameConversionBuffer& buffer) { buffer.reserveInitialCapacity(jsName.length() + 1); const char* source = jsName.data(); while (true) { if (*source == '$') { ++source; buffer.uncheckedAppend(*source); } else if (*source == '_') buffer.uncheckedAppend(':'); else buffer.uncheckedAppend(*source); if (!*source) return; ++source; } } Method* ObjcClass::methodNamed(PropertyName propertyName, Instance*) const { String name(propertyName.publicName()); if (name.isNull()) return nullptr; if (Method* method = m_methodCache.get(name.impl())) return method; CString jsName = name.ascii(); JSNameConversionBuffer buffer; convertJSMethodNameToObjc(jsName, buffer); RetainPtr<CFStringRef> methodName = adoptCF(CFStringCreateWithCString(NULL, buffer.data(), kCFStringEncodingASCII)); Method* methodPtr = 0; ClassStructPtr thisClass = _isa; while (thisClass && !methodPtr) { unsigned numMethodsInClass = 0; MethodStructPtr* objcMethodList = class_copyMethodList(thisClass, &numMethodsInClass); for (unsigned i = 0; i < numMethodsInClass; i++) { MethodStructPtr objcMethod = objcMethodList[i]; SEL objcMethodSelector = method_getName(objcMethod); 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.data()) == 0) { auto method = std::make_unique<ObjcMethod>(thisClass, objcMethodSelector); methodPtr = method.get(); m_methodCache.add(name.impl(), WTFMove(method)); break; } } thisClass = class_getSuperclass(thisClass); free(objcMethodList); } return methodPtr; } Field* ObjcClass::fieldNamed(PropertyName propertyName, Instance* instance) const { String name(propertyName.publicName()); if (name.isNull()) return nullptr; Field* field = m_fieldCache.get(name.impl()); if (field) return field; ClassStructPtr thisClass = _isa; CString jsName = name.ascii(); RetainPtr<CFStringRef> fieldName = adoptCF(CFStringCreateWithCString(NULL, jsName.data(), kCFStringEncodingASCII)); id targetObject = (static_cast<ObjcInstance*>(instance))->getObject(); #if PLATFORM(IOS) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" id attributes = [targetObject respondsToSelector:@selector(attributeKeys)] ? [targetObject performSelector:@selector(attributeKeys)] : nil; #pragma clang diagnostic pop #else id attributes = [targetObject attributeKeys]; #endif 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()]) { auto newField = std::make_unique<ObjcField>((CFStringRef)keyName); field = newField.get(); m_fieldCache.add(name.impl(), WTFMove(newField)); break; } } } else { // Class doesn't override attributeKeys, so fall back on class runtime // introspection. while (thisClass) { unsigned numFieldsInClass = 0; IvarStructPtr* ivarsInClass = class_copyIvarList(thisClass, &numFieldsInClass); for (unsigned i = 0; i < numFieldsInClass; i++) { IvarStructPtr objcIVar = ivarsInClass[i]; const char* objcIvarName = ivar_getName(objcIVar); 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, jsName.data()) == 0) { auto newField = std::make_unique<ObjcField>(objcIVar); field = newField.get(); m_fieldCache.add(name.impl(), WTFMove(newField)); break; } } thisClass = class_getSuperclass(thisClass); free(ivarsInClass); } } return field; } JSValue ObjcClass::fallbackObject(ExecState* exec, Instance* instance, PropertyName propertyName) { ObjcInstance* objcInstance = static_cast<ObjcInstance*>(instance); id targetObject = objcInstance->getObject(); if (![targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]) return jsUndefined(); if (!propertyName.publicName()) return jsUndefined(); return ObjcFallbackObjectImp::create(exec, exec->lexicalGlobalObject(), objcInstance, propertyName.publicName()); } } }