Merge API shims and JSLock
[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 "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"
37 #import "JSCInlines.h"
38 #import "JSCJSValue.h"
39 #import "Strong.h"
40 #import "StrongInlines.h"
41 #import <wtf/HashMap.h>
42 #import <wtf/HashSet.h>
43 #import <wtf/ObjcRuntimeExtras.h>
44 #import <wtf/Vector.h>
45 #import <wtf/TCSpinLock.h>
46 #import <wtf/text/WTFString.h>
47 #import <wtf/text/StringHash.h>
48
49 #if ENABLE(REMOTE_INSPECTOR)
50 #import "CallFrame.h"
51 #import "JSGlobalObject.h"
52 #import "JSGlobalObjectInspectorController.h"
53 #endif
54
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 {
565     JSC::JSLockHolder locker(toJS(context));
566     return toJS(object)->inherits(JSC::DateInstance::info());
567 }
568
569 inline bool isArray(JSObjectRef object, JSGlobalContextRef context)
570 {
571     JSC::JSLockHolder locker(toJS(context));
572     return toJS(object)->inherits(JSC::JSArray::info());
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;
605     Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
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 {
622     JSC::ExecState* exec = toJS(m_context);
623     m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
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
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
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))
677         return (JSContainerConvertor::Task){ object, [NSDate dateWithTimeIntervalSince1970:JSValueToNumber(context, object, 0) / 1000.0], ContainerNone };
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);
688     JSC::JSLockHolder locker(toJS(context));
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
714             JSC::JSLockHolder locker(toJS(context));
715
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
770     NSString *stringNS = CFBridgingRelease(JSStringCopyCFString(kCFAllocatorDefault, jsstring));
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
783     double result = JSValueToNumber(context, value, exception) / 1000.0;
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
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     }
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
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     }
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;
853     Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
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 {
871     JSC::ExecState* exec = toJS(m_context.JSGlobalContextRef);
872     m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
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]]) {
928             JSValueRef argument = JSValueMakeNumber(contextRef, [object timeIntervalSince1970] * 1000.0);
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
952     JSC::JSLockHolder locker(toJS(contextRef));
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