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 46static 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. 58static 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 } 94done: 95 NSString *result = [NSString stringWithUTF8String:buffer]; 96 free(buffer); 97 return result; 98} 99 100static 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 110static 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. 127static 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 138static 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. 152static 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 171inline 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 181static 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 207static 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. 223static 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 254static 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 280static 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 294static 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 400static 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 633id 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. 649NS_ROOT_CLASS @interface JSExport <JSExport> 650@end 651@implementation JSExport 652@end 653 654bool supportsInitMethodConstructors() 655{ 656 static int32_t versionOfLinkTimeLibrary = 0; 657 if (!versionOfLinkTimeLibrary) 658 versionOfLinkTimeLibrary = NSVersionOfLinkTimeLibrary("JavaScriptCore"); 659 return versionOfLinkTimeLibrary >= webkitFirstVersionWithInitConstructorSupport; 660} 661 662Protocol *getJSExportProtocol() 663{ 664 static Protocol *protocol = objc_getProtocol("JSExport"); 665 return protocol; 666} 667 668Class getNSBlockClass() 669{ 670 static Class cls = objc_getClass("NSBlock"); 671 return cls; 672} 673 674#endif 675