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