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