REGRESSION(200114): Netflix app does not see ChromeCast
[WebKit-https.git] / Source / JavaScriptCore / API / JSWrapperMap.mm
1 /*
2  * Copyright (C) 2013-2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #import "JavaScriptCore.h"
28
29 #if JSC_OBJC_API_ENABLED
30 #import "APICast.h"
31 #import "JSAPIWrapperObject.h"
32 #import "JSCInlines.h"
33 #import "JSCallbackObject.h"
34 #import "JSContextInternal.h"
35 #import "JSWrapperMap.h"
36 #import "ObjCCallbackFunction.h"
37 #import "ObjcRuntimeExtras.h"
38 #import "WeakGCMap.h"
39 #import "WeakGCMapInlines.h"
40 #import <wtf/HashSet.h>
41 #import <wtf/Vector.h>
42 #import <wtf/spi/cocoa/NSMapTableSPI.h>
43 #import <wtf/spi/darwin/dyldSPI.h>
44
45 #include <mach-o/dyld.h>
46
47 #if PLATFORM(APPLETV)
48 #else
49 static const int32_t firstJavaScriptCoreVersionWithInitConstructorSupport = 0x21A0400; // 538.4.0
50 #if PLATFORM(IOS)
51 static const uint32_t firstSDKVersionWithInitConstructorSupport = DYLD_IOS_VERSION_10_0;
52 #elif PLATFORM(MAC)
53 static const uint32_t firstSDKVersionWithInitConstructorSupport = 0xA0A00; // OSX 10.10.0
54 #endif
55 #endif
56
57 @class JSObjCClassInfo;
58
59 @interface JSWrapperMap () 
60
61 - (JSObjCClassInfo*)classInfoForClass:(Class)cls;
62
63 @end
64
65 // Default conversion of selectors to property names.
66 // All semicolons are removed, lowercase letters following a semicolon are capitalized.
67 static NSString *selectorToPropertyName(const char* start)
68 {
69     // Use 'index' to check for colons, if there are none, this is easy!
70     const char* firstColon = strchr(start, ':');
71     if (!firstColon)
72         return [NSString stringWithUTF8String:start];
73
74     // 'header' is the length of string up to the first colon.
75     size_t header = firstColon - start;
76     // The new string needs to be long enough to hold 'header', plus the remainder of the string, excluding
77     // at least one ':', but including a '\0'. (This is conservative if there are more than one ':').
78     char* buffer = static_cast<char*>(malloc(header + strlen(firstColon + 1) + 1));
79     // Copy 'header' characters, set output to point to the end of this & input to point past the first ':'.
80     memcpy(buffer, start, header);
81     char* output = buffer + header;
82     const char* input = start + header + 1;
83
84     // On entry to the loop, we have already skipped over a ':' from the input.
85     while (true) {
86         char c;
87         // Skip over any additional ':'s. We'll leave c holding the next character after the
88         // last ':', and input pointing past c.
89         while ((c = *(input++)) == ':');
90         // Copy the character, converting to upper case if necessary.
91         // If the character we copy is '\0', then we're done!
92         if (!(*(output++) = toupper(c)))
93             goto done;
94         // Loop over characters other than ':'.
95         while ((c = *(input++)) != ':') {
96             // Copy the character.
97             // If the character we copy is '\0', then we're done!
98             if (!(*(output++) = c))
99                 goto done;
100         }
101         // If we get here, we've consumed a ':' - wash, rinse, repeat.
102     }
103 done:
104     NSString *result = [NSString stringWithUTF8String:buffer];
105     free(buffer);
106     return result;
107 }
108
109 static bool constructorHasInstance(JSContextRef ctx, JSObjectRef constructorRef, JSValueRef possibleInstance, JSValueRef*)
110 {
111     JSC::ExecState* exec = toJS(ctx);
112     JSC::JSLockHolder locker(exec);
113
114     JSC::JSObject* constructor = toJS(constructorRef);
115     JSC::JSValue instance = toJS(exec, possibleInstance);
116     return JSC::JSObject::defaultHasInstance(exec, instance, constructor->get(exec, exec->propertyNames().prototype));
117 }
118
119 static JSC::JSObject* makeWrapper(JSContextRef ctx, JSClassRef jsClass, id wrappedObject)
120 {
121     JSC::ExecState* exec = toJS(ctx);
122     JSC::JSLockHolder locker(exec);
123
124     ASSERT(jsClass);
125     JSC::JSCallbackObject<JSC::JSAPIWrapperObject>* object = JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::create(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->objcWrapperObjectStructure(), jsClass, 0);
126     object->setWrappedObject(wrappedObject);
127     if (JSC::JSObject* prototype = jsClass->prototype(exec))
128         object->setPrototypeDirect(exec->vm(), prototype);
129
130     return object;
131 }
132
133 // Make an object that is in all ways a completely vanilla JavaScript object,
134 // other than that it has a native brand set that will be displayed by the default
135 // Object.prototype.toString conversion.
136 static JSC::JSObject *objectWithCustomBrand(JSContext *context, NSString *brand, Class cls = 0)
137 {
138     JSClassDefinition definition;
139     definition = kJSClassDefinitionEmpty;
140     definition.className = [brand UTF8String];
141     JSClassRef classRef = JSClassCreate(&definition);
142     JSC::JSObject* result = makeWrapper([context JSGlobalContextRef], classRef, cls);
143     JSClassRelease(classRef);
144     return result;
145 }
146
147 static JSC::JSObject *constructorWithCustomBrand(JSContext *context, NSString *brand, Class cls)
148 {
149     JSClassDefinition definition;
150     definition = kJSClassDefinitionEmpty;
151     definition.className = [brand UTF8String];
152     definition.hasInstance = constructorHasInstance;
153     JSClassRef classRef = JSClassCreate(&definition);
154     JSC::JSObject* result = makeWrapper([context JSGlobalContextRef], classRef, cls);
155     JSClassRelease(classRef);
156     return result;
157 }
158
159 // Look for @optional properties in the prototype containing a selector to property
160 // name mapping, separated by a __JS_EXPORT_AS__ delimiter.
161 static NSMutableDictionary *createRenameMap(Protocol *protocol, BOOL isInstanceMethod)
162 {
163     NSMutableDictionary *renameMap = [[NSMutableDictionary alloc] init];
164
165     forEachMethodInProtocol(protocol, NO, isInstanceMethod, ^(SEL sel, const char*){
166         NSString *rename = @(sel_getName(sel));
167         NSRange range = [rename rangeOfString:@"__JS_EXPORT_AS__"];
168         if (range.location == NSNotFound)
169             return;
170         NSString *selector = [rename substringToIndex:range.location];
171         NSUInteger begin = range.location + range.length;
172         NSUInteger length = [rename length] - begin - 1;
173         NSString *name = [rename substringWithRange:(NSRange){ begin, length }];
174         renameMap[selector] = name;
175     });
176
177     return renameMap;
178 }
179
180 inline void putNonEnumerable(JSValue *base, NSString *propertyName, JSValue *value)
181 {
182     [base defineProperty:propertyName descriptor:@{
183         JSPropertyDescriptorValueKey: value,
184         JSPropertyDescriptorWritableKey: @YES,
185         JSPropertyDescriptorEnumerableKey: @NO,
186         JSPropertyDescriptorConfigurableKey: @YES
187     }];
188 }
189
190 static bool isInitFamilyMethod(NSString *name)
191 {
192     NSUInteger i = 0;
193
194     // Skip over initial underscores.
195     for (; i < [name length]; ++i) {
196         if ([name characterAtIndex:i] != '_')
197             break;
198     }
199
200     // Match 'init'.
201     NSUInteger initIndex = 0;
202     NSString* init = @"init";
203     for (; i < [name length] && initIndex < [init length]; ++i, ++initIndex) {
204         if ([name characterAtIndex:i] != [init characterAtIndex:initIndex])
205             return false;
206     }
207
208     // We didn't match all of 'init'.
209     if (initIndex < [init length])
210         return false;
211
212     // If we're at the end or the next character is a capital letter then this is an init-family selector.
213     return i == [name length] || [[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:[name characterAtIndex:i]]; 
214 }
215
216 static bool shouldSkipMethodWithName(NSString *name)
217 {
218     // For clients that don't support init-based constructors just copy 
219     // over the init method as we would have before.
220     if (!supportsInitMethodConstructors())
221         return false;
222
223     // Skip over init family methods because we handle those specially 
224     // for the purposes of hooking up the constructor correctly.
225     return isInitFamilyMethod(name);
226 }
227
228 // This method will iterate over the set of required methods in the protocol, and:
229 //  * Determine a property name (either via a renameMap or default conversion).
230 //  * If an accessorMap is provided, and contains this name, store the method in the map.
231 //  * Otherwise, if the object doesn't already contain a property with name, create it.
232 static void copyMethodsToObject(JSContext *context, Class objcClass, Protocol *protocol, BOOL isInstanceMethod, JSValue *object, NSMutableDictionary *accessorMethods = nil)
233 {
234     NSMutableDictionary *renameMap = createRenameMap(protocol, isInstanceMethod);
235
236     forEachMethodInProtocol(protocol, YES, isInstanceMethod, ^(SEL sel, const char* types){
237         const char* nameCStr = sel_getName(sel);
238         NSString *name = @(nameCStr);
239
240         if (shouldSkipMethodWithName(name))
241             return;
242
243         if (accessorMethods && accessorMethods[name]) {
244             JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types);
245             if (!method)
246                 return;
247             accessorMethods[name] = [JSValue valueWithJSValueRef:method inContext:context];
248         } else {
249             name = renameMap[name];
250             if (!name)
251                 name = selectorToPropertyName(nameCStr);
252             if ([object hasProperty:name])
253                 return;
254             JSObjectRef method = objCCallbackFunctionForMethod(context, objcClass, protocol, isInstanceMethod, sel, types);
255             if (method)
256                 putNonEnumerable(object, name, [JSValue valueWithJSValueRef:method inContext:context]);
257         }
258     });
259
260     [renameMap release];
261 }
262
263 static bool parsePropertyAttributes(objc_property_t property, char*& getterName, char*& setterName)
264 {
265     bool readonly = false;
266     unsigned attributeCount;
267     objc_property_attribute_t* attributes = property_copyAttributeList(property, &attributeCount);
268     if (attributeCount) {
269         for (unsigned i = 0; i < attributeCount; ++i) {
270             switch (*(attributes[i].name)) {
271             case 'G':
272                 getterName = strdup(attributes[i].value);
273                 break;
274             case 'S':
275                 setterName = strdup(attributes[i].value);
276                 break;
277             case 'R':
278                 readonly = true;
279                 break;
280             default:
281                 break;
282             }
283         }
284         free(attributes);
285     }
286     return readonly;
287 }
288
289 static char* makeSetterName(const char* name)
290 {
291     size_t nameLength = strlen(name);
292     char* setterName = (char*)malloc(nameLength + 5); // "set" Name ":\0"
293     setterName[0] = 's';
294     setterName[1] = 'e';
295     setterName[2] = 't';
296     setterName[3] = toupper(*name);
297     memcpy(setterName + 4, name + 1, nameLength - 1);
298     setterName[nameLength + 3] = ':';
299     setterName[nameLength + 4] = '\0';
300     return setterName;
301 }
302
303 static void copyPrototypeProperties(JSContext *context, Class objcClass, Protocol *protocol, JSValue *prototypeValue)
304 {
305     // First gather propreties into this list, then handle the methods (capturing the accessor methods).
306     struct Property {
307         const char* name;
308         char* getterName;
309         char* setterName;
310     };
311     __block Vector<Property> propertyList;
312
313     // Map recording the methods used as getters/setters.
314     NSMutableDictionary *accessorMethods = [NSMutableDictionary dictionary];
315
316     // Useful value.
317     JSValue *undefined = [JSValue valueWithUndefinedInContext:context];
318
319     forEachPropertyInProtocol(protocol, ^(objc_property_t property){
320         char* getterName = 0;
321         char* setterName = 0;
322         bool readonly = parsePropertyAttributes(property, getterName, setterName);
323         const char* name = property_getName(property);
324
325         // Add the names of the getter & setter methods to 
326         if (!getterName)
327             getterName = strdup(name);
328         accessorMethods[@(getterName)] = undefined;
329         if (!readonly) {
330             if (!setterName)
331                 setterName = makeSetterName(name);
332             accessorMethods[@(setterName)] = undefined;
333         }
334
335         // Add the properties to a list.
336         propertyList.append((Property){ name, getterName, setterName });
337     });
338
339     // Copy methods to the prototype, capturing accessors in the accessorMethods map.
340     copyMethodsToObject(context, objcClass, protocol, YES, prototypeValue, accessorMethods);
341
342     // Iterate the propertyList & generate accessor properties.
343     for (size_t i = 0; i < propertyList.size(); ++i) {
344         Property& property = propertyList[i];
345
346         JSValue *getter = accessorMethods[@(property.getterName)];
347         free(property.getterName);
348         ASSERT(![getter isUndefined]);
349
350         JSValue *setter = undefined;
351         if (property.setterName) {
352             setter = accessorMethods[@(property.setterName)];
353             free(property.setterName);
354             ASSERT(![setter isUndefined]);
355         }
356         
357         [prototypeValue defineProperty:@(property.name) descriptor:@{
358             JSPropertyDescriptorGetKey: getter,
359             JSPropertyDescriptorSetKey: setter,
360             JSPropertyDescriptorEnumerableKey: @NO,
361             JSPropertyDescriptorConfigurableKey: @YES
362         }];
363     }
364 }
365
366 @interface JSObjCClassInfo : NSObject {
367     JSContext *m_context;
368     Class m_class;
369     bool m_block;
370     JSClassRef m_classRef;
371     JSC::Weak<JSC::JSObject> m_prototype;
372     JSC::Weak<JSC::JSObject> m_constructor;
373 }
374
375 - (id)initWithContext:(JSContext *)context forClass:(Class)cls;
376 - (JSC::JSObject *)wrapperForObject:(id)object;
377 - (JSC::JSObject *)constructor;
378 - (JSC::JSObject *)prototype;
379
380 @end
381
382 @implementation JSObjCClassInfo
383
384 - (id)initWithContext:(JSContext *)context forClass:(Class)cls
385 {
386     self = [super init];
387     if (!self)
388         return nil;
389
390     const char* className = class_getName(cls);
391     m_context = context;
392     m_class = cls;
393     m_block = [cls isSubclassOfClass:getNSBlockClass()];
394     JSClassDefinition definition;
395     definition = kJSClassDefinitionEmpty;
396     definition.className = className;
397     m_classRef = JSClassCreate(&definition);
398
399     return self;
400 }
401
402 - (void)dealloc
403 {
404     JSClassRelease(m_classRef);
405     [super dealloc];
406 }
407
408 static JSC::JSObject* allocateConstructorForCustomClass(JSContext *context, const char* className, Class cls)
409 {
410     if (!supportsInitMethodConstructors())
411         return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls);
412
413     // For each protocol that the class implements, gather all of the init family methods into a hash table.
414     __block HashMap<String, Protocol *> initTable;
415     Protocol *exportProtocol = getJSExportProtocol();
416     for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) {
417         forEachProtocolImplementingProtocol(currentClass, exportProtocol, ^(Protocol *protocol) {
418             forEachMethodInProtocol(protocol, YES, YES, ^(SEL selector, const char*) {
419                 const char* name = sel_getName(selector);
420                 if (!isInitFamilyMethod(@(name)))
421                     return;
422                 initTable.set(name, protocol);
423             });
424         });
425     }
426
427     for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) {
428         __block unsigned numberOfInitsFound = 0;
429         __block SEL initMethod = 0;
430         __block Protocol *initProtocol = 0;
431         __block const char* types = 0;
432         forEachMethodInClass(currentClass, ^(Method method) {
433             SEL selector = method_getName(method);
434             const char* name = sel_getName(selector);
435             auto iter = initTable.find(name);
436
437             if (iter == initTable.end())
438                 return;
439
440             numberOfInitsFound++;
441             initMethod = selector;
442             initProtocol = iter->value;
443             types = method_getTypeEncoding(method);
444         });
445
446         if (!numberOfInitsFound)
447             continue;
448
449         if (numberOfInitsFound > 1) {
450             NSLog(@"ERROR: Class %@ exported more than one init family method via JSExport. Class %@ will not have a callable JavaScript constructor function.", cls, cls);
451             break;
452         }
453
454         JSObjectRef method = objCCallbackFunctionForInit(context, cls, initProtocol, initMethod, types);
455         return toJS(method);
456     }
457     return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls);
458 }
459
460 typedef std::pair<JSC::JSObject*, JSC::JSObject*> ConstructorPrototypePair;
461
462 - (ConstructorPrototypePair)allocateConstructorAndPrototype
463 {
464     JSObjCClassInfo* superClassInfo = [m_context.wrapperMap classInfoForClass:class_getSuperclass(m_class)];
465
466     ASSERT(!m_constructor || !m_prototype);
467     ASSERT((m_class == [NSObject class]) == !superClassInfo);
468
469     JSC::JSObject* jsPrototype = m_prototype.get();
470     JSC::JSObject* jsConstructor = m_constructor.get();
471
472     if (!superClassInfo) {
473         JSContextRef cContext = [m_context JSGlobalContextRef];
474         JSValue *constructor = m_context[@"Object"];
475         if (!jsConstructor)
476             jsConstructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0));
477
478         if (!jsPrototype) {
479             JSValue *prototype = constructor[@"prototype"];
480             jsPrototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0));
481         }
482     } else {
483         const char* className = class_getName(m_class);
484
485         // Create or grab the prototype/constructor pair.
486         if (!jsPrototype)
487             jsPrototype = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sPrototype", className]);
488
489         if (!jsConstructor)
490             jsConstructor = allocateConstructorForCustomClass(m_context, className, m_class);
491
492         JSValue* prototype = [JSValue valueWithJSValueRef:toRef(jsPrototype) inContext:m_context];
493         JSValue* constructor = [JSValue valueWithJSValueRef:toRef(jsConstructor) inContext:m_context];
494         putNonEnumerable(prototype, @"constructor", constructor);
495         putNonEnumerable(constructor, @"prototype", prototype);
496
497         Protocol *exportProtocol = getJSExportProtocol();
498         forEachProtocolImplementingProtocol(m_class, exportProtocol, ^(Protocol *protocol){
499             copyPrototypeProperties(m_context, m_class, protocol, prototype);
500             copyMethodsToObject(m_context, m_class, protocol, NO, constructor);
501         });
502
503         // Set [Prototype].
504         JSC::JSObject* superClassPrototype = [superClassInfo prototype];
505         JSObjectSetPrototype([m_context JSGlobalContextRef], toRef(jsPrototype), toRef(superClassPrototype));
506     }
507
508     m_prototype = jsPrototype;
509     m_constructor = jsConstructor;
510     return ConstructorPrototypePair(jsConstructor, jsPrototype);
511 }
512
513 - (JSC::JSObject*)wrapperForObject:(id)object
514 {
515     ASSERT([object isKindOfClass:m_class]);
516     ASSERT(m_block == [object isKindOfClass:getNSBlockClass()]);
517     if (m_block) {
518         if (JSObjectRef method = objCCallbackFunctionForBlock(m_context, object)) {
519             JSValue *constructor = [JSValue valueWithJSValueRef:method inContext:m_context];
520             JSValue *prototype = [JSValue valueWithNewObjectInContext:m_context];
521             putNonEnumerable(constructor, @"prototype", prototype);
522             putNonEnumerable(prototype, @"constructor", constructor);
523             return toJS(method);
524         }
525     }
526
527     JSC::JSObject* prototype = [self prototype];
528
529     JSC::JSObject* wrapper = makeWrapper([m_context JSGlobalContextRef], m_classRef, object);
530     JSObjectSetPrototype([m_context JSGlobalContextRef], toRef(wrapper), toRef(prototype));
531     return wrapper;
532 }
533
534 - (JSC::JSObject*)constructor
535 {
536     JSC::JSObject* constructor = m_constructor.get();
537     if (!constructor)
538         constructor = [self allocateConstructorAndPrototype].first;
539     ASSERT(!!constructor);
540     return constructor;
541 }
542
543 - (JSC::JSObject*)prototype
544 {
545     JSC::JSObject* prototype = m_prototype.get();
546     if (!prototype)
547         prototype = [self allocateConstructorAndPrototype].second;
548     ASSERT(!!prototype);
549     return prototype;
550 }
551
552 @end
553
554 @implementation JSWrapperMap {
555     JSContext *m_context;
556     NSMutableDictionary *m_classMap;
557     std::unique_ptr<JSC::WeakGCMap<id, JSC::JSObject>> m_cachedJSWrappers;
558     NSMapTable *m_cachedObjCWrappers;
559 }
560
561 - (id)initWithContext:(JSContext *)context
562 {
563     self = [super init];
564     if (!self)
565         return nil;
566
567     NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality;
568     NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
569     m_cachedObjCWrappers = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0];
570
571     m_cachedJSWrappers = std::make_unique<JSC::WeakGCMap<id, JSC::JSObject>>(toJS([context JSGlobalContextRef])->vm());
572
573     m_context = context;
574     m_classMap = [[NSMutableDictionary alloc] init];
575     return self;
576 }
577
578 - (void)dealloc
579 {
580     [m_cachedObjCWrappers release];
581     [m_classMap release];
582     [super dealloc];
583 }
584
585 - (JSObjCClassInfo*)classInfoForClass:(Class)cls
586 {
587     if (!cls)
588         return nil;
589
590     // Check if we've already created a JSObjCClassInfo for this Class.
591     if (JSObjCClassInfo* classInfo = (JSObjCClassInfo*)m_classMap[cls])
592         return classInfo;
593
594     // Skip internal classes beginning with '_' - just copy link to the parent class's info.
595     if ('_' == *class_getName(cls))
596         return m_classMap[cls] = [self classInfoForClass:class_getSuperclass(cls)];
597
598     return m_classMap[cls] = [[[JSObjCClassInfo alloc] initWithContext:m_context forClass:cls] autorelease];
599 }
600
601 - (JSValue *)jsWrapperForObject:(id)object
602 {
603     JSC::JSObject* jsWrapper = m_cachedJSWrappers->get(object);
604     if (jsWrapper)
605         return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:m_context];
606
607     if (class_isMetaClass(object_getClass(object)))
608         jsWrapper = [[self classInfoForClass:(Class)object] constructor];
609     else {
610         JSObjCClassInfo* classInfo = [self classInfoForClass:[object class]];
611         jsWrapper = [classInfo wrapperForObject:object];
612     }
613
614     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=105891
615     // This general approach to wrapper caching is pretty effective, but there are a couple of problems:
616     // (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.
617     // (2) A long lived object may rack up many JSValues. When the contexts are released these will unprotect the associated JavaScript objects,
618     //     but still, would probably nicer if we made it so that only one associated object was required, broadcasting object dealloc.
619     m_cachedJSWrappers->set(object, jsWrapper);
620     return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:m_context];
621 }
622
623 - (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value
624 {
625     JSValue *wrapper = static_cast<JSValue *>(NSMapGet(m_cachedObjCWrappers, value));
626     if (!wrapper) {
627         wrapper = [[[JSValue alloc] initWithValue:value inContext:m_context] autorelease];
628         NSMapInsert(m_cachedObjCWrappers, value, wrapper);
629     }
630     return wrapper;
631 }
632
633 @end
634
635 id tryUnwrapObjcObject(JSGlobalContextRef context, JSValueRef value)
636 {
637     if (!JSValueIsObject(context, value))
638         return nil;
639     JSValueRef exception = 0;
640     JSObjectRef object = JSValueToObject(context, value, &exception);
641     ASSERT(!exception);
642     JSC::JSLockHolder locker(toJS(context));
643     if (toJS(object)->inherits(JSC::JSCallbackObject<JSC::JSAPIWrapperObject>::info()))
644         return (id)JSC::jsCast<JSC::JSAPIWrapperObject*>(toJS(object))->wrappedObject();
645     if (id target = tryUnwrapConstructor(object))
646         return target;
647     return nil;
648 }
649
650 // This class ensures that the JSExport protocol is registered with the runtime.
651 NS_ROOT_CLASS @interface JSExport <JSExport>
652 @end
653 @implementation JSExport
654 @end
655
656 bool supportsInitMethodConstructors()
657 {
658 #if PLATFORM(APPLETV)
659     // There are no old clients on Apple TV, so there's no need for backwards compatibility.
660     return true;
661 #else
662     // First check to see the version of JavaScriptCore we directly linked against.
663     static int32_t versionOfLinkTimeJavaScriptCore = 0;
664     if (!versionOfLinkTimeJavaScriptCore)
665         versionOfLinkTimeJavaScriptCore = NSVersionOfLinkTimeLibrary("JavaScriptCore");
666     // Only do the link time version comparison if we linked directly with JavaScriptCore
667     if (versionOfLinkTimeJavaScriptCore != -1)
668         return versionOfLinkTimeJavaScriptCore >= firstJavaScriptCoreVersionWithInitConstructorSupport;
669
670     // If we didn't link directly with JavaScriptCore,
671     // base our check on what SDK was used to build the application.
672     static uint32_t programSDKVersion = 0;
673     if (!programSDKVersion)
674         programSDKVersion = dyld_get_program_sdk_version();
675
676     return programSDKVersion >= firstSDKVersionWithInitConstructorSupport;
677 #endif
678 }
679
680 Protocol *getJSExportProtocol()
681 {
682     static Protocol *protocol = objc_getProtocol("JSExport");
683     return protocol;
684 }
685
686 Class getNSBlockClass()
687 {
688     static Class cls = objc_getClass("NSBlock");
689     return cls;
690 }
691
692 #endif