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