]>
Commit | Line | Data |
---|---|---|
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 | 47 | static 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. | |
59 | static 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 | } | |
95 | done: | |
96 | NSString *result = [NSString stringWithUTF8String:buffer]; | |
97 | free(buffer); | |
98 | return result; | |
99 | } | |
100 | ||
12899fa2 A |
101 | static 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 | 111 | static 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 | 128 | static 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 | 139 | static 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. | |
153 | static 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 | ||
172 | inline 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 |
182 | static 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 | ||
208 | static 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. | |
224 | static 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 | ||
255 | static 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 | ||
281 | static 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 | ||
295 | static 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 | 400 | static 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 |
452 | typedef 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 | ||
627 | id 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. |
643 | NS_ROOT_CLASS @interface JSExport <JSExport> | |
644 | @end | |
645 | @implementation JSExport | |
646 | @end | |
647 | ||
648 | bool 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 |
661 | Protocol *getJSExportProtocol() |
662 | { | |
663 | static Protocol *protocol = objc_getProtocol("JSExport"); | |
664 | return protocol; | |
665 | } | |
666 | ||
667 | Class getNSBlockClass() | |
668 | { | |
669 | static Class cls = objc_getClass("NSBlock"); | |
670 | return cls; | |
671 | } | |
672 | ||
673 | #endif |