2 * Copyright (C) 2013 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
30 #import "DateInstance.h"
32 #import "JavaScriptCore.h"
33 #import "JSContextInternal.h"
34 #import "JSVirtualMachineInternal.h"
35 #import "JSValueInternal.h"
36 #import "JSWrapperMap.h"
37 #import "ObjcRuntimeExtras.h"
38 #import "Operations.h"
39 #import "JSCJSValue.h"
40 #import <wtf/HashMap.h>
41 #import <wtf/HashSet.h>
42 #import <wtf/Vector.h>
43 #import <wtf/TCSpinLock.h>
44 #import <wtf/text/WTFString.h>
45 #import <wtf/text/StringHash.h>
47 #if JSC_OBJC_API_ENABLED
49 NSString * const JSPropertyDescriptorWritableKey = @"writable";
50 NSString * const JSPropertyDescriptorEnumerableKey = @"enumerable";
51 NSString * const JSPropertyDescriptorConfigurableKey = @"configurable";
52 NSString * const JSPropertyDescriptorValueKey = @"value";
53 NSString * const JSPropertyDescriptorGetKey = @"get";
54 NSString * const JSPropertyDescriptorSetKey = @"set";
56 @implementation JSValue {
60 - (JSValueRef)JSValueRef
65 + (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context
67 return [JSValue valueWithJSValueRef:objectToValue(context, value) inContext:context];
70 + (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context
72 return [JSValue valueWithJSValueRef:JSValueMakeBoolean([context JSGlobalContextRef], value) inContext:context];
75 + (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context
77 return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
80 + (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context
82 return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
85 + (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context
87 return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
90 + (JSValue *)valueWithNewObjectInContext:(JSContext *)context
92 return [JSValue valueWithJSValueRef:JSObjectMake([context JSGlobalContextRef], 0, 0) inContext:context];
95 + (JSValue *)valueWithNewArrayInContext:(JSContext *)context
97 return [JSValue valueWithJSValueRef:JSObjectMakeArray([context JSGlobalContextRef], 0, NULL, 0) inContext:context];
100 + (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context
102 JSStringRef patternString = JSStringCreateWithCFString((CFStringRef)pattern);
103 JSStringRef flagsString = JSStringCreateWithCFString((CFStringRef)flags);
104 JSValueRef arguments[2] = { JSValueMakeString([context JSGlobalContextRef], patternString), JSValueMakeString([context JSGlobalContextRef], flagsString) };
105 JSStringRelease(patternString);
106 JSStringRelease(flagsString);
108 return [JSValue valueWithJSValueRef:JSObjectMakeRegExp([context JSGlobalContextRef], 2, arguments, 0) inContext:context];
111 + (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context
113 JSStringRef string = JSStringCreateWithCFString((CFStringRef)message);
114 JSValueRef argument = JSValueMakeString([context JSGlobalContextRef], string);
115 JSStringRelease(string);
117 return [JSValue valueWithJSValueRef:JSObjectMakeError([context JSGlobalContextRef], 1, &argument, 0) inContext:context];
120 + (JSValue *)valueWithNullInContext:(JSContext *)context
122 return [JSValue valueWithJSValueRef:JSValueMakeNull([context JSGlobalContextRef]) inContext:context];
125 + (JSValue *)valueWithUndefinedInContext:(JSContext *)context
127 return [JSValue valueWithJSValueRef:JSValueMakeUndefined([context JSGlobalContextRef]) inContext:context];
132 return valueToObject(_context, m_value);
135 - (id)toObjectOfClass:(Class)expectedClass
137 id result = [self toObject];
138 return [result isKindOfClass:expectedClass] ? result : nil;
143 return JSValueToBoolean([_context JSGlobalContextRef], m_value);
148 JSValueRef exception = 0;
149 double result = JSValueToNumber([_context JSGlobalContextRef], m_value, &exception);
151 [_context notifyException:exception];
152 return std::numeric_limits<double>::quiet_NaN();
160 return JSC::toInt32([self toDouble]);
165 return JSC::toUInt32([self toDouble]);
168 - (NSNumber *)toNumber
170 JSValueRef exception = 0;
171 id result = valueToNumber([_context JSGlobalContextRef], m_value, &exception);
173 [_context notifyException:exception];
177 - (NSString *)toString
179 JSValueRef exception = 0;
180 id result = valueToString([_context JSGlobalContextRef], m_value, &exception);
182 [_context notifyException:exception];
188 JSValueRef exception = 0;
189 id result = valueToDate([_context JSGlobalContextRef], m_value, &exception);
191 [_context notifyException:exception];
197 JSValueRef exception = 0;
198 id result = valueToArray([_context JSGlobalContextRef], m_value, &exception);
200 [_context notifyException:exception];
204 - (NSDictionary *)toDictionary
206 JSValueRef exception = 0;
207 id result = valueToDictionary([_context JSGlobalContextRef], m_value, &exception);
209 [_context notifyException:exception];
213 - (JSValue *)valueForProperty:(NSString *)propertyName
215 JSValueRef exception = 0;
216 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
218 return [_context valueFromNotifyException:exception];
220 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
221 JSValueRef result = JSObjectGetProperty([_context JSGlobalContextRef], object, name, &exception);
222 JSStringRelease(name);
224 return [_context valueFromNotifyException:exception];
226 return [JSValue valueWithJSValueRef:result inContext:_context];
229 - (void)setValue:(id)value forProperty:(NSString *)propertyName
231 JSValueRef exception = 0;
232 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
234 [_context notifyException:exception];
238 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
239 JSObjectSetProperty([_context JSGlobalContextRef], object, name, objectToValue(_context, value), 0, &exception);
240 JSStringRelease(name);
242 [_context notifyException:exception];
247 - (BOOL)deleteProperty:(NSString *)propertyName
249 JSValueRef exception = 0;
250 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
252 return [_context boolFromNotifyException:exception];
254 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
255 BOOL result = JSObjectDeleteProperty([_context JSGlobalContextRef], object, name, &exception);
256 JSStringRelease(name);
258 return [_context boolFromNotifyException:exception];
263 - (BOOL)hasProperty:(NSString *)propertyName
265 JSValueRef exception = 0;
266 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
268 return [_context boolFromNotifyException:exception];
270 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
271 BOOL result = JSObjectHasProperty([_context JSGlobalContextRef], object, name);
272 JSStringRelease(name);
276 - (void)defineProperty:(NSString *)property descriptor:(id)descriptor
278 [[_context globalObject][@"Object"] invokeMethod:@"defineProperty" withArguments:@[ self, property, descriptor ]];
281 - (JSValue *)valueAtIndex:(NSUInteger)index
283 // Properties that are higher than an unsigned value can hold are converted to a double then inserted as a normal property.
284 // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in get().
285 if (index != (unsigned)index)
286 return [self valueForProperty:[[JSValue valueWithDouble:index inContext:_context] toString]];
288 JSValueRef exception = 0;
289 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
291 return [_context valueFromNotifyException:exception];
293 JSValueRef result = JSObjectGetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, &exception);
295 return [_context valueFromNotifyException:exception];
297 return [JSValue valueWithJSValueRef:result inContext:_context];
300 - (void)setValue:(id)value atIndex:(NSUInteger)index
302 // Properties that are higher than an unsigned value can hold are converted to a double, then inserted as a normal property.
303 // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in putByIndex().
304 if (index != (unsigned)index)
305 return [self setValue:value forProperty:[[JSValue valueWithDouble:index inContext:_context] toString]];
307 JSValueRef exception = 0;
308 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
310 [_context notifyException:exception];
314 JSObjectSetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, objectToValue(_context, value), &exception);
316 [_context notifyException:exception];
323 return JSValueIsUndefined([_context JSGlobalContextRef], m_value);
328 return JSValueIsNull([_context JSGlobalContextRef], m_value);
333 return JSValueIsBoolean([_context JSGlobalContextRef], m_value);
338 return JSValueIsNumber([_context JSGlobalContextRef], m_value);
343 return JSValueIsString([_context JSGlobalContextRef], m_value);
348 return JSValueIsObject([_context JSGlobalContextRef], m_value);
351 - (BOOL)isEqualToObject:(id)value
353 return JSValueIsStrictEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value));
356 - (BOOL)isEqualWithTypeCoercionToObject:(id)value
358 JSValueRef exception = 0;
359 BOOL result = JSValueIsEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value), &exception);
361 return [_context boolFromNotifyException:exception];
366 - (BOOL)isInstanceOf:(id)value
368 JSValueRef exception = 0;
369 JSObjectRef constructor = JSValueToObject([_context JSGlobalContextRef], objectToValue(_context, value), &exception);
371 return [_context boolFromNotifyException:exception];
373 BOOL result = JSValueIsInstanceOfConstructor([_context JSGlobalContextRef], m_value, constructor, &exception);
375 return [_context boolFromNotifyException:exception];
380 - (JSValue *)callWithArguments:(NSArray *)argumentArray
382 NSUInteger argumentCount = [argumentArray count];
383 JSValueRef arguments[argumentCount];
384 for (unsigned i = 0; i < argumentCount; ++i)
385 arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]);
387 JSValueRef exception = 0;
388 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
390 return [_context valueFromNotifyException:exception];
392 JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, 0, argumentCount, arguments, &exception);
394 return [_context valueFromNotifyException:exception];
396 return [JSValue valueWithJSValueRef:result inContext:_context];
399 - (JSValue *)constructWithArguments:(NSArray *)argumentArray
401 NSUInteger argumentCount = [argumentArray count];
402 JSValueRef arguments[argumentCount];
403 for (unsigned i = 0; i < argumentCount; ++i)
404 arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]);
406 JSValueRef exception = 0;
407 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
409 return [_context valueFromNotifyException:exception];
411 JSObjectRef result = JSObjectCallAsConstructor([_context JSGlobalContextRef], object, argumentCount, arguments, &exception);
413 return [_context valueFromNotifyException:exception];
415 return [JSValue valueWithJSValueRef:result inContext:_context];
418 - (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments
420 NSUInteger argumentCount = [arguments count];
421 JSValueRef argumentArray[argumentCount];
422 for (unsigned i = 0; i < argumentCount; ++i)
423 argumentArray[i] = objectToValue(_context, [arguments objectAtIndex:i]);
425 JSValueRef exception = 0;
426 JSObjectRef thisObject = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
428 return [_context valueFromNotifyException:exception];
430 JSStringRef name = JSStringCreateWithCFString((CFStringRef)method);
431 JSValueRef function = JSObjectGetProperty([_context JSGlobalContextRef], thisObject, name, &exception);
432 JSStringRelease(name);
434 return [_context valueFromNotifyException:exception];
436 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], function, &exception);
438 return [_context valueFromNotifyException:exception];
440 JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, thisObject, argumentCount, argumentArray, &exception);
442 return [_context valueFromNotifyException:exception];
444 return [JSValue valueWithJSValueRef:result inContext:_context];
449 @implementation JSValue(StructSupport)
454 static_cast<CGFloat>([self[@"x"] toDouble]),
455 static_cast<CGFloat>([self[@"y"] toDouble])
462 [[self[@"location"] toNumber] unsignedIntegerValue],
463 [[self[@"length"] toNumber] unsignedIntegerValue]
478 static_cast<CGFloat>([self[@"width"] toDouble]),
479 static_cast<CGFloat>([self[@"height"] toDouble])
483 + (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context
485 return [JSValue valueWithObject:@{
488 } inContext:context];
491 + (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context
493 return [JSValue valueWithObject:@{
494 @"location":@(range.location),
495 @"length":@(range.length)
496 } inContext:context];
499 + (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context
501 return [JSValue valueWithObject:@{
502 @"x":@(rect.origin.x),
503 @"y":@(rect.origin.y),
504 @"width":@(rect.size.width),
505 @"height":@(rect.size.height)
506 } inContext:context];
509 + (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context
511 return [JSValue valueWithObject:@{
512 @"width":@(size.width),
513 @"height":@(size.height)
514 } inContext:context];
519 @implementation JSValue(SubscriptSupport)
521 - (JSValue *)objectForKeyedSubscript:(id)key
523 if (![key isKindOfClass:[NSString class]]) {
524 key = [[JSValue valueWithObject:key inContext:_context] toString];
526 return [JSValue valueWithUndefinedInContext:_context];
529 return [self valueForProperty:(NSString *)key];
532 - (JSValue *)objectAtIndexedSubscript:(NSUInteger)index
534 return [self valueAtIndex:index];
537 - (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key
539 if (![key isKindOfClass:[NSString class]]) {
540 key = [[JSValue valueWithObject:key inContext:_context] toString];
545 [self setValue:object forProperty:(NSString *)key];
548 - (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index
550 [self setValue:object atIndex:index];
555 inline bool isDate(JSObjectRef object, JSGlobalContextRef context)
557 JSC::APIEntryShim entryShim(toJS(context));
558 return toJS(object)->inherits(&JSC::DateInstance::s_info);
561 inline bool isArray(JSObjectRef object, JSGlobalContextRef context)
563 JSC::APIEntryShim entryShim(toJS(context));
564 return toJS(object)->inherits(&JSC::JSArray::s_info);
567 @implementation JSValue(Internal)
569 enum ConversionType {
575 class JSContainerConvertor {
583 JSContainerConvertor(JSGlobalContextRef context)
588 id convert(JSValueRef property);
591 bool isWorkListEmpty() const { return !m_worklist.size(); }
594 JSGlobalContextRef m_context;
595 HashMap<JSValueRef, id> m_objectMap;
596 Vector<Task> m_worklist;
599 inline id JSContainerConvertor::convert(JSValueRef value)
601 HashMap<JSValueRef, id>::iterator iter = m_objectMap.find(value);
602 if (iter != m_objectMap.end())
605 Task result = valueToObjectWithoutCopy(m_context, value);
611 void JSContainerConvertor::add(Task task)
613 m_objectMap.add(task.js, task.objc);
614 if (task.type != ContainerNone)
615 m_worklist.append(task);
618 JSContainerConvertor::Task JSContainerConvertor::take()
620 ASSERT(!isWorkListEmpty());
621 Task last = m_worklist.last();
622 m_worklist.removeLast();
626 static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef context, JSValueRef value)
628 if (!JSValueIsObject(context, value)) {
630 if (JSValueIsBoolean(context, value))
631 primitive = JSValueToBoolean(context, value) ? @YES : @NO;
632 else if (JSValueIsNumber(context, value)) {
633 // Normalize the number, so it will unique correctly in the hash map -
634 // it's nicer not to leak this internal implementation detail!
635 value = JSValueMakeNumber(context, JSValueToNumber(context, value, 0));
636 primitive = [NSNumber numberWithDouble:JSValueToNumber(context, value, 0)];
637 } else if (JSValueIsString(context, value)) {
638 // Would be nice to unique strings, too.
639 JSStringRef jsstring = JSValueToStringCopy(context, value, 0);
640 NSString * stringNS = (NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring);
641 JSStringRelease(jsstring);
642 primitive = [stringNS autorelease];
643 } else if (JSValueIsNull(context, value))
644 primitive = [NSNull null];
646 ASSERT(JSValueIsUndefined(context, value));
649 return (JSContainerConvertor::Task){ value, primitive, ContainerNone };
652 JSObjectRef object = JSValueToObject(context, value, 0);
654 if (id wrapped = tryUnwrapObjcObject(context, object))
655 return (JSContainerConvertor::Task){ object, wrapped, ContainerNone };
657 if (isDate(object, context))
658 return (JSContainerConvertor::Task){ object, [NSDate dateWithTimeIntervalSince1970:JSValueToNumber(context, object, 0)], ContainerNone };
660 if (isArray(object, context))
661 return (JSContainerConvertor::Task){ object, [NSMutableArray array], ContainerArray };
663 return (JSContainerConvertor::Task){ object, [NSMutableDictionary dictionary], ContainerDictionary };
666 static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task)
668 ASSERT(task.type != ContainerNone);
669 JSContainerConvertor convertor(context);
671 ASSERT(!convertor.isWorkListEmpty());
674 JSContainerConvertor::Task current = convertor.take();
675 ASSERT(JSValueIsObject(context, current.js));
676 JSObjectRef js = JSValueToObject(context, current.js, 0);
678 if (current.type == ContainerArray) {
679 ASSERT([current.objc isKindOfClass:[NSMutableArray class]]);
680 NSMutableArray *array = (NSMutableArray *)current.objc;
682 JSStringRef lengthString = JSStringCreateWithUTF8CString("length");
683 unsigned length = JSC::toUInt32(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString, 0), 0));
684 JSStringRelease(lengthString);
686 for (unsigned i = 0; i < length; ++i) {
687 id objc = convertor.convert(JSObjectGetPropertyAtIndex(context, js, i, 0));
688 [array addObject:objc ? objc : [NSNull null]];
691 ASSERT([current.objc isKindOfClass:[NSMutableDictionary class]]);
692 NSMutableDictionary *dictionary = (NSMutableDictionary *)current.objc;
694 JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js);
695 size_t length = JSPropertyNameArrayGetCount(propertyNameArray);
697 for (size_t i = 0; i < length; ++i) {
698 JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNameArray, i);
699 if (id objc = convertor.convert(JSObjectGetProperty(context, js, propertyName, 0)))
700 dictionary[[(NSString *)JSStringCopyCFString(kCFAllocatorDefault, propertyName) autorelease]] = objc;
703 JSPropertyNameArrayRelease(propertyNameArray);
706 } while (!convertor.isWorkListEmpty());
711 id valueToObject(JSContext *context, JSValueRef value)
713 JSContainerConvertor::Task result = valueToObjectWithoutCopy([context JSGlobalContextRef], value);
714 if (result.type == ContainerNone)
716 return containerValueToObject([context JSGlobalContextRef], result);
719 id valueToNumber(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
722 if (id wrapped = tryUnwrapObjcObject(context, value)) {
723 if ([wrapped isKindOfClass:[NSNumber class]])
727 if (JSValueIsBoolean(context, value))
728 return JSValueToBoolean(context, value) ? @YES : @NO;
730 double result = JSValueToNumber(context, value, exception);
731 return [NSNumber numberWithDouble:*exception ? std::numeric_limits<double>::quiet_NaN() : result];
734 id valueToString(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
737 if (id wrapped = tryUnwrapObjcObject(context, value)) {
738 if ([wrapped isKindOfClass:[NSString class]])
742 JSStringRef jsstring = JSValueToStringCopy(context, value, exception);
748 NSString *stringNS = [(NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring) autorelease];
749 JSStringRelease(jsstring);
753 id valueToDate(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
756 if (id wrapped = tryUnwrapObjcObject(context, value)) {
757 if ([wrapped isKindOfClass:[NSDate class]])
761 double result = JSValueToNumber(context, value, exception);
762 return *exception ? nil : [NSDate dateWithTimeIntervalSince1970:result];
765 id valueToArray(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
768 if (id wrapped = tryUnwrapObjcObject(context, value)) {
769 if ([wrapped isKindOfClass:[NSArray class]])
773 if (JSValueIsObject(context, value))
774 return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableArray array], ContainerArray});
776 if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value)))
777 *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSArray"));
781 id valueToDictionary(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
784 if (id wrapped = tryUnwrapObjcObject(context, value)) {
785 if ([wrapped isKindOfClass:[NSDictionary class]])
789 if (JSValueIsObject(context, value))
790 return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableDictionary dictionary], ContainerDictionary});
792 if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value)))
793 *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSDictionary"));
797 class ObjcContainerConvertor {
805 ObjcContainerConvertor(JSContext *context)
810 JSValueRef convert(id object);
813 bool isWorkListEmpty() const { return !m_worklist.size(); }
816 JSContext *m_context;
817 HashMap<id, JSValueRef> m_objectMap;
818 Vector<Task> m_worklist;
821 JSValueRef ObjcContainerConvertor::convert(id object)
825 auto it = m_objectMap.find(object);
826 if (it != m_objectMap.end())
829 ObjcContainerConvertor::Task task = objectToValueWithoutCopy(m_context, object);
834 void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task)
836 m_objectMap.add(task.objc, task.js);
837 if (task.type != ContainerNone)
838 m_worklist.append(task);
841 ObjcContainerConvertor::Task ObjcContainerConvertor::take()
843 ASSERT(!isWorkListEmpty());
844 Task last = m_worklist.last();
845 m_worklist.removeLast();
849 inline bool isNSBoolean(id object)
851 ASSERT([@YES class] == [@NO class]);
852 ASSERT([@YES class] != [NSNumber class]);
853 ASSERT([[@YES class] isSubclassOfClass:[NSNumber class]]);
854 return [object isKindOfClass:[@YES class]];
857 static ObjcContainerConvertor::Task objectToValueWithoutCopy(JSContext *context, id object)
859 JSGlobalContextRef contextRef = [context JSGlobalContextRef];
862 return (ObjcContainerConvertor::Task){ object, JSValueMakeUndefined(contextRef), ContainerNone };
864 if (!class_conformsToProtocol(object_getClass(object), getJSExportProtocol())) {
865 if ([object isKindOfClass:[NSArray class]])
866 return (ObjcContainerConvertor::Task){ object, JSObjectMakeArray(contextRef, 0, NULL, 0), ContainerArray };
868 if ([object isKindOfClass:[NSDictionary class]])
869 return (ObjcContainerConvertor::Task){ object, JSObjectMake(contextRef, 0, 0), ContainerDictionary };
871 if ([object isKindOfClass:[NSNull class]])
872 return (ObjcContainerConvertor::Task){ object, JSValueMakeNull(contextRef), ContainerNone };
874 if ([object isKindOfClass:[JSValue class]])
875 return (ObjcContainerConvertor::Task){ object, ((JSValue *)object)->m_value, ContainerNone };
877 if ([object isKindOfClass:[NSString class]]) {
878 JSStringRef string = JSStringCreateWithCFString((CFStringRef)object);
879 JSValueRef js = JSValueMakeString(contextRef, string);
880 JSStringRelease(string);
881 return (ObjcContainerConvertor::Task){ object, js, ContainerNone };
884 if ([object isKindOfClass:[NSNumber class]]) {
885 if (isNSBoolean(object))
886 return (ObjcContainerConvertor::Task){ object, JSValueMakeBoolean(contextRef, [object boolValue]), ContainerNone };
887 return (ObjcContainerConvertor::Task){ object, JSValueMakeNumber(contextRef, [object doubleValue]), ContainerNone };
890 if ([object isKindOfClass:[NSDate class]]) {
891 JSValueRef argument = JSValueMakeNumber(contextRef, [object timeIntervalSince1970]);
892 JSObjectRef result = JSObjectMakeDate(contextRef, 1, &argument, 0);
893 return (ObjcContainerConvertor::Task){ object, result, ContainerNone };
896 if ([object isKindOfClass:[JSManagedValue class]]) {
897 JSValue *value = [static_cast<JSManagedValue *>(object) value];
899 return (ObjcContainerConvertor::Task) { object, JSValueMakeUndefined(contextRef), ContainerNone };
900 return (ObjcContainerConvertor::Task){ object, value->m_value, ContainerNone };
904 return (ObjcContainerConvertor::Task){ object, valueInternalValue([context wrapperForObjCObject:object]), ContainerNone };
907 JSValueRef objectToValue(JSContext *context, id object)
909 JSGlobalContextRef contextRef = [context JSGlobalContextRef];
911 ObjcContainerConvertor::Task task = objectToValueWithoutCopy(context, object);
912 if (task.type == ContainerNone)
915 ObjcContainerConvertor convertor(context);
917 ASSERT(!convertor.isWorkListEmpty());
920 ObjcContainerConvertor::Task current = convertor.take();
921 ASSERT(JSValueIsObject(contextRef, current.js));
922 JSObjectRef js = JSValueToObject(contextRef, current.js, 0);
924 if (current.type == ContainerArray) {
925 ASSERT([current.objc isKindOfClass:[NSArray class]]);
926 NSArray *array = (NSArray *)current.objc;
927 NSUInteger count = [array count];
928 for (NSUInteger index = 0; index < count; ++index)
929 JSObjectSetPropertyAtIndex(contextRef, js, index, convertor.convert([array objectAtIndex:index]), 0);
931 ASSERT(current.type == ContainerDictionary);
932 ASSERT([current.objc isKindOfClass:[NSDictionary class]]);
933 NSDictionary *dictionary = (NSDictionary *)current.objc;
934 for (id key in [dictionary keyEnumerator]) {
935 if ([key isKindOfClass:[NSString class]]) {
936 JSStringRef propertyName = JSStringCreateWithCFString((CFStringRef)key);
937 JSObjectSetProperty(contextRef, js, propertyName, convertor.convert([dictionary objectForKey:key]), 0, 0);
938 JSStringRelease(propertyName);
943 } while (!convertor.isWorkListEmpty());
948 JSValueRef valueInternalValue(JSValue * value)
950 return value->m_value;
953 + (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context
955 return [context wrapperForJSObject:value];
963 - (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context
965 if (!value || !context)
972 _context = [context retain];
974 JSValueProtect([_context JSGlobalContextRef], m_value);
978 struct StructTagHandler {
982 typedef HashMap<String, StructTagHandler> StructHandlers;
984 static StructHandlers* createStructHandlerMap()
986 StructHandlers* structHandlers = new StructHandlers();
988 size_t valueWithXinContextLength = strlen("valueWithX:inContext:");
989 size_t toXLength = strlen("toX");
991 // Step 1: find all valueWith<Foo>:inContext: class methods in JSValue.
992 forEachMethodInClass(object_getClass([JSValue class]), ^(Method method){
993 SEL selector = method_getName(method);
994 const char* name = sel_getName(selector);
995 size_t nameLength = strlen(name);
996 // Check for valueWith<Foo>:context:
997 if (nameLength < valueWithXinContextLength || memcmp(name, "valueWith", 9) || memcmp(name + nameLength - 11, ":inContext:", 11))
999 // Check for [ id, SEL, <type>, <contextType> ]
1000 if (method_getNumberOfArguments(method) != 4)
1003 // Check 2nd argument type is "@"
1004 char* secondType = method_copyArgumentType(method, 3);
1005 if (strcmp(secondType, "@") != 0) {
1010 // Check result type is also "@"
1011 method_getReturnType(method, idType, 3);
1012 if (strcmp(idType, "@") != 0)
1014 char* type = method_copyArgumentType(method, 2);
1015 structHandlers->add(StringImpl::create(type), (StructTagHandler){ selector, 0 });
1019 // Step 2: find all to<Foo> instance methods in JSValue.
1020 forEachMethodInClass([JSValue class], ^(Method method){
1021 SEL selector = method_getName(method);
1022 const char* name = sel_getName(selector);
1023 size_t nameLength = strlen(name);
1024 // Check for to<Foo>
1025 if (nameLength < toXLength || memcmp(name, "to", 2))
1027 // Check for [ id, SEL ]
1028 if (method_getNumberOfArguments(method) != 2)
1030 // Try to find a matching valueWith<Foo>:context: method.
1031 char* type = method_copyReturnType(method);
1033 StructHandlers::iterator iter = structHandlers->find(type);
1035 if (iter == structHandlers->end())
1037 StructTagHandler& handler = iter->value;
1039 // check that strlen(<foo>) == strlen(<Foo>)
1040 const char* valueWithName = sel_getName(handler.typeToValueSEL);
1041 size_t valueWithLength = strlen(valueWithName);
1042 if (valueWithLength - valueWithXinContextLength != nameLength - toXLength)
1044 // Check that <Foo> == <Foo>
1045 if (memcmp(valueWithName + 9, name + 2, nameLength - toXLength - 1))
1047 handler.valueToTypeSEL = selector;
1050 // Step 3: clean up - remove entries where we found prospective valueWith<Foo>:inContext: conversions, but no matching to<Foo> methods.
1051 typedef HashSet<String> RemoveSet;
1052 RemoveSet removeSet;
1053 for (StructHandlers::iterator iter = structHandlers->begin(); iter != structHandlers->end(); ++iter) {
1054 StructTagHandler& handler = iter->value;
1055 if (!handler.valueToTypeSEL)
1056 removeSet.add(iter->key);
1059 for (RemoveSet::iterator iter = removeSet.begin(); iter != removeSet.end(); ++iter)
1060 structHandlers->remove(*iter);
1062 return structHandlers;
1065 static StructTagHandler* handerForStructTag(const char* encodedType)
1067 static SpinLock handerForStructTagLock = SPINLOCK_INITIALIZER;
1068 SpinLockHolder lockHolder(&handerForStructTagLock);
1070 static StructHandlers* structHandlers = createStructHandlerMap();
1072 StructHandlers::iterator iter = structHandlers->find(encodedType);
1073 if (iter == structHandlers->end())
1075 return &iter->value;
1078 + (SEL)selectorForStructToValue:(const char *)structTag
1080 StructTagHandler* handler = handerForStructTag(structTag);
1081 return handler ? handler->typeToValueSEL : nil;
1084 + (SEL)selectorForValueToStruct:(const char *)structTag
1086 StructTagHandler* handler = handerForStructTag(structTag);
1087 return handler ? handler->valueToTypeSEL : nil;
1092 JSValueUnprotect([_context JSGlobalContextRef], m_value);
1098 - (NSString *)description
1100 if (id wrapped = tryUnwrapObjcObject([_context JSGlobalContextRef], m_value))
1101 return [wrapped description];
1102 return [self toString];
1105 NSInvocation *typeToValueInvocationFor(const char* encodedType)
1107 SEL selector = [JSValue selectorForStructToValue:encodedType];
1111 const char* methodTypes = method_getTypeEncoding(class_getClassMethod([JSValue class], selector));
1112 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
1113 [invocation setSelector:selector];
1117 NSInvocation *valueToTypeInvocationFor(const char* encodedType)
1119 SEL selector = [JSValue selectorForValueToStruct:encodedType];
1123 const char* methodTypes = method_getTypeEncoding(class_getInstanceMethod([JSValue class], selector));
1124 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
1125 [invocation setSelector:selector];