| /* |
| * 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 "JSCInlines.h" |
| #import "WeakGCMap.h" |
| #import <wtf/TCSpinLock.h> |
| #import <wtf/Vector.h> |
| #import <wtf/HashSet.h> |
| |
| #include <mach-o/dyld.h> |
| |
| static const int32_t webkitFirstVersionWithInitConstructorSupport = 0x21A0400; // 538.4.0 |
| |
| @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 bool constructorHasInstance(JSContextRef ctx, JSObjectRef constructorRef, JSValueRef possibleInstance, JSValueRef*) |
| { |
| JSC::ExecState* exec = toJS(ctx); |
| JSC::APIEntryShim entryShim(exec); |
| |
| JSC::JSObject* constructor = toJS(constructorRef); |
| JSC::JSValue instance = toJS(exec, possibleInstance); |
| return JSC::JSObject::defaultHasInstance(exec, instance, constructor->get(exec, exec->propertyNames().prototype)); |
| } |
| |
| 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]; |
| } |
| |
| static JSValue *constructorWithCustomBrand(JSContext *context, NSString *brand, Class cls) |
| { |
| JSClassDefinition definition; |
| definition = kJSClassDefinitionEmpty; |
| definition.className = [brand UTF8String]; |
| definition.hasInstance = constructorHasInstance; |
| 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 |
| }]; |
| } |
| |
| static bool isInitFamilyMethod(NSString *name) |
| { |
| NSUInteger i = 0; |
| |
| // Skip over initial underscores. |
| for (; i < [name length]; ++i) { |
| if ([name characterAtIndex:i] != '_') |
| break; |
| } |
| |
| // Match 'init'. |
| NSUInteger initIndex = 0; |
| NSString* init = @"init"; |
| for (; i < [name length] && initIndex < [init length]; ++i, ++initIndex) { |
| if ([name characterAtIndex:i] != [init characterAtIndex:initIndex]) |
| return false; |
| } |
| |
| // We didn't match all of 'init'. |
| if (initIndex < [init length]) |
| return false; |
| |
| // If we're at the end or the next character is a capital letter then this is an init-family selector. |
| return i == [name length] || [[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:[name characterAtIndex:i]]; |
| } |
| |
| static bool shouldSkipMethodWithName(NSString *name) |
| { |
| // For clients that don't support init-based constructors just copy |
| // over the init method as we would have before. |
| if (!supportsInitMethodConstructors()) |
| return false; |
| |
| // Skip over init family methods because we handle those specially |
| // for the purposes of hooking up the constructor correctly. |
| return isInitFamilyMethod(name); |
| } |
| |
| // 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 (shouldSkipMethodWithName(name)) |
| return; |
| |
| 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]; |
| } |
| |
| static JSValue *allocateConstructorForCustomClass(JSContext *context, const char* className, Class cls) |
| { |
| if (!supportsInitMethodConstructors()) |
| return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls); |
| |
| // For each protocol that the class implements, gather all of the init family methods into a hash table. |
| __block HashMap<String, Protocol *> initTable; |
| Protocol *exportProtocol = getJSExportProtocol(); |
| for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) { |
| forEachProtocolImplementingProtocol(currentClass, exportProtocol, ^(Protocol *protocol) { |
| forEachMethodInProtocol(protocol, YES, YES, ^(SEL selector, const char*) { |
| const char* name = sel_getName(selector); |
| if (!isInitFamilyMethod(@(name))) |
| return; |
| initTable.set(name, protocol); |
| }); |
| }); |
| } |
| |
| for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) { |
| __block unsigned numberOfInitsFound = 0; |
| __block SEL initMethod = 0; |
| __block Protocol *initProtocol = 0; |
| __block const char* types = 0; |
| forEachMethodInClass(currentClass, ^(Method method) { |
| SEL selector = method_getName(method); |
| const char* name = sel_getName(selector); |
| auto iter = initTable.find(name); |
| |
| if (iter == initTable.end()) |
| return; |
| |
| numberOfInitsFound++; |
| initMethod = selector; |
| initProtocol = iter->value; |
| types = method_getTypeEncoding(method); |
| }); |
| |
| if (!numberOfInitsFound) |
| continue; |
| |
| if (numberOfInitsFound > 1) { |
| NSLog(@"ERROR: Class %@ exported more than one init family method via JSExport. Class %@ will not have a callable JavaScript constructor function.", cls, cls); |
| break; |
| } |
| |
| JSObjectRef method = objCCallbackFunctionForInit(context, cls, initProtocol, initMethod, types); |
| return [JSValue valueWithJSValueRef:method inContext:context]; |
| } |
| return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls); |
| } |
| |
| - (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 = allocateConstructorForCustomClass(m_context, 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)) { |
| JSValue *constructor = [JSValue valueWithJSValueRef:method inContext:m_context]; |
| JSValue *prototype = [JSValue valueWithNewObjectInContext:m_context]; |
| putNonEnumerable(constructor, @"prototype", prototype); |
| putNonEnumerable(prototype, @"constructor", constructor); |
| return constructor; |
| } |
| } |
| |
| 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>::info())) |
| return (id)JSC::jsCast<JSC::JSAPIWrapperObject*>(toJS(object))->wrappedObject(); |
| if (id target = tryUnwrapConstructor(object)) |
| return target; |
| return nil; |
| } |
| |
| // This class ensures that the JSExport protocol is registered with the runtime. |
| NS_ROOT_CLASS @interface JSExport <JSExport> |
| @end |
| @implementation JSExport |
| @end |
| |
| bool supportsInitMethodConstructors() |
| { |
| static int32_t versionOfLinkTimeLibrary = 0; |
| if (!versionOfLinkTimeLibrary) |
| versionOfLinkTimeLibrary = NSVersionOfLinkTimeLibrary("JavaScriptCore"); |
| return versionOfLinkTimeLibrary >= webkitFirstVersionWithInitConstructorSupport; |
| } |
| |
| Protocol *getJSExportProtocol() |
| { |
| static Protocol *protocol = objc_getProtocol("JSExport"); |
| return protocol; |
| } |
| |
| Class getNSBlockClass() |
| { |
| static Class cls = objc_getClass("NSBlock"); |
| return cls; |
| } |
| |
| #endif |