]>
Commit | Line | Data |
---|---|---|
93a37866 A |
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 | |
81345200 | 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
93a37866 A |
24 | */ |
25 | ||
26 | #include "config.h" | |
27 | ||
28 | #import "APICast.h" | |
93a37866 A |
29 | #import "DateInstance.h" |
30 | #import "Error.h" | |
31 | #import "JavaScriptCore.h" | |
32 | #import "JSContextInternal.h" | |
33 | #import "JSVirtualMachineInternal.h" | |
34 | #import "JSValueInternal.h" | |
35 | #import "JSWrapperMap.h" | |
36 | #import "ObjcRuntimeExtras.h" | |
81345200 | 37 | #import "JSCInlines.h" |
93a37866 | 38 | #import "JSCJSValue.h" |
81345200 | 39 | #import "Strong.h" |
12899fa2 | 40 | #import "StrongInlines.h" |
93a37866 A |
41 | #import <wtf/HashMap.h> |
42 | #import <wtf/HashSet.h> | |
12899fa2 | 43 | #import <wtf/ObjcRuntimeExtras.h> |
93a37866 A |
44 | #import <wtf/Vector.h> |
45 | #import <wtf/TCSpinLock.h> | |
46 | #import <wtf/text/WTFString.h> | |
47 | #import <wtf/text/StringHash.h> | |
48 | ||
81345200 A |
49 | #if ENABLE(REMOTE_INSPECTOR) |
50 | #import "CallFrame.h" | |
51 | #import "JSGlobalObject.h" | |
52 | #import "JSGlobalObjectInspectorController.h" | |
53 | #endif | |
54 | ||
93a37866 A |
55 | #if JSC_OBJC_API_ENABLED |
56 | ||
57 | NSString * const JSPropertyDescriptorWritableKey = @"writable"; | |
58 | NSString * const JSPropertyDescriptorEnumerableKey = @"enumerable"; | |
59 | NSString * const JSPropertyDescriptorConfigurableKey = @"configurable"; | |
60 | NSString * const JSPropertyDescriptorValueKey = @"value"; | |
61 | NSString * const JSPropertyDescriptorGetKey = @"get"; | |
62 | NSString * const JSPropertyDescriptorSetKey = @"set"; | |
63 | ||
64 | @implementation JSValue { | |
65 | JSValueRef m_value; | |
66 | } | |
67 | ||
68 | - (JSValueRef)JSValueRef | |
69 | { | |
70 | return m_value; | |
71 | } | |
72 | ||
73 | + (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context | |
74 | { | |
75 | return [JSValue valueWithJSValueRef:objectToValue(context, value) inContext:context]; | |
76 | } | |
77 | ||
78 | + (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context | |
79 | { | |
80 | return [JSValue valueWithJSValueRef:JSValueMakeBoolean([context JSGlobalContextRef], value) inContext:context]; | |
81 | } | |
82 | ||
83 | + (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context | |
84 | { | |
85 | return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context]; | |
86 | } | |
87 | ||
88 | + (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context | |
89 | { | |
90 | return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context]; | |
91 | } | |
92 | ||
93 | + (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context | |
94 | { | |
95 | return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context]; | |
96 | } | |
97 | ||
98 | + (JSValue *)valueWithNewObjectInContext:(JSContext *)context | |
99 | { | |
100 | return [JSValue valueWithJSValueRef:JSObjectMake([context JSGlobalContextRef], 0, 0) inContext:context]; | |
101 | } | |
102 | ||
103 | + (JSValue *)valueWithNewArrayInContext:(JSContext *)context | |
104 | { | |
105 | return [JSValue valueWithJSValueRef:JSObjectMakeArray([context JSGlobalContextRef], 0, NULL, 0) inContext:context]; | |
106 | } | |
107 | ||
108 | + (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context | |
109 | { | |
110 | JSStringRef patternString = JSStringCreateWithCFString((CFStringRef)pattern); | |
111 | JSStringRef flagsString = JSStringCreateWithCFString((CFStringRef)flags); | |
112 | JSValueRef arguments[2] = { JSValueMakeString([context JSGlobalContextRef], patternString), JSValueMakeString([context JSGlobalContextRef], flagsString) }; | |
113 | JSStringRelease(patternString); | |
114 | JSStringRelease(flagsString); | |
115 | ||
116 | return [JSValue valueWithJSValueRef:JSObjectMakeRegExp([context JSGlobalContextRef], 2, arguments, 0) inContext:context]; | |
117 | } | |
118 | ||
119 | + (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context | |
120 | { | |
121 | JSStringRef string = JSStringCreateWithCFString((CFStringRef)message); | |
122 | JSValueRef argument = JSValueMakeString([context JSGlobalContextRef], string); | |
123 | JSStringRelease(string); | |
124 | ||
125 | return [JSValue valueWithJSValueRef:JSObjectMakeError([context JSGlobalContextRef], 1, &argument, 0) inContext:context]; | |
126 | } | |
127 | ||
128 | + (JSValue *)valueWithNullInContext:(JSContext *)context | |
129 | { | |
130 | return [JSValue valueWithJSValueRef:JSValueMakeNull([context JSGlobalContextRef]) inContext:context]; | |
131 | } | |
132 | ||
133 | + (JSValue *)valueWithUndefinedInContext:(JSContext *)context | |
134 | { | |
135 | return [JSValue valueWithJSValueRef:JSValueMakeUndefined([context JSGlobalContextRef]) inContext:context]; | |
136 | } | |
137 | ||
138 | - (id)toObject | |
139 | { | |
140 | return valueToObject(_context, m_value); | |
141 | } | |
142 | ||
143 | - (id)toObjectOfClass:(Class)expectedClass | |
144 | { | |
145 | id result = [self toObject]; | |
146 | return [result isKindOfClass:expectedClass] ? result : nil; | |
147 | } | |
148 | ||
149 | - (BOOL)toBool | |
150 | { | |
151 | return JSValueToBoolean([_context JSGlobalContextRef], m_value); | |
152 | } | |
153 | ||
154 | - (double)toDouble | |
155 | { | |
156 | JSValueRef exception = 0; | |
157 | double result = JSValueToNumber([_context JSGlobalContextRef], m_value, &exception); | |
158 | if (exception) { | |
159 | [_context notifyException:exception]; | |
160 | return std::numeric_limits<double>::quiet_NaN(); | |
161 | } | |
162 | ||
163 | return result; | |
164 | } | |
165 | ||
166 | - (int32_t)toInt32 | |
167 | { | |
168 | return JSC::toInt32([self toDouble]); | |
169 | } | |
170 | ||
171 | - (uint32_t)toUInt32 | |
172 | { | |
173 | return JSC::toUInt32([self toDouble]); | |
174 | } | |
175 | ||
176 | - (NSNumber *)toNumber | |
177 | { | |
178 | JSValueRef exception = 0; | |
179 | id result = valueToNumber([_context JSGlobalContextRef], m_value, &exception); | |
180 | if (exception) | |
181 | [_context notifyException:exception]; | |
182 | return result; | |
183 | } | |
184 | ||
185 | - (NSString *)toString | |
186 | { | |
187 | JSValueRef exception = 0; | |
188 | id result = valueToString([_context JSGlobalContextRef], m_value, &exception); | |
189 | if (exception) | |
190 | [_context notifyException:exception]; | |
191 | return result; | |
192 | } | |
193 | ||
194 | - (NSDate *)toDate | |
195 | { | |
196 | JSValueRef exception = 0; | |
197 | id result = valueToDate([_context JSGlobalContextRef], m_value, &exception); | |
198 | if (exception) | |
199 | [_context notifyException:exception]; | |
200 | return result; | |
201 | } | |
202 | ||
203 | - (NSArray *)toArray | |
204 | { | |
205 | JSValueRef exception = 0; | |
206 | id result = valueToArray([_context JSGlobalContextRef], m_value, &exception); | |
207 | if (exception) | |
208 | [_context notifyException:exception]; | |
209 | return result; | |
210 | } | |
211 | ||
212 | - (NSDictionary *)toDictionary | |
213 | { | |
214 | JSValueRef exception = 0; | |
215 | id result = valueToDictionary([_context JSGlobalContextRef], m_value, &exception); | |
216 | if (exception) | |
217 | [_context notifyException:exception]; | |
218 | return result; | |
219 | } | |
220 | ||
221 | - (JSValue *)valueForProperty:(NSString *)propertyName | |
222 | { | |
223 | JSValueRef exception = 0; | |
224 | JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); | |
225 | if (exception) | |
226 | return [_context valueFromNotifyException:exception]; | |
227 | ||
228 | JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); | |
229 | JSValueRef result = JSObjectGetProperty([_context JSGlobalContextRef], object, name, &exception); | |
230 | JSStringRelease(name); | |
231 | if (exception) | |
232 | return [_context valueFromNotifyException:exception]; | |
233 | ||
234 | return [JSValue valueWithJSValueRef:result inContext:_context]; | |
235 | } | |
236 | ||
237 | - (void)setValue:(id)value forProperty:(NSString *)propertyName | |
238 | { | |
239 | JSValueRef exception = 0; | |
240 | JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); | |
241 | if (exception) { | |
242 | [_context notifyException:exception]; | |
243 | return; | |
244 | } | |
245 | ||
246 | JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); | |
247 | JSObjectSetProperty([_context JSGlobalContextRef], object, name, objectToValue(_context, value), 0, &exception); | |
248 | JSStringRelease(name); | |
249 | if (exception) { | |
250 | [_context notifyException:exception]; | |
251 | return; | |
252 | } | |
253 | } | |
254 | ||
255 | - (BOOL)deleteProperty:(NSString *)propertyName | |
256 | { | |
257 | JSValueRef exception = 0; | |
258 | JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); | |
259 | if (exception) | |
260 | return [_context boolFromNotifyException:exception]; | |
261 | ||
262 | JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); | |
263 | BOOL result = JSObjectDeleteProperty([_context JSGlobalContextRef], object, name, &exception); | |
264 | JSStringRelease(name); | |
265 | if (exception) | |
266 | return [_context boolFromNotifyException:exception]; | |
267 | ||
268 | return result; | |
269 | } | |
270 | ||
271 | - (BOOL)hasProperty:(NSString *)propertyName | |
272 | { | |
273 | JSValueRef exception = 0; | |
274 | JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); | |
275 | if (exception) | |
276 | return [_context boolFromNotifyException:exception]; | |
277 | ||
278 | JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); | |
279 | BOOL result = JSObjectHasProperty([_context JSGlobalContextRef], object, name); | |
280 | JSStringRelease(name); | |
281 | return result; | |
282 | } | |
283 | ||
284 | - (void)defineProperty:(NSString *)property descriptor:(id)descriptor | |
285 | { | |
286 | [[_context globalObject][@"Object"] invokeMethod:@"defineProperty" withArguments:@[ self, property, descriptor ]]; | |
287 | } | |
288 | ||
289 | - (JSValue *)valueAtIndex:(NSUInteger)index | |
290 | { | |
291 | // Properties that are higher than an unsigned value can hold are converted to a double then inserted as a normal property. | |
292 | // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in get(). | |
293 | if (index != (unsigned)index) | |
294 | return [self valueForProperty:[[JSValue valueWithDouble:index inContext:_context] toString]]; | |
295 | ||
296 | JSValueRef exception = 0; | |
297 | JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); | |
298 | if (exception) | |
299 | return [_context valueFromNotifyException:exception]; | |
300 | ||
301 | JSValueRef result = JSObjectGetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, &exception); | |
302 | if (exception) | |
303 | return [_context valueFromNotifyException:exception]; | |
304 | ||
305 | return [JSValue valueWithJSValueRef:result inContext:_context]; | |
306 | } | |
307 | ||
308 | - (void)setValue:(id)value atIndex:(NSUInteger)index | |
309 | { | |
310 | // Properties that are higher than an unsigned value can hold are converted to a double, then inserted as a normal property. | |
311 | // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in putByIndex(). | |
312 | if (index != (unsigned)index) | |
313 | return [self setValue:value forProperty:[[JSValue valueWithDouble:index inContext:_context] toString]]; | |
314 | ||
315 | JSValueRef exception = 0; | |
316 | JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); | |
317 | if (exception) { | |
318 | [_context notifyException:exception]; | |
319 | return; | |
320 | } | |
321 | ||
322 | JSObjectSetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, objectToValue(_context, value), &exception); | |
323 | if (exception) { | |
324 | [_context notifyException:exception]; | |
325 | return; | |
326 | } | |
327 | } | |
328 | ||
329 | - (BOOL)isUndefined | |
330 | { | |
331 | return JSValueIsUndefined([_context JSGlobalContextRef], m_value); | |
332 | } | |
333 | ||
334 | - (BOOL)isNull | |
335 | { | |
336 | return JSValueIsNull([_context JSGlobalContextRef], m_value); | |
337 | } | |
338 | ||
339 | - (BOOL)isBoolean | |
340 | { | |
341 | return JSValueIsBoolean([_context JSGlobalContextRef], m_value); | |
342 | } | |
343 | ||
344 | - (BOOL)isNumber | |
345 | { | |
346 | return JSValueIsNumber([_context JSGlobalContextRef], m_value); | |
347 | } | |
348 | ||
349 | - (BOOL)isString | |
350 | { | |
351 | return JSValueIsString([_context JSGlobalContextRef], m_value); | |
352 | } | |
353 | ||
354 | - (BOOL)isObject | |
355 | { | |
356 | return JSValueIsObject([_context JSGlobalContextRef], m_value); | |
357 | } | |
358 | ||
359 | - (BOOL)isEqualToObject:(id)value | |
360 | { | |
361 | return JSValueIsStrictEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value)); | |
362 | } | |
363 | ||
364 | - (BOOL)isEqualWithTypeCoercionToObject:(id)value | |
365 | { | |
366 | JSValueRef exception = 0; | |
367 | BOOL result = JSValueIsEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value), &exception); | |
368 | if (exception) | |
369 | return [_context boolFromNotifyException:exception]; | |
370 | ||
371 | return result; | |
372 | } | |
373 | ||
374 | - (BOOL)isInstanceOf:(id)value | |
375 | { | |
376 | JSValueRef exception = 0; | |
377 | JSObjectRef constructor = JSValueToObject([_context JSGlobalContextRef], objectToValue(_context, value), &exception); | |
378 | if (exception) | |
379 | return [_context boolFromNotifyException:exception]; | |
380 | ||
381 | BOOL result = JSValueIsInstanceOfConstructor([_context JSGlobalContextRef], m_value, constructor, &exception); | |
382 | if (exception) | |
383 | return [_context boolFromNotifyException:exception]; | |
384 | ||
385 | return result; | |
386 | } | |
387 | ||
388 | - (JSValue *)callWithArguments:(NSArray *)argumentArray | |
389 | { | |
390 | NSUInteger argumentCount = [argumentArray count]; | |
391 | JSValueRef arguments[argumentCount]; | |
392 | for (unsigned i = 0; i < argumentCount; ++i) | |
393 | arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]); | |
394 | ||
395 | JSValueRef exception = 0; | |
396 | JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); | |
397 | if (exception) | |
398 | return [_context valueFromNotifyException:exception]; | |
399 | ||
400 | JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, 0, argumentCount, arguments, &exception); | |
401 | if (exception) | |
402 | return [_context valueFromNotifyException:exception]; | |
403 | ||
404 | return [JSValue valueWithJSValueRef:result inContext:_context]; | |
405 | } | |
406 | ||
407 | - (JSValue *)constructWithArguments:(NSArray *)argumentArray | |
408 | { | |
409 | NSUInteger argumentCount = [argumentArray count]; | |
410 | JSValueRef arguments[argumentCount]; | |
411 | for (unsigned i = 0; i < argumentCount; ++i) | |
412 | arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]); | |
413 | ||
414 | JSValueRef exception = 0; | |
415 | JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); | |
416 | if (exception) | |
417 | return [_context valueFromNotifyException:exception]; | |
418 | ||
419 | JSObjectRef result = JSObjectCallAsConstructor([_context JSGlobalContextRef], object, argumentCount, arguments, &exception); | |
420 | if (exception) | |
421 | return [_context valueFromNotifyException:exception]; | |
422 | ||
423 | return [JSValue valueWithJSValueRef:result inContext:_context]; | |
424 | } | |
425 | ||
426 | - (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments | |
427 | { | |
428 | NSUInteger argumentCount = [arguments count]; | |
429 | JSValueRef argumentArray[argumentCount]; | |
430 | for (unsigned i = 0; i < argumentCount; ++i) | |
431 | argumentArray[i] = objectToValue(_context, [arguments objectAtIndex:i]); | |
432 | ||
433 | JSValueRef exception = 0; | |
434 | JSObjectRef thisObject = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); | |
435 | if (exception) | |
436 | return [_context valueFromNotifyException:exception]; | |
437 | ||
438 | JSStringRef name = JSStringCreateWithCFString((CFStringRef)method); | |
439 | JSValueRef function = JSObjectGetProperty([_context JSGlobalContextRef], thisObject, name, &exception); | |
440 | JSStringRelease(name); | |
441 | if (exception) | |
442 | return [_context valueFromNotifyException:exception]; | |
443 | ||
444 | JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], function, &exception); | |
445 | if (exception) | |
446 | return [_context valueFromNotifyException:exception]; | |
447 | ||
448 | JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, thisObject, argumentCount, argumentArray, &exception); | |
449 | if (exception) | |
450 | return [_context valueFromNotifyException:exception]; | |
451 | ||
452 | return [JSValue valueWithJSValueRef:result inContext:_context]; | |
453 | } | |
454 | ||
455 | @end | |
456 | ||
457 | @implementation JSValue(StructSupport) | |
458 | ||
459 | - (CGPoint)toPoint | |
460 | { | |
461 | return (CGPoint){ | |
462 | static_cast<CGFloat>([self[@"x"] toDouble]), | |
463 | static_cast<CGFloat>([self[@"y"] toDouble]) | |
464 | }; | |
465 | } | |
466 | ||
467 | - (NSRange)toRange | |
468 | { | |
469 | return (NSRange){ | |
470 | [[self[@"location"] toNumber] unsignedIntegerValue], | |
471 | [[self[@"length"] toNumber] unsignedIntegerValue] | |
472 | }; | |
473 | } | |
474 | ||
475 | - (CGRect)toRect | |
476 | { | |
477 | return (CGRect){ | |
478 | [self toPoint], | |
479 | [self toSize] | |
480 | }; | |
481 | } | |
482 | ||
483 | - (CGSize)toSize | |
484 | { | |
485 | return (CGSize){ | |
486 | static_cast<CGFloat>([self[@"width"] toDouble]), | |
487 | static_cast<CGFloat>([self[@"height"] toDouble]) | |
488 | }; | |
489 | } | |
490 | ||
491 | + (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context | |
492 | { | |
493 | return [JSValue valueWithObject:@{ | |
494 | @"x":@(point.x), | |
495 | @"y":@(point.y) | |
496 | } inContext:context]; | |
497 | } | |
498 | ||
499 | + (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context | |
500 | { | |
501 | return [JSValue valueWithObject:@{ | |
502 | @"location":@(range.location), | |
503 | @"length":@(range.length) | |
504 | } inContext:context]; | |
505 | } | |
506 | ||
507 | + (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context | |
508 | { | |
509 | return [JSValue valueWithObject:@{ | |
510 | @"x":@(rect.origin.x), | |
511 | @"y":@(rect.origin.y), | |
512 | @"width":@(rect.size.width), | |
513 | @"height":@(rect.size.height) | |
514 | } inContext:context]; | |
515 | } | |
516 | ||
517 | + (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context | |
518 | { | |
519 | return [JSValue valueWithObject:@{ | |
520 | @"width":@(size.width), | |
521 | @"height":@(size.height) | |
522 | } inContext:context]; | |
523 | } | |
524 | ||
525 | @end | |
526 | ||
527 | @implementation JSValue(SubscriptSupport) | |
528 | ||
529 | - (JSValue *)objectForKeyedSubscript:(id)key | |
530 | { | |
531 | if (![key isKindOfClass:[NSString class]]) { | |
532 | key = [[JSValue valueWithObject:key inContext:_context] toString]; | |
533 | if (!key) | |
534 | return [JSValue valueWithUndefinedInContext:_context]; | |
535 | } | |
536 | ||
537 | return [self valueForProperty:(NSString *)key]; | |
538 | } | |
539 | ||
540 | - (JSValue *)objectAtIndexedSubscript:(NSUInteger)index | |
541 | { | |
542 | return [self valueAtIndex:index]; | |
543 | } | |
544 | ||
545 | - (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key | |
546 | { | |
547 | if (![key isKindOfClass:[NSString class]]) { | |
548 | key = [[JSValue valueWithObject:key inContext:_context] toString]; | |
549 | if (!key) | |
550 | return; | |
551 | } | |
552 | ||
553 | [self setValue:object forProperty:(NSString *)key]; | |
554 | } | |
555 | ||
556 | - (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index | |
557 | { | |
558 | [self setValue:object atIndex:index]; | |
559 | } | |
560 | ||
561 | @end | |
562 | ||
563 | inline bool isDate(JSObjectRef object, JSGlobalContextRef context) | |
564 | { | |
81345200 A |
565 | JSC::JSLockHolder locker(toJS(context)); |
566 | return toJS(object)->inherits(JSC::DateInstance::info()); | |
93a37866 A |
567 | } |
568 | ||
569 | inline bool isArray(JSObjectRef object, JSGlobalContextRef context) | |
570 | { | |
81345200 A |
571 | JSC::JSLockHolder locker(toJS(context)); |
572 | return toJS(object)->inherits(JSC::JSArray::info()); | |
93a37866 A |
573 | } |
574 | ||
575 | @implementation JSValue(Internal) | |
576 | ||
577 | enum ConversionType { | |
578 | ContainerNone, | |
579 | ContainerArray, | |
580 | ContainerDictionary | |
581 | }; | |
582 | ||
583 | class JSContainerConvertor { | |
584 | public: | |
585 | struct Task { | |
586 | JSValueRef js; | |
587 | id objc; | |
588 | ConversionType type; | |
589 | }; | |
590 | ||
591 | JSContainerConvertor(JSGlobalContextRef context) | |
592 | : m_context(context) | |
593 | { | |
594 | } | |
595 | ||
596 | id convert(JSValueRef property); | |
597 | void add(Task); | |
598 | Task take(); | |
599 | bool isWorkListEmpty() const { return !m_worklist.size(); } | |
600 | ||
601 | private: | |
602 | JSGlobalContextRef m_context; | |
603 | HashMap<JSValueRef, id> m_objectMap; | |
604 | Vector<Task> m_worklist; | |
12899fa2 | 605 | Vector<JSC::Strong<JSC::Unknown>> m_jsValues; |
93a37866 A |
606 | }; |
607 | ||
608 | inline id JSContainerConvertor::convert(JSValueRef value) | |
609 | { | |
610 | HashMap<JSValueRef, id>::iterator iter = m_objectMap.find(value); | |
611 | if (iter != m_objectMap.end()) | |
612 | return iter->value; | |
613 | ||
614 | Task result = valueToObjectWithoutCopy(m_context, value); | |
615 | if (result.js) | |
616 | add(result); | |
617 | return result.objc; | |
618 | } | |
619 | ||
620 | void JSContainerConvertor::add(Task task) | |
621 | { | |
12899fa2 A |
622 | JSC::ExecState* exec = toJS(m_context); |
623 | m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js))); | |
93a37866 A |
624 | m_objectMap.add(task.js, task.objc); |
625 | if (task.type != ContainerNone) | |
626 | m_worklist.append(task); | |
627 | } | |
628 | ||
629 | JSContainerConvertor::Task JSContainerConvertor::take() | |
630 | { | |
631 | ASSERT(!isWorkListEmpty()); | |
632 | Task last = m_worklist.last(); | |
633 | m_worklist.removeLast(); | |
634 | return last; | |
635 | } | |
636 | ||
81345200 A |
637 | #if ENABLE(REMOTE_INSPECTOR) |
638 | static void reportExceptionToInspector(JSGlobalContextRef context, JSC::JSValue exception) | |
639 | { | |
640 | JSC::ExecState* exec = toJS(context); | |
641 | exec->vmEntryGlobalObject()->inspectorController().reportAPIException(exec, exception); | |
642 | } | |
643 | #endif | |
644 | ||
93a37866 A |
645 | static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef context, JSValueRef value) |
646 | { | |
647 | if (!JSValueIsObject(context, value)) { | |
648 | id primitive; | |
649 | if (JSValueIsBoolean(context, value)) | |
650 | primitive = JSValueToBoolean(context, value) ? @YES : @NO; | |
651 | else if (JSValueIsNumber(context, value)) { | |
652 | // Normalize the number, so it will unique correctly in the hash map - | |
653 | // it's nicer not to leak this internal implementation detail! | |
654 | value = JSValueMakeNumber(context, JSValueToNumber(context, value, 0)); | |
655 | primitive = [NSNumber numberWithDouble:JSValueToNumber(context, value, 0)]; | |
656 | } else if (JSValueIsString(context, value)) { | |
657 | // Would be nice to unique strings, too. | |
658 | JSStringRef jsstring = JSValueToStringCopy(context, value, 0); | |
659 | NSString * stringNS = (NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring); | |
660 | JSStringRelease(jsstring); | |
661 | primitive = [stringNS autorelease]; | |
662 | } else if (JSValueIsNull(context, value)) | |
663 | primitive = [NSNull null]; | |
664 | else { | |
665 | ASSERT(JSValueIsUndefined(context, value)); | |
666 | primitive = nil; | |
667 | } | |
668 | return (JSContainerConvertor::Task){ value, primitive, ContainerNone }; | |
669 | } | |
670 | ||
671 | JSObjectRef object = JSValueToObject(context, value, 0); | |
672 | ||
673 | if (id wrapped = tryUnwrapObjcObject(context, object)) | |
674 | return (JSContainerConvertor::Task){ object, wrapped, ContainerNone }; | |
675 | ||
676 | if (isDate(object, context)) | |
81345200 | 677 | return (JSContainerConvertor::Task){ object, [NSDate dateWithTimeIntervalSince1970:JSValueToNumber(context, object, 0) / 1000.0], ContainerNone }; |
93a37866 A |
678 | |
679 | if (isArray(object, context)) | |
680 | return (JSContainerConvertor::Task){ object, [NSMutableArray array], ContainerArray }; | |
681 | ||
682 | return (JSContainerConvertor::Task){ object, [NSMutableDictionary dictionary], ContainerDictionary }; | |
683 | } | |
684 | ||
685 | static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task) | |
686 | { | |
687 | ASSERT(task.type != ContainerNone); | |
81345200 | 688 | JSC::JSLockHolder locker(toJS(context)); |
93a37866 A |
689 | JSContainerConvertor convertor(context); |
690 | convertor.add(task); | |
691 | ASSERT(!convertor.isWorkListEmpty()); | |
692 | ||
693 | do { | |
694 | JSContainerConvertor::Task current = convertor.take(); | |
695 | ASSERT(JSValueIsObject(context, current.js)); | |
696 | JSObjectRef js = JSValueToObject(context, current.js, 0); | |
697 | ||
698 | if (current.type == ContainerArray) { | |
699 | ASSERT([current.objc isKindOfClass:[NSMutableArray class]]); | |
700 | NSMutableArray *array = (NSMutableArray *)current.objc; | |
701 | ||
702 | JSStringRef lengthString = JSStringCreateWithUTF8CString("length"); | |
703 | unsigned length = JSC::toUInt32(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString, 0), 0)); | |
704 | JSStringRelease(lengthString); | |
705 | ||
706 | for (unsigned i = 0; i < length; ++i) { | |
707 | id objc = convertor.convert(JSObjectGetPropertyAtIndex(context, js, i, 0)); | |
708 | [array addObject:objc ? objc : [NSNull null]]; | |
709 | } | |
710 | } else { | |
711 | ASSERT([current.objc isKindOfClass:[NSMutableDictionary class]]); | |
712 | NSMutableDictionary *dictionary = (NSMutableDictionary *)current.objc; | |
713 | ||
81345200 A |
714 | JSC::JSLockHolder locker(toJS(context)); |
715 | ||
93a37866 A |
716 | JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js); |
717 | size_t length = JSPropertyNameArrayGetCount(propertyNameArray); | |
718 | ||
719 | for (size_t i = 0; i < length; ++i) { | |
720 | JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNameArray, i); | |
721 | if (id objc = convertor.convert(JSObjectGetProperty(context, js, propertyName, 0))) | |
722 | dictionary[[(NSString *)JSStringCopyCFString(kCFAllocatorDefault, propertyName) autorelease]] = objc; | |
723 | } | |
724 | ||
725 | JSPropertyNameArrayRelease(propertyNameArray); | |
726 | } | |
727 | ||
728 | } while (!convertor.isWorkListEmpty()); | |
729 | ||
730 | return task.objc; | |
731 | } | |
732 | ||
733 | id valueToObject(JSContext *context, JSValueRef value) | |
734 | { | |
735 | JSContainerConvertor::Task result = valueToObjectWithoutCopy([context JSGlobalContextRef], value); | |
736 | if (result.type == ContainerNone) | |
737 | return result.objc; | |
738 | return containerValueToObject([context JSGlobalContextRef], result); | |
739 | } | |
740 | ||
741 | id valueToNumber(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) | |
742 | { | |
743 | ASSERT(!*exception); | |
744 | if (id wrapped = tryUnwrapObjcObject(context, value)) { | |
745 | if ([wrapped isKindOfClass:[NSNumber class]]) | |
746 | return wrapped; | |
747 | } | |
748 | ||
749 | if (JSValueIsBoolean(context, value)) | |
750 | return JSValueToBoolean(context, value) ? @YES : @NO; | |
751 | ||
752 | double result = JSValueToNumber(context, value, exception); | |
753 | return [NSNumber numberWithDouble:*exception ? std::numeric_limits<double>::quiet_NaN() : result]; | |
754 | } | |
755 | ||
756 | id valueToString(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) | |
757 | { | |
758 | ASSERT(!*exception); | |
759 | if (id wrapped = tryUnwrapObjcObject(context, value)) { | |
760 | if ([wrapped isKindOfClass:[NSString class]]) | |
761 | return wrapped; | |
762 | } | |
763 | ||
764 | JSStringRef jsstring = JSValueToStringCopy(context, value, exception); | |
765 | if (*exception) { | |
766 | ASSERT(!jsstring); | |
767 | return nil; | |
768 | } | |
769 | ||
81345200 | 770 | NSString *stringNS = CFBridgingRelease(JSStringCopyCFString(kCFAllocatorDefault, jsstring)); |
93a37866 A |
771 | JSStringRelease(jsstring); |
772 | return stringNS; | |
773 | } | |
774 | ||
775 | id valueToDate(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) | |
776 | { | |
777 | ASSERT(!*exception); | |
778 | if (id wrapped = tryUnwrapObjcObject(context, value)) { | |
779 | if ([wrapped isKindOfClass:[NSDate class]]) | |
780 | return wrapped; | |
781 | } | |
782 | ||
81345200 | 783 | double result = JSValueToNumber(context, value, exception) / 1000.0; |
93a37866 A |
784 | return *exception ? nil : [NSDate dateWithTimeIntervalSince1970:result]; |
785 | } | |
786 | ||
787 | id valueToArray(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) | |
788 | { | |
789 | ASSERT(!*exception); | |
790 | if (id wrapped = tryUnwrapObjcObject(context, value)) { | |
791 | if ([wrapped isKindOfClass:[NSArray class]]) | |
792 | return wrapped; | |
793 | } | |
794 | ||
795 | if (JSValueIsObject(context, value)) | |
796 | return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableArray array], ContainerArray}); | |
797 | ||
81345200 A |
798 | JSC::JSLockHolder locker(toJS(context)); |
799 | if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) { | |
800 | JSC::JSObject* exceptionObject = JSC::createTypeError(toJS(context), ASCIILiteral("Cannot convert primitive to NSArray")); | |
801 | *exception = toRef(exceptionObject); | |
802 | #if ENABLE(REMOTE_INSPECTOR) | |
803 | reportExceptionToInspector(context, exceptionObject); | |
804 | #endif | |
805 | } | |
93a37866 A |
806 | return nil; |
807 | } | |
808 | ||
809 | id valueToDictionary(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) | |
810 | { | |
811 | ASSERT(!*exception); | |
812 | if (id wrapped = tryUnwrapObjcObject(context, value)) { | |
813 | if ([wrapped isKindOfClass:[NSDictionary class]]) | |
814 | return wrapped; | |
815 | } | |
816 | ||
817 | if (JSValueIsObject(context, value)) | |
818 | return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableDictionary dictionary], ContainerDictionary}); | |
819 | ||
81345200 A |
820 | JSC::JSLockHolder locker(toJS(context)); |
821 | if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) { | |
822 | JSC::JSObject* exceptionObject = JSC::createTypeError(toJS(context), ASCIILiteral("Cannot convert primitive to NSDictionary")); | |
823 | *exception = toRef(exceptionObject); | |
824 | #if ENABLE(REMOTE_INSPECTOR) | |
825 | reportExceptionToInspector(context, exceptionObject); | |
826 | #endif | |
827 | } | |
93a37866 A |
828 | return nil; |
829 | } | |
830 | ||
831 | class ObjcContainerConvertor { | |
832 | public: | |
833 | struct Task { | |
834 | id objc; | |
835 | JSValueRef js; | |
836 | ConversionType type; | |
837 | }; | |
838 | ||
839 | ObjcContainerConvertor(JSContext *context) | |
840 | : m_context(context) | |
841 | { | |
842 | } | |
843 | ||
844 | JSValueRef convert(id object); | |
845 | void add(Task); | |
846 | Task take(); | |
847 | bool isWorkListEmpty() const { return !m_worklist.size(); } | |
848 | ||
849 | private: | |
850 | JSContext *m_context; | |
851 | HashMap<id, JSValueRef> m_objectMap; | |
852 | Vector<Task> m_worklist; | |
12899fa2 | 853 | Vector<JSC::Strong<JSC::Unknown>> m_jsValues; |
93a37866 A |
854 | }; |
855 | ||
856 | JSValueRef ObjcContainerConvertor::convert(id object) | |
857 | { | |
858 | ASSERT(object); | |
859 | ||
860 | auto it = m_objectMap.find(object); | |
861 | if (it != m_objectMap.end()) | |
862 | return it->value; | |
863 | ||
864 | ObjcContainerConvertor::Task task = objectToValueWithoutCopy(m_context, object); | |
865 | add(task); | |
866 | return task.js; | |
867 | } | |
868 | ||
869 | void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task) | |
870 | { | |
12899fa2 A |
871 | JSC::ExecState* exec = toJS(m_context.JSGlobalContextRef); |
872 | m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js))); | |
93a37866 A |
873 | m_objectMap.add(task.objc, task.js); |
874 | if (task.type != ContainerNone) | |
875 | m_worklist.append(task); | |
876 | } | |
877 | ||
878 | ObjcContainerConvertor::Task ObjcContainerConvertor::take() | |
879 | { | |
880 | ASSERT(!isWorkListEmpty()); | |
881 | Task last = m_worklist.last(); | |
882 | m_worklist.removeLast(); | |
883 | return last; | |
884 | } | |
885 | ||
886 | inline bool isNSBoolean(id object) | |
887 | { | |
888 | ASSERT([@YES class] == [@NO class]); | |
889 | ASSERT([@YES class] != [NSNumber class]); | |
890 | ASSERT([[@YES class] isSubclassOfClass:[NSNumber class]]); | |
891 | return [object isKindOfClass:[@YES class]]; | |
892 | } | |
893 | ||
894 | static ObjcContainerConvertor::Task objectToValueWithoutCopy(JSContext *context, id object) | |
895 | { | |
896 | JSGlobalContextRef contextRef = [context JSGlobalContextRef]; | |
897 | ||
898 | if (!object) | |
899 | return (ObjcContainerConvertor::Task){ object, JSValueMakeUndefined(contextRef), ContainerNone }; | |
900 | ||
901 | if (!class_conformsToProtocol(object_getClass(object), getJSExportProtocol())) { | |
902 | if ([object isKindOfClass:[NSArray class]]) | |
903 | return (ObjcContainerConvertor::Task){ object, JSObjectMakeArray(contextRef, 0, NULL, 0), ContainerArray }; | |
904 | ||
905 | if ([object isKindOfClass:[NSDictionary class]]) | |
906 | return (ObjcContainerConvertor::Task){ object, JSObjectMake(contextRef, 0, 0), ContainerDictionary }; | |
907 | ||
908 | if ([object isKindOfClass:[NSNull class]]) | |
909 | return (ObjcContainerConvertor::Task){ object, JSValueMakeNull(contextRef), ContainerNone }; | |
910 | ||
911 | if ([object isKindOfClass:[JSValue class]]) | |
912 | return (ObjcContainerConvertor::Task){ object, ((JSValue *)object)->m_value, ContainerNone }; | |
913 | ||
914 | if ([object isKindOfClass:[NSString class]]) { | |
915 | JSStringRef string = JSStringCreateWithCFString((CFStringRef)object); | |
916 | JSValueRef js = JSValueMakeString(contextRef, string); | |
917 | JSStringRelease(string); | |
918 | return (ObjcContainerConvertor::Task){ object, js, ContainerNone }; | |
919 | } | |
920 | ||
921 | if ([object isKindOfClass:[NSNumber class]]) { | |
922 | if (isNSBoolean(object)) | |
923 | return (ObjcContainerConvertor::Task){ object, JSValueMakeBoolean(contextRef, [object boolValue]), ContainerNone }; | |
924 | return (ObjcContainerConvertor::Task){ object, JSValueMakeNumber(contextRef, [object doubleValue]), ContainerNone }; | |
925 | } | |
926 | ||
927 | if ([object isKindOfClass:[NSDate class]]) { | |
81345200 | 928 | JSValueRef argument = JSValueMakeNumber(contextRef, [object timeIntervalSince1970] * 1000.0); |
93a37866 A |
929 | JSObjectRef result = JSObjectMakeDate(contextRef, 1, &argument, 0); |
930 | return (ObjcContainerConvertor::Task){ object, result, ContainerNone }; | |
931 | } | |
932 | ||
933 | if ([object isKindOfClass:[JSManagedValue class]]) { | |
934 | JSValue *value = [static_cast<JSManagedValue *>(object) value]; | |
935 | if (!value) | |
936 | return (ObjcContainerConvertor::Task) { object, JSValueMakeUndefined(contextRef), ContainerNone }; | |
937 | return (ObjcContainerConvertor::Task){ object, value->m_value, ContainerNone }; | |
938 | } | |
939 | } | |
940 | ||
941 | return (ObjcContainerConvertor::Task){ object, valueInternalValue([context wrapperForObjCObject:object]), ContainerNone }; | |
942 | } | |
943 | ||
944 | JSValueRef objectToValue(JSContext *context, id object) | |
945 | { | |
946 | JSGlobalContextRef contextRef = [context JSGlobalContextRef]; | |
947 | ||
948 | ObjcContainerConvertor::Task task = objectToValueWithoutCopy(context, object); | |
949 | if (task.type == ContainerNone) | |
950 | return task.js; | |
951 | ||
81345200 | 952 | JSC::JSLockHolder locker(toJS(contextRef)); |
93a37866 A |
953 | ObjcContainerConvertor convertor(context); |
954 | convertor.add(task); | |
955 | ASSERT(!convertor.isWorkListEmpty()); | |
956 | ||
957 | do { | |
958 | ObjcContainerConvertor::Task current = convertor.take(); | |
959 | ASSERT(JSValueIsObject(contextRef, current.js)); | |
960 | JSObjectRef js = JSValueToObject(contextRef, current.js, 0); | |
961 | ||
962 | if (current.type == ContainerArray) { | |
963 | ASSERT([current.objc isKindOfClass:[NSArray class]]); | |
964 | NSArray *array = (NSArray *)current.objc; | |
965 | NSUInteger count = [array count]; | |
966 | for (NSUInteger index = 0; index < count; ++index) | |
967 | JSObjectSetPropertyAtIndex(contextRef, js, index, convertor.convert([array objectAtIndex:index]), 0); | |
968 | } else { | |
969 | ASSERT(current.type == ContainerDictionary); | |
970 | ASSERT([current.objc isKindOfClass:[NSDictionary class]]); | |
971 | NSDictionary *dictionary = (NSDictionary *)current.objc; | |
972 | for (id key in [dictionary keyEnumerator]) { | |
973 | if ([key isKindOfClass:[NSString class]]) { | |
974 | JSStringRef propertyName = JSStringCreateWithCFString((CFStringRef)key); | |
975 | JSObjectSetProperty(contextRef, js, propertyName, convertor.convert([dictionary objectForKey:key]), 0, 0); | |
976 | JSStringRelease(propertyName); | |
977 | } | |
978 | } | |
979 | } | |
980 | ||
981 | } while (!convertor.isWorkListEmpty()); | |
982 | ||
983 | return task.js; | |
984 | } | |
985 | ||
986 | JSValueRef valueInternalValue(JSValue * value) | |
987 | { | |
988 | return value->m_value; | |
989 | } | |
990 | ||
991 | + (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context | |
992 | { | |
993 | return [context wrapperForJSObject:value]; | |
994 | } | |
995 | ||
996 | - (JSValue *)init | |
997 | { | |
998 | return nil; | |
999 | } | |
1000 | ||
1001 | - (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context | |
1002 | { | |
1003 | if (!value || !context) | |
1004 | return nil; | |
1005 | ||
1006 | self = [super init]; | |
1007 | if (!self) | |
1008 | return nil; | |
1009 | ||
1010 | _context = [context retain]; | |
1011 | m_value = value; | |
1012 | JSValueProtect([_context JSGlobalContextRef], m_value); | |
1013 | return self; | |
1014 | } | |
1015 | ||
1016 | struct StructTagHandler { | |
1017 | SEL typeToValueSEL; | |
1018 | SEL valueToTypeSEL; | |
1019 | }; | |
1020 | typedef HashMap<String, StructTagHandler> StructHandlers; | |
1021 | ||
1022 | static StructHandlers* createStructHandlerMap() | |
1023 | { | |
1024 | StructHandlers* structHandlers = new StructHandlers(); | |
1025 | ||
1026 | size_t valueWithXinContextLength = strlen("valueWithX:inContext:"); | |
1027 | size_t toXLength = strlen("toX"); | |
1028 | ||
1029 | // Step 1: find all valueWith<Foo>:inContext: class methods in JSValue. | |
1030 | forEachMethodInClass(object_getClass([JSValue class]), ^(Method method){ | |
1031 | SEL selector = method_getName(method); | |
1032 | const char* name = sel_getName(selector); | |
1033 | size_t nameLength = strlen(name); | |
1034 | // Check for valueWith<Foo>:context: | |
1035 | if (nameLength < valueWithXinContextLength || memcmp(name, "valueWith", 9) || memcmp(name + nameLength - 11, ":inContext:", 11)) | |
1036 | return; | |
1037 | // Check for [ id, SEL, <type>, <contextType> ] | |
1038 | if (method_getNumberOfArguments(method) != 4) | |
1039 | return; | |
1040 | char idType[3]; | |
1041 | // Check 2nd argument type is "@" | |
1042 | char* secondType = method_copyArgumentType(method, 3); | |
1043 | if (strcmp(secondType, "@") != 0) { | |
1044 | free(secondType); | |
1045 | return; | |
1046 | } | |
1047 | free(secondType); | |
1048 | // Check result type is also "@" | |
1049 | method_getReturnType(method, idType, 3); | |
1050 | if (strcmp(idType, "@") != 0) | |
1051 | return; | |
1052 | char* type = method_copyArgumentType(method, 2); | |
1053 | structHandlers->add(StringImpl::create(type), (StructTagHandler){ selector, 0 }); | |
1054 | free(type); | |
1055 | }); | |
1056 | ||
1057 | // Step 2: find all to<Foo> instance methods in JSValue. | |
1058 | forEachMethodInClass([JSValue class], ^(Method method){ | |
1059 | SEL selector = method_getName(method); | |
1060 | const char* name = sel_getName(selector); | |
1061 | size_t nameLength = strlen(name); | |
1062 | // Check for to<Foo> | |
1063 | if (nameLength < toXLength || memcmp(name, "to", 2)) | |
1064 | return; | |
1065 | // Check for [ id, SEL ] | |
1066 | if (method_getNumberOfArguments(method) != 2) | |
1067 | return; | |
1068 | // Try to find a matching valueWith<Foo>:context: method. | |
1069 | char* type = method_copyReturnType(method); | |
1070 | ||
1071 | StructHandlers::iterator iter = structHandlers->find(type); | |
1072 | free(type); | |
1073 | if (iter == structHandlers->end()) | |
1074 | return; | |
1075 | StructTagHandler& handler = iter->value; | |
1076 | ||
1077 | // check that strlen(<foo>) == strlen(<Foo>) | |
1078 | const char* valueWithName = sel_getName(handler.typeToValueSEL); | |
1079 | size_t valueWithLength = strlen(valueWithName); | |
1080 | if (valueWithLength - valueWithXinContextLength != nameLength - toXLength) | |
1081 | return; | |
1082 | // Check that <Foo> == <Foo> | |
1083 | if (memcmp(valueWithName + 9, name + 2, nameLength - toXLength - 1)) | |
1084 | return; | |
1085 | handler.valueToTypeSEL = selector; | |
1086 | }); | |
1087 | ||
1088 | // Step 3: clean up - remove entries where we found prospective valueWith<Foo>:inContext: conversions, but no matching to<Foo> methods. | |
1089 | typedef HashSet<String> RemoveSet; | |
1090 | RemoveSet removeSet; | |
1091 | for (StructHandlers::iterator iter = structHandlers->begin(); iter != structHandlers->end(); ++iter) { | |
1092 | StructTagHandler& handler = iter->value; | |
1093 | if (!handler.valueToTypeSEL) | |
1094 | removeSet.add(iter->key); | |
1095 | } | |
1096 | ||
1097 | for (RemoveSet::iterator iter = removeSet.begin(); iter != removeSet.end(); ++iter) | |
1098 | structHandlers->remove(*iter); | |
1099 | ||
1100 | return structHandlers; | |
1101 | } | |
1102 | ||
1103 | static StructTagHandler* handerForStructTag(const char* encodedType) | |
1104 | { | |
1105 | static SpinLock handerForStructTagLock = SPINLOCK_INITIALIZER; | |
1106 | SpinLockHolder lockHolder(&handerForStructTagLock); | |
1107 | ||
1108 | static StructHandlers* structHandlers = createStructHandlerMap(); | |
1109 | ||
1110 | StructHandlers::iterator iter = structHandlers->find(encodedType); | |
1111 | if (iter == structHandlers->end()) | |
1112 | return 0; | |
1113 | return &iter->value; | |
1114 | } | |
1115 | ||
1116 | + (SEL)selectorForStructToValue:(const char *)structTag | |
1117 | { | |
1118 | StructTagHandler* handler = handerForStructTag(structTag); | |
1119 | return handler ? handler->typeToValueSEL : nil; | |
1120 | } | |
1121 | ||
1122 | + (SEL)selectorForValueToStruct:(const char *)structTag | |
1123 | { | |
1124 | StructTagHandler* handler = handerForStructTag(structTag); | |
1125 | return handler ? handler->valueToTypeSEL : nil; | |
1126 | } | |
1127 | ||
1128 | - (void)dealloc | |
1129 | { | |
1130 | JSValueUnprotect([_context JSGlobalContextRef], m_value); | |
1131 | [_context release]; | |
1132 | _context = nil; | |
1133 | [super dealloc]; | |
1134 | } | |
1135 | ||
1136 | - (NSString *)description | |
1137 | { | |
1138 | if (id wrapped = tryUnwrapObjcObject([_context JSGlobalContextRef], m_value)) | |
1139 | return [wrapped description]; | |
1140 | return [self toString]; | |
1141 | } | |
1142 | ||
1143 | NSInvocation *typeToValueInvocationFor(const char* encodedType) | |
1144 | { | |
1145 | SEL selector = [JSValue selectorForStructToValue:encodedType]; | |
1146 | if (!selector) | |
1147 | return 0; | |
1148 | ||
1149 | const char* methodTypes = method_getTypeEncoding(class_getClassMethod([JSValue class], selector)); | |
1150 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]]; | |
1151 | [invocation setSelector:selector]; | |
1152 | return invocation; | |
1153 | } | |
1154 | ||
1155 | NSInvocation *valueToTypeInvocationFor(const char* encodedType) | |
1156 | { | |
1157 | SEL selector = [JSValue selectorForValueToStruct:encodedType]; | |
1158 | if (!selector) | |
1159 | return 0; | |
1160 | ||
1161 | const char* methodTypes = method_getTypeEncoding(class_getInstanceMethod([JSValue class], selector)); | |
1162 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]]; | |
1163 | [invocation setSelector:selector]; | |
1164 | return invocation; | |
1165 | } | |
1166 | ||
1167 | @end | |
1168 | ||
1169 | #endif |