/* * Copyright (C) 2013-2015, 2017 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 "APIUtils.h" #import "JSAPIWrapperObject.h" #import "JSCInlines.h" #import "JSCallbackObject.h" #import "JSContextInternal.h" #import "JSWrapperMap.h" #import "ObjCCallbackFunction.h" #ifdef DARLING #import "ObjCRuntimeExtras.h" #else #import "ObjcRuntimeExtras.h" #endif #import "ObjectConstructor.h" #import "WeakGCMap.h" #import "WeakGCMapInlines.h" #import #import #include #if PLATFORM(APPLETV) #else static const int32_t firstJavaScriptCoreVersionWithInitConstructorSupport = 0x21A0400; // 538.4.0 #if PLATFORM(IOS_FAMILY) static const uint32_t firstSDKVersionWithInitConstructorSupport = DYLD_IOS_VERSION_10_0; #elif PLATFORM(MAC) static const uint32_t firstSDKVersionWithInitConstructorSupport = 0xA0A00; // OSX 10.10.0 #endif #endif @class JSObjCClassInfo; @interface JSWrapperMap () - (JSObjCClassInfo*)classInfoForClass:(Class)cls; @end static const constexpr unsigned InitialBufferSize { 256 }; // 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 = strchr(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 ':'). Vector buffer(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.data(), start, header); char* output = buffer.data() + 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++) = toASCIIUpper(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: return [NSString stringWithUTF8String:buffer.data()]; } static bool constructorHasInstance(JSContextRef ctx, JSObjectRef constructorRef, JSValueRef possibleInstance, JSValueRef*) { JSC::ExecState* exec = toJS(ctx); JSC::VM& vm = exec->vm(); JSC::JSLockHolder locker(vm); JSC::JSObject* constructor = toJS(constructorRef); JSC::JSValue instance = toJS(exec, possibleInstance); return JSC::JSObject::defaultHasInstance(exec, instance, constructor->get(exec, vm.propertyNames->prototype)); } static JSC::JSObject* makeWrapper(JSContextRef ctx, JSClassRef jsClass, id wrappedObject) { JSC::ExecState* exec = toJS(ctx); JSC::VM& vm = exec->vm(); JSC::JSLockHolder locker(vm); ASSERT(jsClass); JSC::JSCallbackObject* object = JSC::JSCallbackObject::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->objcWrapperObjectStructure(), jsClass, 0); object->setWrappedObject((__bridge void*)wrappedObject); if (JSC::JSObject* prototype = jsClass->prototype(exec)) object->setPrototypeDirect(vm, prototype); return 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 JSC::JSObject *objectWithCustomBrand(JSContext *context, NSString *brand, Class cls = 0) { JSClassDefinition definition; definition = kJSClassDefinitionEmpty; definition.className = [brand UTF8String]; JSClassRef classRef = JSClassCreate(&definition); JSC::JSObject* result = makeWrapper([context JSGlobalContextRef], classRef, cls); JSClassRelease(classRef); return result; } static JSC::JSObject *constructorWithCustomBrand(JSContext *context, NSString *brand, Class cls) { JSClassDefinition definition; definition = kJSClassDefinitionEmpty; definition.className = [brand UTF8String]; definition.hasInstance = constructorHasInstance; JSClassRef classRef = JSClassCreate(&definition); JSC::JSObject* result = makeWrapper([context JSGlobalContextRef], classRef, cls); JSClassRelease(classRef); return result; } // 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(JSContext *context, JSValue *base, NSString *propertyName, JSValue *value) { if (![base isObject]) return; JSC::ExecState* exec = toJS([context JSGlobalContextRef]); JSC::VM& vm = exec->vm(); JSC::JSLockHolder locker(vm); auto scope = DECLARE_CATCH_SCOPE(vm); JSC::JSObject* baseObject = JSC::asObject(toJS(exec, [base JSValueRef])); auto name = OpaqueJSString::tryCreate(propertyName); if (!name) return; JSC::PropertyDescriptor descriptor; descriptor.setValue(toJS(exec, [value JSValueRef])); descriptor.setEnumerable(false); descriptor.setConfigurable(true); descriptor.setWritable(true); bool shouldThrow = false; baseObject->methodTable(vm)->defineOwnProperty(baseObject, exec, name->identifier(&vm), descriptor, shouldThrow); JSValueRef exception = 0; if (handleExceptionIfNeeded(scope, exec, &exception) == ExceptionStatus::DidThrow) [context valueFromNotifyException:exception]; } 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); auto exec = toJS([context JSGlobalContextRef]); JSValue *existingMethod = object[name]; // ObjCCallbackFunction does a dynamic lookup for the // selector before calling the method. In order to save // memory we only put one callback object in any givin // prototype chain. However, to handle the client trying // to override normal builtins e.g. "toString" we check if // the existing value on the prototype chain is an ObjC // callback already. if ([existingMethod isObject] && JSC::jsDynamicCast(exec->vm(), toJS(exec, [existingMethod JSValueRef]))) return; JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types); if (method) putNonEnumerable(context, object, name, [JSValue valueWithJSValueRef:method inContext:context]); } }); [renameMap release]; } struct Property { const char* name; RetainPtr getterName; RetainPtr setterName; }; static bool parsePropertyAttributes(objc_property_t objcProperty, Property& property) { bool readonly = false; unsigned attributeCount; auto attributes = adoptSystem(property_copyAttributeList(objcProperty, &attributeCount)); if (attributeCount) { for (unsigned i = 0; i < attributeCount; ++i) { switch (*(attributes[i].name)) { case 'G': property.getterName = @(attributes[i].value); break; case 'S': property.setterName = @(attributes[i].value); break; case 'R': readonly = true; break; default: break; } } } return readonly; } static RetainPtr makeSetterName(const char* name) { size_t nameLength = strlen(name); // "set" Name ":\0" => nameLength + 5. Vector buffer(nameLength + 5); buffer[0] = 's'; buffer[1] = 'e'; buffer[2] = 't'; buffer[3] = toASCIIUpper(*name); memcpy(buffer.data() + 4, name + 1, nameLength - 1); buffer[nameLength + 3] = ':'; buffer[nameLength + 4] = '\0'; return @(buffer.data()); } 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). __block Vector 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 objcProperty) { const char* name = property_getName(objcProperty); Property property { name, nullptr, nullptr }; bool readonly = parsePropertyAttributes(objcProperty, property); // Add the names of the getter & setter methods to if (!property.getterName) property.getterName = @(name); accessorMethods[property.getterName.get()] = undefined; if (!readonly) { if (!property.setterName) property.setterName = makeSetterName(name); accessorMethods[property.setterName.get()] = undefined; } // Add the properties to a list. propertyList.append(WTFMove(property)); }); // 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 (auto& property : propertyList) { JSValue* getter = accessorMethods[property.getterName.get()]; ASSERT(![getter isUndefined]); JSValue* setter = undefined; if (property.setterName) { setter = accessorMethods[property.setterName.get()]; ASSERT(![setter isUndefined]); } [prototypeValue defineProperty:@(property.name) descriptor:@{ JSPropertyDescriptorGetKey: getter, JSPropertyDescriptorSetKey: setter, JSPropertyDescriptorEnumerableKey: @NO, JSPropertyDescriptorConfigurableKey: @YES }]; } } @interface JSObjCClassInfo : NSObject { Class m_class; bool m_block; JSClassRef m_classRef; JSC::Weak m_prototype; JSC::Weak m_constructor; JSC::Weak m_structure; } - (instancetype)initForClass:(Class)cls; - (JSC::JSObject *)wrapperForObject:(id)object inContext:(JSContext *)context; - (JSC::JSObject *)constructorInContext:(JSContext *)context; - (JSC::JSObject *)prototypeInContext:(JSContext *)context; @end @implementation JSObjCClassInfo - (instancetype)initForClass:(Class)cls { self = [super init]; if (!self) return nil; const char* className = class_getName(cls); m_class = cls; m_block = [cls isSubclassOfClass:getNSBlockClass()]; JSClassDefinition definition; definition = kJSClassDefinitionEmpty; definition.className = className; m_classRef = JSClassCreate(&definition); return self; } - (void)dealloc { JSClassRelease(m_classRef); [super dealloc]; } static JSC::JSObject* 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 initTable; Protocol *exportProtocol = getJSExportProtocol(); for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) { forEachProtocolImplementingProtocol(currentClass, exportProtocol, ^(Protocol *protocol, bool&) { forEachMethodInProtocol(protocol, YES, YES, ^(SEL selector, const char*) { const char* name = sel_getName(selector); if (!isInitFamilyMethod(@(name))) return; initTable.set(name, (__bridge CFTypeRef)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 = (__bridge Protocol *)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 toJS(method); } return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls); } typedef std::pair ConstructorPrototypePair; - (ConstructorPrototypePair)allocateConstructorAndPrototypeInContext:(JSContext *)context { JSObjCClassInfo* superClassInfo = [context.wrapperMap classInfoForClass:class_getSuperclass(m_class)]; ASSERT(!m_constructor || !m_prototype); ASSERT((m_class == [NSObject class]) == !superClassInfo); JSC::JSObject* jsPrototype = m_prototype.get(); JSC::JSObject* jsConstructor = m_constructor.get(); if (!superClassInfo) { JSC::JSGlobalObject* globalObject = toJSGlobalObject([context JSGlobalContextRef]); if (!jsConstructor) jsConstructor = globalObject->objectConstructor(); if (!jsPrototype) jsPrototype = globalObject->objectPrototype(); } else { const char* className = class_getName(m_class); // Create or grab the prototype/constructor pair. if (!jsPrototype) jsPrototype = objectWithCustomBrand(context, [NSString stringWithFormat:@"%sPrototype", className]); if (!jsConstructor) jsConstructor = allocateConstructorForCustomClass(context, className, m_class); JSValue* prototype = [JSValue valueWithJSValueRef:toRef(jsPrototype) inContext:context]; JSValue* constructor = [JSValue valueWithJSValueRef:toRef(jsConstructor) inContext:context]; putNonEnumerable(context, prototype, @"constructor", constructor); putNonEnumerable(context, constructor, @"prototype", prototype); Protocol *exportProtocol = getJSExportProtocol(); forEachProtocolImplementingProtocol(m_class, exportProtocol, ^(Protocol *protocol, bool&){ copyPrototypeProperties(context, m_class, protocol, prototype); copyMethodsToObject(context, m_class, protocol, NO, constructor); }); // Set [Prototype]. JSC::JSObject* superClassPrototype = [superClassInfo prototypeInContext:context]; JSObjectSetPrototype([context JSGlobalContextRef], toRef(jsPrototype), toRef(superClassPrototype)); } m_prototype = jsPrototype; m_constructor = jsConstructor; return ConstructorPrototypePair(jsConstructor, jsPrototype); } - (JSC::JSObject*)wrapperForObject:(id)object inContext:(JSContext *)context { ASSERT([object isKindOfClass:m_class]); ASSERT(m_block == [object isKindOfClass:getNSBlockClass()]); if (m_block) { if (JSObjectRef method = objCCallbackFunctionForBlock(context, object)) { JSValue *constructor = [JSValue valueWithJSValueRef:method inContext:context]; JSValue *prototype = [JSValue valueWithNewObjectInContext:context]; putNonEnumerable(context, constructor, @"prototype", prototype); putNonEnumerable(context, prototype, @"constructor", constructor); return toJS(method); } } JSC::Structure* structure = [self structureInContext:context]; JSC::ExecState* exec = toJS([context JSGlobalContextRef]); JSC::VM& vm = exec->vm(); JSC::JSLockHolder locker(vm); auto wrapper = JSC::JSCallbackObject::create(exec, exec->lexicalGlobalObject(), structure, m_classRef, 0); wrapper->setWrappedObject((__bridge void*)object); return wrapper; } - (JSC::JSObject*)constructorInContext:(JSContext *)context { JSC::JSObject* constructor = m_constructor.get(); if (!constructor) constructor = [self allocateConstructorAndPrototypeInContext:context].first; ASSERT(!!constructor); return constructor; } - (JSC::JSObject*)prototypeInContext:(JSContext *)context { JSC::JSObject* prototype = m_prototype.get(); if (!prototype) prototype = [self allocateConstructorAndPrototypeInContext:context].second; ASSERT(!!prototype); return prototype; } - (JSC::Structure*)structureInContext:(JSContext *)context { JSC::Structure* structure = m_structure.get(); if (structure) return structure; JSC::ExecState* exec = toJS([context JSGlobalContextRef]); JSC::JSGlobalObject* globalObject = toJSGlobalObject([context JSGlobalContextRef]); JSC::JSObject* prototype = [self prototypeInContext:context]; m_structure = JSC::JSCallbackObject::createStructure(exec->vm(), globalObject, prototype); return m_structure.get(); } @end @implementation JSWrapperMap { NSMutableDictionary *m_classMap; std::unique_ptr> m_cachedJSWrappers; NSMapTable *m_cachedObjCWrappers; } - (instancetype)initWithGlobalContextRef:(JSGlobalContextRef)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_cachedJSWrappers = std::make_unique>(toJS(context)->vm()); ASSERT(!toJSGlobalObject(context)->wrapperMap()); toJSGlobalObject(context)->setWrapperMap(self); 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)) { bool conformsToExportProtocol = false; forEachProtocolImplementingProtocol(cls, getJSExportProtocol(), [&conformsToExportProtocol](Protocol *, bool& stop) { conformsToExportProtocol = true; stop = true; }); if (!conformsToExportProtocol) return m_classMap[cls] = [self classInfoForClass:class_getSuperclass(cls)]; } return m_classMap[cls] = [[[JSObjCClassInfo alloc] initForClass:cls] autorelease]; } - (JSValue *)jsWrapperForObject:(id)object inContext:(JSContext *)context { ASSERT(toJSGlobalObject([context JSGlobalContextRef])->wrapperMap() == self); JSC::JSObject* jsWrapper = m_cachedJSWrappers->get(object); if (jsWrapper) return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:context]; if (class_isMetaClass(object_getClass(object))) jsWrapper = [[self classInfoForClass:(Class)object] constructorInContext:context]; else { JSObjCClassInfo* classInfo = [self classInfoForClass:[object class]]; jsWrapper = [classInfo wrapperForObject:object inContext:context]; } // 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. m_cachedJSWrappers->set(object, jsWrapper); return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:context]; } - (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value inContext:context { ASSERT(toJSGlobalObject([context JSGlobalContextRef])->wrapperMap() == self); JSValue *wrapper = (__bridge JSValue *)NSMapGet(m_cachedObjCWrappers, value); if (!wrapper) { wrapper = [[[JSValue alloc] initWithValue:value inContext:context] autorelease]; NSMapInsert(m_cachedObjCWrappers, value, (__bridge void*)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); JSC::JSLockHolder locker(toJS(context)); JSC::VM& vm = toJS(context)->vm(); if (toJS(object)->inherits>(vm)) return (__bridge id)JSC::jsCast(toJS(object))->wrappedObject(); if (id target = tryUnwrapConstructor(&vm, object)) return target; return nil; } // This class ensures that the JSExport protocol is registered with the runtime. NS_ROOT_CLASS @interface JSExport @end @implementation JSExport @end bool supportsInitMethodConstructors() { #if PLATFORM(APPLETV) // There are no old clients on Apple TV, so there's no need for backwards compatibility. return true; #else // First check to see the version of JavaScriptCore we directly linked against. static int32_t versionOfLinkTimeJavaScriptCore = 0; if (!versionOfLinkTimeJavaScriptCore) versionOfLinkTimeJavaScriptCore = NSVersionOfLinkTimeLibrary("JavaScriptCore"); // Only do the link time version comparison if we linked directly with JavaScriptCore if (versionOfLinkTimeJavaScriptCore != -1) return versionOfLinkTimeJavaScriptCore >= firstJavaScriptCoreVersionWithInitConstructorSupport; // If we didn't link directly with JavaScriptCore, // base our check on what SDK was used to build the application. static uint32_t programSDKVersion = 0; if (!programSDKVersion) programSDKVersion = dyld_get_program_sdk_version(); return programSDKVersion >= firstSDKVersionWithInitConstructorSupport; #endif } Protocol *getJSExportProtocol() { static Protocol *protocol = objc_getProtocol("JSExport"); return protocol; } Class getNSBlockClass() { static Class cls = objc_getClass("NSBlock"); return cls; } #endif