| /* |
| * Copyright (C) 2013-2019 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. |
| */ |
| |
| #import "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" |
| #import "ObjcRuntimeExtras.h" |
| #import "ObjectConstructor.h" |
| #import "WeakGCMap.h" |
| #import "WeakGCMapInlines.h" |
| #import <wtf/Vector.h> |
| |
| #if PLATFORM(COCOA) |
| #import <wtf/cocoa/RuntimeApplicationChecksCocoa.h> |
| #endif |
| |
| #import <mach-o/dyld.h> |
| |
| #if PLATFORM(APPLETV) |
| #else |
| static constexpr int32_t firstJavaScriptCoreVersionWithInitConstructorSupport = 0x21A0400; // 538.4.0 |
| #if PLATFORM(IOS_FAMILY) |
| static constexpr uint32_t firstSDKVersionWithInitConstructorSupport = DYLD_IOS_VERSION_10_0; |
| #elif PLATFORM(MAC) |
| static constexpr uint32_t firstSDKVersionWithInitConstructorSupport = 0xA0A00; // OSX 10.10.0 |
| #endif |
| #endif |
| |
| @class JSObjCClassInfo; |
| |
| @interface JSWrapperMap () |
| |
| - (JSObjCClassInfo*)classInfoForClass:(Class)cls; |
| |
| @end |
| |
| static 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<char, InitialBufferSize> 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::JSGlobalObject* globalObject = toJS(ctx); |
| JSC::VM& vm = globalObject->vm(); |
| JSC::JSLockHolder locker(vm); |
| |
| JSC::JSObject* constructor = toJS(constructorRef); |
| JSC::JSValue instance = toJS(globalObject, possibleInstance); |
| return JSC::JSObject::defaultHasInstance(globalObject, instance, constructor->get(globalObject, vm.propertyNames->prototype)); |
| } |
| |
| static JSC::JSObject* makeWrapper(JSContextRef ctx, JSClassRef jsClass, id wrappedObject) |
| { |
| JSC::JSGlobalObject* globalObject = toJS(ctx); |
| JSC::VM& vm = globalObject->vm(); |
| JSC::JSLockHolder locker(vm); |
| |
| ASSERT(jsClass); |
| JSC::JSCallbackObject<JSC::JSAPIWrapperObject>* object = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(globalObject, globalObject->objcWrapperObjectStructure(), jsClass, 0); |
| object->setWrappedObject((__bridge void*)wrappedObject); |
| if (JSC::JSObject* prototype = jsClass->prototype(globalObject)) |
| 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::JSGlobalObject* globalObject = toJS([context JSGlobalContextRef]); |
| JSC::VM& vm = globalObject->vm(); |
| JSC::JSLockHolder locker(vm); |
| auto scope = DECLARE_CATCH_SCOPE(vm); |
| |
| JSC::JSObject* baseObject = JSC::asObject(toJS(globalObject, [base JSValueRef])); |
| auto name = OpaqueJSString::tryCreate(propertyName); |
| if (!name) |
| return; |
| |
| JSC::PropertyDescriptor descriptor; |
| descriptor.setValue(toJS(globalObject, [value JSValueRef])); |
| descriptor.setEnumerable(false); |
| descriptor.setConfigurable(true); |
| descriptor.setWritable(true); |
| bool shouldThrow = false; |
| baseObject->methodTable(vm)->defineOwnProperty(baseObject, globalObject, name->identifier(&vm), descriptor, shouldThrow); |
| |
| JSValueRef exception = 0; |
| if (handleExceptionIfNeeded(scope, [context JSGlobalContextRef], &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); |
| JSC::JSGlobalObject* globalObject = 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<JSC::ObjCCallbackFunction*>(globalObject->vm(), toJS(globalObject, [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<NSString> getterName; |
| RetainPtr<NSString> setterName; |
| }; |
| |
| static bool parsePropertyAttributes(objc_property_t objcProperty, Property& property) |
| { |
| bool readonly = false; |
| unsigned attributeCount; |
| auto attributes = adoptSystem<objc_property_attribute_t[]>(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<NSString> makeSetterName(const char* name) |
| { |
| size_t nameLength = strlen(name); |
| // "set" Name ":\0" => nameLength + 5. |
| Vector<char, 128> 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<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 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; |
| NakedPtr<OpaqueJSClass> m_classRef; |
| JSC::Weak<JSC::JSObject> m_prototype; |
| JSC::Weak<JSC::JSObject> m_constructor; |
| JSC::Weak<JSC::Structure> 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.get()); |
| [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<String, CFTypeRef> 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<JSC::JSObject*, JSC::JSObject*> 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::JSGlobalObject* globalObject = toJS([context JSGlobalContextRef]); |
| JSC::VM& vm = globalObject->vm(); |
| JSC::JSLockHolder locker(vm); |
| |
| auto wrapper = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(globalObject, 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::JSGlobalObject* globalObject = toJSGlobalObject([context JSGlobalContextRef]); |
| JSC::JSObject* prototype = [self prototypeInContext:context]; |
| m_structure = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::createStructure(globalObject->vm(), globalObject, prototype); |
| |
| return m_structure.get(); |
| } |
| |
| @end |
| |
| @implementation JSWrapperMap { |
| NSMutableDictionary *m_classMap; |
| std::unique_ptr<JSC::WeakGCMap<__unsafe_unretained id, JSC::JSObject>> 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 = makeUnique<JSC::WeakGCMap<__unsafe_unretained id, JSC::JSObject>>(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<JSC::JSCallbackObject<JSC::JSAPIWrapperObject>>(vm)) |
| return (__bridge id)JSC::jsCast<JSC::JSAPIWrapperObject*>(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 <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 = applicationSDKVersion(); |
| |
| 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 |