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