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