| 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 |