diff options
Diffstat (limited to 'Source/JavaScriptCore/API/JSWrapperMap.mm')
-rw-r--r-- | Source/JavaScriptCore/API/JSWrapperMap.mm | 521 |
1 files changed, 0 insertions, 521 deletions
diff --git a/Source/JavaScriptCore/API/JSWrapperMap.mm b/Source/JavaScriptCore/API/JSWrapperMap.mm deleted file mode 100644 index 4dde1a659..000000000 --- a/Source/JavaScriptCore/API/JSWrapperMap.mm +++ /dev/null @@ -1,521 +0,0 @@ -/* - * Copyright (C) 2013 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" -#import "JavaScriptCore.h" - -#if JSC_OBJC_API_ENABLED - -#import "APICast.h" -#import "APIShims.h" -#import "JSAPIWrapperObject.h" -#import "JSCallbackObject.h" -#import "JSContextInternal.h" -#import "JSWrapperMap.h" -#import "ObjCCallbackFunction.h" -#import "ObjcRuntimeExtras.h" -#import "Operations.h" -#import "WeakGCMap.h" -#import <wtf/TCSpinLock.h> -#import <wtf/Vector.h> - -@class JSObjCClassInfo; - -@interface JSWrapperMap () - -- (JSObjCClassInfo*)classInfoForClass:(Class)cls; - -@end - -// Default conversion of selectors to property names. -// All semicolons are removed, lowercase letters following a semicolon are capitalized. -static NSString *selectorToPropertyName(const char* start) -{ - // Use 'index' to check for colons, if there are none, this is easy! - const char* firstColon = index(start, ':'); - if (!firstColon) - return [NSString stringWithUTF8String:start]; - - // 'header' is the length of string up to the first colon. - size_t header = firstColon - start; - // The new string needs to be long enough to hold 'header', plus the remainder of the string, excluding - // at least one ':', but including a '\0'. (This is conservative if there are more than one ':'). - char* buffer = static_cast<char*>(malloc(header + strlen(firstColon + 1) + 1)); - // Copy 'header' characters, set output to point to the end of this & input to point past the first ':'. - memcpy(buffer, start, header); - char* output = buffer + header; - const char* input = start + header + 1; - - // On entry to the loop, we have already skipped over a ':' from the input. - while (true) { - char c; - // Skip over any additional ':'s. We'll leave c holding the next character after the - // last ':', and input pointing past c. - while ((c = *(input++)) == ':'); - // Copy the character, converting to upper case if necessary. - // If the character we copy is '\0', then we're done! - if (!(*(output++) = toupper(c))) - goto done; - // Loop over characters other than ':'. - while ((c = *(input++)) != ':') { - // Copy the character. - // If the character we copy is '\0', then we're done! - if (!(*(output++) = c)) - goto done; - } - // If we get here, we've consumed a ':' - wash, rinse, repeat. - } -done: - NSString *result = [NSString stringWithUTF8String:buffer]; - free(buffer); - return result; -} - -static JSObjectRef makeWrapper(JSContextRef ctx, JSClassRef jsClass, id wrappedObject) -{ - JSC::ExecState* exec = toJS(ctx); - JSC::APIEntryShim entryShim(exec); - - ASSERT(jsClass); - JSC::JSCallbackObject<JSC::JSAPIWrapperObject>* object = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->objcWrapperObjectStructure(), jsClass, 0); - object->setWrappedObject(wrappedObject); - if (JSC::JSObject* prototype = jsClass->prototype(exec)) - object->setPrototype(exec->vm(), prototype); - - return toRef(object); -} - -// Make an object that is in all ways a completely vanilla JavaScript object, -// other than that it has a native brand set that will be displayed by the default -// Object.prototype.toString conversion. -static JSValue *objectWithCustomBrand(JSContext *context, NSString *brand, Class cls = 0) -{ - JSClassDefinition definition; - definition = kJSClassDefinitionEmpty; - definition.className = [brand UTF8String]; - JSClassRef classRef = JSClassCreate(&definition); - JSObjectRef result = makeWrapper([context JSGlobalContextRef], classRef, cls); - JSClassRelease(classRef); - return [JSValue valueWithJSValueRef:result inContext:context]; -} - -// Look for @optional properties in the prototype containing a selector to property -// name mapping, separated by a __JS_EXPORT_AS__ delimiter. -static NSMutableDictionary *createRenameMap(Protocol *protocol, BOOL isInstanceMethod) -{ - NSMutableDictionary *renameMap = [[NSMutableDictionary alloc] init]; - - forEachMethodInProtocol(protocol, NO, isInstanceMethod, ^(SEL sel, const char*){ - NSString *rename = @(sel_getName(sel)); - NSRange range = [rename rangeOfString:@"__JS_EXPORT_AS__"]; - if (range.location == NSNotFound) - return; - NSString *selector = [rename substringToIndex:range.location]; - NSUInteger begin = range.location + range.length; - NSUInteger length = [rename length] - begin - 1; - NSString *name = [rename substringWithRange:(NSRange){ begin, length }]; - renameMap[selector] = name; - }); - - return renameMap; -} - -inline void putNonEnumerable(JSValue *base, NSString *propertyName, JSValue *value) -{ - [base defineProperty:propertyName descriptor:@{ - JSPropertyDescriptorValueKey: value, - JSPropertyDescriptorWritableKey: @YES, - JSPropertyDescriptorEnumerableKey: @NO, - JSPropertyDescriptorConfigurableKey: @YES - }]; -} - -// This method will iterate over the set of required methods in the protocol, and: -// * Determine a property name (either via a renameMap or default conversion). -// * If an accessorMap is provided, and contains this name, store the method in the map. -// * Otherwise, if the object doesn't already contain a property with name, create it. -static void copyMethodsToObject(JSContext *context, Class objcClass, Protocol *protocol, BOOL isInstanceMethod, JSValue *object, NSMutableDictionary *accessorMethods = nil) -{ - NSMutableDictionary *renameMap = createRenameMap(protocol, isInstanceMethod); - - forEachMethodInProtocol(protocol, YES, isInstanceMethod, ^(SEL sel, const char* types){ - const char* nameCStr = sel_getName(sel); - NSString *name = @(nameCStr); - if (accessorMethods && accessorMethods[name]) { - JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types); - if (!method) - return; - accessorMethods[name] = [JSValue valueWithJSValueRef:method inContext:context]; - } else { - name = renameMap[name]; - if (!name) - name = selectorToPropertyName(nameCStr); - if ([object hasProperty:name]) - return; - JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types); - if (method) - putNonEnumerable(object, name, [JSValue valueWithJSValueRef:method inContext:context]); - } - }); - - [renameMap release]; -} - -static bool parsePropertyAttributes(objc_property_t property, char*& getterName, char*& setterName) -{ - bool readonly = false; - unsigned attributeCount; - objc_property_attribute_t* attributes = property_copyAttributeList(property, &attributeCount); - if (attributeCount) { - for (unsigned i = 0; i < attributeCount; ++i) { - switch (*(attributes[i].name)) { - case 'G': - getterName = strdup(attributes[i].value); - break; - case 'S': - setterName = strdup(attributes[i].value); - break; - case 'R': - readonly = true; - break; - default: - break; - } - } - free(attributes); - } - return readonly; -} - -static char* makeSetterName(const char* name) -{ - size_t nameLength = strlen(name); - char* setterName = (char*)malloc(nameLength + 5); // "set" Name ":\0" - setterName[0] = 's'; - setterName[1] = 'e'; - setterName[2] = 't'; - setterName[3] = toupper(*name); - memcpy(setterName + 4, name + 1, nameLength - 1); - setterName[nameLength + 3] = ':'; - setterName[nameLength + 4] = '\0'; - return setterName; -} - -static void copyPrototypeProperties(JSContext *context, Class objcClass, Protocol *protocol, JSValue *prototypeValue) -{ - // First gather propreties into this list, then handle the methods (capturing the accessor methods). - struct Property { - const char* name; - char* getterName; - char* setterName; - }; - __block Vector<Property> propertyList; - - // Map recording the methods used as getters/setters. - NSMutableDictionary *accessorMethods = [NSMutableDictionary dictionary]; - - // Useful value. - JSValue *undefined = [JSValue valueWithUndefinedInContext:context]; - - forEachPropertyInProtocol(protocol, ^(objc_property_t property){ - char* getterName = 0; - char* setterName = 0; - bool readonly = parsePropertyAttributes(property, getterName, setterName); - const char* name = property_getName(property); - - // Add the names of the getter & setter methods to - if (!getterName) - getterName = strdup(name); - accessorMethods[@(getterName)] = undefined; - if (!readonly) { - if (!setterName) - setterName = makeSetterName(name); - accessorMethods[@(setterName)] = undefined; - } - - // Add the properties to a list. - propertyList.append((Property){ name, getterName, setterName }); - }); - - // Copy methods to the prototype, capturing accessors in the accessorMethods map. - copyMethodsToObject(context, objcClass, protocol, YES, prototypeValue, accessorMethods); - - // Iterate the propertyList & generate accessor properties. - for (size_t i = 0; i < propertyList.size(); ++i) { - Property& property = propertyList[i]; - - JSValue *getter = accessorMethods[@(property.getterName)]; - free(property.getterName); - ASSERT(![getter isUndefined]); - - JSValue *setter = undefined; - if (property.setterName) { - setter = accessorMethods[@(property.setterName)]; - free(property.setterName); - ASSERT(![setter isUndefined]); - } - - [prototypeValue defineProperty:@(property.name) descriptor:@{ - JSPropertyDescriptorGetKey: getter, - JSPropertyDescriptorSetKey: setter, - JSPropertyDescriptorEnumerableKey: @NO, - JSPropertyDescriptorConfigurableKey: @YES - }]; - } -} - -@interface JSObjCClassInfo : NSObject { - JSContext *m_context; - Class m_class; - bool m_block; - JSClassRef m_classRef; - JSC::Weak<JSC::JSObject> m_prototype; - JSC::Weak<JSC::JSObject> m_constructor; -} - -- (id)initWithContext:(JSContext *)context forClass:(Class)cls superClassInfo:(JSObjCClassInfo*)superClassInfo; -- (JSValue *)wrapperForObject:(id)object; -- (JSValue *)constructor; - -@end - -@implementation JSObjCClassInfo - -- (id)initWithContext:(JSContext *)context forClass:(Class)cls superClassInfo:(JSObjCClassInfo*)superClassInfo -{ - self = [super init]; - if (!self) - return nil; - - const char* className = class_getName(cls); - m_context = context; - m_class = cls; - m_block = [cls isSubclassOfClass:getNSBlockClass()]; - JSClassDefinition definition; - definition = kJSClassDefinitionEmpty; - definition.className = className; - m_classRef = JSClassCreate(&definition); - - [self allocateConstructorAndPrototypeWithSuperClassInfo:superClassInfo]; - - return self; -} - -- (void)dealloc -{ - JSClassRelease(m_classRef); - [super dealloc]; -} - -- (void)allocateConstructorAndPrototypeWithSuperClassInfo:(JSObjCClassInfo*)superClassInfo -{ - ASSERT(!m_constructor || !m_prototype); - ASSERT((m_class == [NSObject class]) == !superClassInfo); - if (!superClassInfo) { - JSContextRef cContext = [m_context JSGlobalContextRef]; - JSValue *constructor = m_context[@"Object"]; - if (!m_constructor) - m_constructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0)); - - if (!m_prototype) { - JSValue *prototype = constructor[@"prototype"]; - m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0)); - } - } else { - const char* className = class_getName(m_class); - - // Create or grab the prototype/constructor pair. - JSValue *prototype; - JSValue *constructor; - if (m_prototype) - prototype = [JSValue valueWithJSValueRef:toRef(m_prototype.get()) inContext:m_context]; - else - prototype = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sPrototype", className]); - - if (m_constructor) - constructor = [JSValue valueWithJSValueRef:toRef(m_constructor.get()) inContext:m_context]; - else - constructor = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sConstructor", className], m_class); - - JSContextRef cContext = [m_context JSGlobalContextRef]; - m_prototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0)); - m_constructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0)); - - putNonEnumerable(prototype, @"constructor", constructor); - putNonEnumerable(constructor, @"prototype", prototype); - - Protocol *exportProtocol = getJSExportProtocol(); - forEachProtocolImplementingProtocol(m_class, exportProtocol, ^(Protocol *protocol){ - copyPrototypeProperties(m_context, m_class, protocol, prototype); - copyMethodsToObject(m_context, m_class, protocol, NO, constructor); - }); - - // Set [Prototype]. - JSObjectSetPrototype([m_context JSGlobalContextRef], toRef(m_prototype.get()), toRef(superClassInfo->m_prototype.get())); - } -} - -- (void)reallocateConstructorAndOrPrototype -{ - [self allocateConstructorAndPrototypeWithSuperClassInfo:[m_context.wrapperMap classInfoForClass:class_getSuperclass(m_class)]]; -} - -- (JSValue *)wrapperForObject:(id)object -{ - ASSERT([object isKindOfClass:m_class]); - ASSERT(m_block == [object isKindOfClass:getNSBlockClass()]); - if (m_block) { - if (JSObjectRef method = objCCallbackFunctionForBlock(m_context, object)) - return [JSValue valueWithJSValueRef:method inContext:m_context]; - } - - if (!m_prototype) - [self reallocateConstructorAndOrPrototype]; - ASSERT(!!m_prototype); - - JSObjectRef wrapper = makeWrapper([m_context JSGlobalContextRef], m_classRef, object); - JSObjectSetPrototype([m_context JSGlobalContextRef], wrapper, toRef(m_prototype.get())); - return [JSValue valueWithJSValueRef:wrapper inContext:m_context]; -} - -- (JSValue *)constructor -{ - if (!m_constructor) - [self reallocateConstructorAndOrPrototype]; - ASSERT(!!m_constructor); - return [JSValue valueWithJSValueRef:toRef(m_constructor.get()) inContext:m_context]; -} - -@end - -@implementation JSWrapperMap { - JSContext *m_context; - NSMutableDictionary *m_classMap; - JSC::WeakGCMap<id, JSC::JSObject> m_cachedJSWrappers; - NSMapTable *m_cachedObjCWrappers; -} - -- (id)initWithContext:(JSContext *)context -{ - self = [super init]; - if (!self) - return nil; - - NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality; - NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality; - m_cachedObjCWrappers = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0]; - - m_context = context; - m_classMap = [[NSMutableDictionary alloc] init]; - return self; -} - -- (void)dealloc -{ - [m_cachedObjCWrappers release]; - [m_classMap release]; - [super dealloc]; -} - -- (JSObjCClassInfo*)classInfoForClass:(Class)cls -{ - if (!cls) - return nil; - - // Check if we've already created a JSObjCClassInfo for this Class. - if (JSObjCClassInfo* classInfo = (JSObjCClassInfo*)m_classMap[cls]) - return classInfo; - - // Skip internal classes beginning with '_' - just copy link to the parent class's info. - if ('_' == *class_getName(cls)) - return m_classMap[cls] = [self classInfoForClass:class_getSuperclass(cls)]; - - return m_classMap[cls] = [[[JSObjCClassInfo alloc] initWithContext:m_context forClass:cls superClassInfo:[self classInfoForClass:class_getSuperclass(cls)]] autorelease]; -} - -- (JSValue *)jsWrapperForObject:(id)object -{ - JSC::JSObject* jsWrapper = m_cachedJSWrappers.get(object); - if (jsWrapper) - return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:m_context]; - - JSValue *wrapper; - if (class_isMetaClass(object_getClass(object))) - wrapper = [[self classInfoForClass:(Class)object] constructor]; - else { - JSObjCClassInfo* classInfo = [self classInfoForClass:[object class]]; - wrapper = [classInfo wrapperForObject:object]; - } - - // FIXME: https://bugs.webkit.org/show_bug.cgi?id=105891 - // This general approach to wrapper caching is pretty effective, but there are a couple of problems: - // (1) For immortal objects JSValues will effectively leak and this results in error output being logged - we should avoid adding associated objects to immortal objects. - // (2) A long lived object may rack up many JSValues. When the contexts are released these will unprotect the associated JavaScript objects, - // but still, would probably nicer if we made it so that only one associated object was required, broadcasting object dealloc. - JSC::ExecState* exec = toJS([m_context JSGlobalContextRef]); - jsWrapper = toJS(exec, valueInternalValue(wrapper)).toObject(exec); - m_cachedJSWrappers.set(object, jsWrapper); - return wrapper; -} - -- (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value -{ - JSValue *wrapper = static_cast<JSValue *>(NSMapGet(m_cachedObjCWrappers, value)); - if (!wrapper) { - wrapper = [[[JSValue alloc] initWithValue:value inContext:m_context] autorelease]; - NSMapInsert(m_cachedObjCWrappers, value, wrapper); - } - return wrapper; -} - -@end - -id tryUnwrapObjcObject(JSGlobalContextRef context, JSValueRef value) -{ - if (!JSValueIsObject(context, value)) - return nil; - JSValueRef exception = 0; - JSObjectRef object = JSValueToObject(context, value, &exception); - ASSERT(!exception); - if (toJS(object)->inherits(&JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::s_info)) - return (id)JSC::jsCast<JSC::JSAPIWrapperObject*>(toJS(object))->wrappedObject(); - if (id target = tryUnwrapBlock(object)) - return target; - return nil; -} - -Protocol *getJSExportProtocol() -{ - static Protocol *protocol = objc_getProtocol("JSExport"); - return protocol; -} - -Class getNSBlockClass() -{ - static Class cls = objc_getClass("NSBlock"); - return cls; -} - -#endif |