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 "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>
49 #if JSC_OBJC_API_ENABLED
51 NSString * const JSPropertyDescriptorWritableKey = @"writable";
52 NSString * const JSPropertyDescriptorEnumerableKey = @"enumerable";
53 NSString * const JSPropertyDescriptorConfigurableKey = @"configurable";
54 NSString * const JSPropertyDescriptorValueKey = @"value";
55 NSString * const JSPropertyDescriptorGetKey = @"get";
56 NSString * const JSPropertyDescriptorSetKey = @"set";
58 @implementation JSValue {
62 - (JSValueRef)JSValueRef
67 + (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context
69 return [JSValue valueWithJSValueRef:objectToValue(context, value) inContext:context];
72 + (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context
74 return [JSValue valueWithJSValueRef:JSValueMakeBoolean([context JSGlobalContextRef], value) inContext:context];
77 + (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context
79 return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
82 + (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context
84 return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
87 + (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context
89 return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
92 + (JSValue *)valueWithNewObjectInContext:(JSContext *)context
94 return [JSValue valueWithJSValueRef:JSObjectMake([context JSGlobalContextRef], 0, 0) inContext:context];
97 + (JSValue *)valueWithNewArrayInContext:(JSContext *)context
99 return [JSValue valueWithJSValueRef:JSObjectMakeArray([context JSGlobalContextRef], 0, NULL, 0) inContext:context];
102 + (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context
104 JSStringRef patternString = JSStringCreateWithCFString((CFStringRef)pattern);
105 JSStringRef flagsString = JSStringCreateWithCFString((CFStringRef)flags);
106 JSValueRef arguments[2] = { JSValueMakeString([context JSGlobalContextRef], patternString), JSValueMakeString([context JSGlobalContextRef], flagsString) };
107 JSStringRelease(patternString);
108 JSStringRelease(flagsString);
110 return [JSValue valueWithJSValueRef:JSObjectMakeRegExp([context JSGlobalContextRef], 2, arguments, 0) inContext:context];
113 + (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context
115 JSStringRef string = JSStringCreateWithCFString((CFStringRef)message);
116 JSValueRef argument = JSValueMakeString([context JSGlobalContextRef], string);
117 JSStringRelease(string);
119 return [JSValue valueWithJSValueRef:JSObjectMakeError([context JSGlobalContextRef], 1, &argument, 0) inContext:context];
122 + (JSValue *)valueWithNullInContext:(JSContext *)context
124 return [JSValue valueWithJSValueRef:JSValueMakeNull([context JSGlobalContextRef]) inContext:context];
127 + (JSValue *)valueWithUndefinedInContext:(JSContext *)context
129 return [JSValue valueWithJSValueRef:JSValueMakeUndefined([context JSGlobalContextRef]) inContext:context];
134 return valueToObject(_context, m_value);
137 - (id)toObjectOfClass:(Class)expectedClass
139 id result = [self toObject];
140 return [result isKindOfClass:expectedClass] ? result : nil;
145 return JSValueToBoolean([_context JSGlobalContextRef], m_value);
150 JSValueRef exception = 0;
151 double result = JSValueToNumber([_context JSGlobalContextRef], m_value, &exception);
153 [_context notifyException:exception];
154 return std::numeric_limits<double>::quiet_NaN();
162 return JSC::toInt32([self toDouble]);
167 return JSC::toUInt32([self toDouble]);
170 - (NSNumber *)toNumber
172 JSValueRef exception = 0;
173 id result = valueToNumber([_context JSGlobalContextRef], m_value, &exception);
175 [_context notifyException:exception];
179 - (NSString *)toString
181 JSValueRef exception = 0;
182 id result = valueToString([_context JSGlobalContextRef], m_value, &exception);
184 [_context notifyException:exception];
190 JSValueRef exception = 0;
191 id result = valueToDate([_context JSGlobalContextRef], m_value, &exception);
193 [_context notifyException:exception];
199 JSValueRef exception = 0;
200 id result = valueToArray([_context JSGlobalContextRef], m_value, &exception);
202 [_context notifyException:exception];
206 - (NSDictionary *)toDictionary
208 JSValueRef exception = 0;
209 id result = valueToDictionary([_context JSGlobalContextRef], m_value, &exception);
211 [_context notifyException:exception];
215 - (JSValue *)valueForProperty:(NSString *)propertyName
217 JSValueRef exception = 0;
218 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
220 return [_context valueFromNotifyException:exception];
222 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
223 JSValueRef result = JSObjectGetProperty([_context JSGlobalContextRef], object, name, &exception);
224 JSStringRelease(name);
226 return [_context valueFromNotifyException:exception];
228 return [JSValue valueWithJSValueRef:result inContext:_context];
231 - (void)setValue:(id)value forProperty:(NSString *)propertyName
233 JSValueRef exception = 0;
234 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
236 [_context notifyException:exception];
240 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
241 JSObjectSetProperty([_context JSGlobalContextRef], object, name, objectToValue(_context, value), 0, &exception);
242 JSStringRelease(name);
244 [_context notifyException:exception];
249 - (BOOL)deleteProperty:(NSString *)propertyName
251 JSValueRef exception = 0;
252 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
254 return [_context boolFromNotifyException:exception];
256 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
257 BOOL result = JSObjectDeleteProperty([_context JSGlobalContextRef], object, name, &exception);
258 JSStringRelease(name);
260 return [_context boolFromNotifyException:exception];
265 - (BOOL)hasProperty:(NSString *)propertyName
267 JSValueRef exception = 0;
268 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
270 return [_context boolFromNotifyException:exception];
272 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
273 BOOL result = JSObjectHasProperty([_context JSGlobalContextRef], object, name);
274 JSStringRelease(name);
278 - (void)defineProperty:(NSString *)property descriptor:(id)descriptor
280 [[_context globalObject][@"Object"] invokeMethod:@"defineProperty" withArguments:@[ self, property, descriptor ]];
283 - (JSValue *)valueAtIndex:(NSUInteger)index
285 // Properties that are higher than an unsigned value can hold are converted to a double then inserted as a normal property.
286 // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in get().
287 if (index != (unsigned)index)
288 return [self valueForProperty:[[JSValue valueWithDouble:index inContext:_context] toString]];
290 JSValueRef exception = 0;
291 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
293 return [_context valueFromNotifyException:exception];
295 JSValueRef result = JSObjectGetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, &exception);
297 return [_context valueFromNotifyException:exception];
299 return [JSValue valueWithJSValueRef:result inContext:_context];
302 - (void)setValue:(id)value atIndex:(NSUInteger)index
304 // Properties that are higher than an unsigned value can hold are converted to a double, then inserted as a normal property.
305 // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in putByIndex().
306 if (index != (unsigned)index)
307 return [self setValue:value forProperty:[[JSValue valueWithDouble:index inContext:_context] toString]];
309 JSValueRef exception = 0;
310 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
312 [_context notifyException:exception];
316 JSObjectSetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, objectToValue(_context, value), &exception);
318 [_context notifyException:exception];
325 return JSValueIsUndefined([_context JSGlobalContextRef], m_value);
330 return JSValueIsNull([_context JSGlobalContextRef], m_value);
335 return JSValueIsBoolean([_context JSGlobalContextRef], m_value);
340 return JSValueIsNumber([_context JSGlobalContextRef], m_value);
345 return JSValueIsString([_context JSGlobalContextRef], m_value);
350 return JSValueIsObject([_context JSGlobalContextRef], m_value);
353 - (BOOL)isEqualToObject:(id)value
355 return JSValueIsStrictEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value));
358 - (BOOL)isEqualWithTypeCoercionToObject:(id)value
360 JSValueRef exception = 0;
361 BOOL result = JSValueIsEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value), &exception);
363 return [_context boolFromNotifyException:exception];
368 - (BOOL)isInstanceOf:(id)value
370 JSValueRef exception = 0;
371 JSObjectRef constructor = JSValueToObject([_context JSGlobalContextRef], objectToValue(_context, value), &exception);
373 return [_context boolFromNotifyException:exception];
375 BOOL result = JSValueIsInstanceOfConstructor([_context JSGlobalContextRef], m_value, constructor, &exception);
377 return [_context boolFromNotifyException:exception];
382 - (JSValue *)callWithArguments:(NSArray *)argumentArray
384 NSUInteger argumentCount = [argumentArray count];
385 JSValueRef arguments[argumentCount];
386 for (unsigned i = 0; i < argumentCount; ++i)
387 arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]);
389 JSValueRef exception = 0;
390 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
392 return [_context valueFromNotifyException:exception];
394 JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, 0, argumentCount, arguments, &exception);
396 return [_context valueFromNotifyException:exception];
398 return [JSValue valueWithJSValueRef:result inContext:_context];
401 - (JSValue *)constructWithArguments:(NSArray *)argumentArray
403 NSUInteger argumentCount = [argumentArray count];
404 JSValueRef arguments[argumentCount];
405 for (unsigned i = 0; i < argumentCount; ++i)
406 arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]);
408 JSValueRef exception = 0;
409 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
411 return [_context valueFromNotifyException:exception];
413 JSObjectRef result = JSObjectCallAsConstructor([_context JSGlobalContextRef], object, argumentCount, arguments, &exception);
415 return [_context valueFromNotifyException:exception];
417 return [JSValue valueWithJSValueRef:result inContext:_context];
420 - (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments
422 NSUInteger argumentCount = [arguments count];
423 JSValueRef argumentArray[argumentCount];
424 for (unsigned i = 0; i < argumentCount; ++i)
425 argumentArray[i] = objectToValue(_context, [arguments objectAtIndex:i]);
427 JSValueRef exception = 0;
428 JSObjectRef thisObject = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
430 return [_context valueFromNotifyException:exception];
432 JSStringRef name = JSStringCreateWithCFString((CFStringRef)method);
433 JSValueRef function = JSObjectGetProperty([_context JSGlobalContextRef], thisObject, name, &exception);
434 JSStringRelease(name);
436 return [_context valueFromNotifyException:exception];
438 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], function, &exception);
440 return [_context valueFromNotifyException:exception];
442 JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, thisObject, argumentCount, argumentArray, &exception);
444 return [_context valueFromNotifyException:exception];
446 return [JSValue valueWithJSValueRef:result inContext:_context];
451 @implementation JSValue(StructSupport)
456 static_cast<CGFloat>([self[@"x"] toDouble]),
457 static_cast<CGFloat>([self[@"y"] toDouble])
464 [[self[@"location"] toNumber] unsignedIntegerValue],
465 [[self[@"length"] toNumber] unsignedIntegerValue]
480 static_cast<CGFloat>([self[@"width"] toDouble]),
481 static_cast<CGFloat>([self[@"height"] toDouble])
485 + (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context
487 return [JSValue valueWithObject:@{
490 } inContext:context];
493 + (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context
495 return [JSValue valueWithObject:@{
496 @"location":@(range.location),
497 @"length":@(range.length)
498 } inContext:context];
501 + (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context
503 return [JSValue valueWithObject:@{
504 @"x":@(rect.origin.x),
505 @"y":@(rect.origin.y),
506 @"width":@(rect.size.width),
507 @"height":@(rect.size.height)
508 } inContext:context];
511 + (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context
513 return [JSValue valueWithObject:@{
514 @"width":@(size.width),
515 @"height":@(size.height)
516 } inContext:context];
521 @implementation JSValue(SubscriptSupport)
523 - (JSValue *)objectForKeyedSubscript:(id)key
525 if (![key isKindOfClass:[NSString class]]) {
526 key = [[JSValue valueWithObject:key inContext:_context] toString];
528 return [JSValue valueWithUndefinedInContext:_context];
531 return [self valueForProperty:(NSString *)key];
534 - (JSValue *)objectAtIndexedSubscript:(NSUInteger)index
536 return [self valueAtIndex:index];
539 - (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key
541 if (![key isKindOfClass:[NSString class]]) {
542 key = [[JSValue valueWithObject:key inContext:_context] toString];
547 [self setValue:object forProperty:(NSString *)key];
550 - (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index
552 [self setValue:object atIndex:index];
557 inline bool isDate(JSObjectRef object, JSGlobalContextRef context)
559 JSC::APIEntryShim entryShim(toJS(context));
560 return toJS(object)->inherits(&JSC::DateInstance::s_info);
563 inline bool isArray(JSObjectRef object, JSGlobalContextRef context)
565 JSC::APIEntryShim entryShim(toJS(context));
566 return toJS(object)->inherits(&JSC::JSArray::s_info);
569 @implementation JSValue(Internal)
571 enum ConversionType {
577 class JSContainerConvertor {
585 JSContainerConvertor(JSGlobalContextRef context)
590 id convert(JSValueRef property);
593 bool isWorkListEmpty() const { return !m_worklist.size(); }
596 JSGlobalContextRef m_context;
597 HashMap<JSValueRef, id> m_objectMap;
598 Vector<Task> m_worklist;
599 Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
602 inline id JSContainerConvertor::convert(JSValueRef value)
604 HashMap<JSValueRef, id>::iterator iter = m_objectMap.find(value);
605 if (iter != m_objectMap.end())
608 Task result = valueToObjectWithoutCopy(m_context, value);
614 void JSContainerConvertor::add(Task task)
616 JSC::ExecState* exec = toJS(m_context);
617 m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
618 m_objectMap.add(task.js, task.objc);
619 if (task.type != ContainerNone)
620 m_worklist.append(task);
623 JSContainerConvertor::Task JSContainerConvertor::take()
625 ASSERT(!isWorkListEmpty());
626 Task last = m_worklist.last();
627 m_worklist.removeLast();
631 static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef context, JSValueRef value)
633 if (!JSValueIsObject(context, value)) {
635 if (JSValueIsBoolean(context, value))
636 primitive = JSValueToBoolean(context, value) ? @YES : @NO;
637 else if (JSValueIsNumber(context, value)) {
638 // Normalize the number, so it will unique correctly in the hash map -
639 // it's nicer not to leak this internal implementation detail!
640 value = JSValueMakeNumber(context, JSValueToNumber(context, value, 0));
641 primitive = [NSNumber numberWithDouble:JSValueToNumber(context, value, 0)];
642 } else if (JSValueIsString(context, value)) {
643 // Would be nice to unique strings, too.
644 JSStringRef jsstring = JSValueToStringCopy(context, value, 0);
645 NSString * stringNS = (NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring);
646 JSStringRelease(jsstring);
647 primitive = [stringNS autorelease];
648 } else if (JSValueIsNull(context, value))
649 primitive = [NSNull null];
651 ASSERT(JSValueIsUndefined(context, value));
654 return (JSContainerConvertor::Task){ value, primitive, ContainerNone };
657 JSObjectRef object = JSValueToObject(context, value, 0);
659 if (id wrapped = tryUnwrapObjcObject(context, object))
660 return (JSContainerConvertor::Task){ object, wrapped, ContainerNone };
662 if (isDate(object, context))
663 return (JSContainerConvertor::Task){ object, [NSDate dateWithTimeIntervalSince1970:JSValueToNumber(context, object, 0)], ContainerNone };
665 if (isArray(object, context))
666 return (JSContainerConvertor::Task){ object, [NSMutableArray array], ContainerArray };
668 return (JSContainerConvertor::Task){ object, [NSMutableDictionary dictionary], ContainerDictionary };
671 static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task)
673 ASSERT(task.type != ContainerNone);
674 JSC::APIEntryShim entryShim(toJS(context));
675 JSContainerConvertor convertor(context);
677 ASSERT(!convertor.isWorkListEmpty());
680 JSContainerConvertor::Task current = convertor.take();
681 ASSERT(JSValueIsObject(context, current.js));
682 JSObjectRef js = JSValueToObject(context, current.js, 0);
684 if (current.type == ContainerArray) {
685 ASSERT([current.objc isKindOfClass:[NSMutableArray class]]);
686 NSMutableArray *array = (NSMutableArray *)current.objc;
688 JSStringRef lengthString = JSStringCreateWithUTF8CString("length");
689 unsigned length = JSC::toUInt32(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString, 0), 0));
690 JSStringRelease(lengthString);
692 for (unsigned i = 0; i < length; ++i) {
693 id objc = convertor.convert(JSObjectGetPropertyAtIndex(context, js, i, 0));
694 [array addObject:objc ? objc : [NSNull null]];
697 ASSERT([current.objc isKindOfClass:[NSMutableDictionary class]]);
698 NSMutableDictionary *dictionary = (NSMutableDictionary *)current.objc;
700 JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js);
701 size_t length = JSPropertyNameArrayGetCount(propertyNameArray);
703 for (size_t i = 0; i < length; ++i) {
704 JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNameArray, i);
705 if (id objc = convertor.convert(JSObjectGetProperty(context, js, propertyName, 0)))
706 dictionary[[(NSString *)JSStringCopyCFString(kCFAllocatorDefault, propertyName) autorelease]] = objc;
709 JSPropertyNameArrayRelease(propertyNameArray);
712 } while (!convertor.isWorkListEmpty());
717 id valueToObject(JSContext *context, JSValueRef value)
719 JSContainerConvertor::Task result = valueToObjectWithoutCopy([context JSGlobalContextRef], value);
720 if (result.type == ContainerNone)
722 return containerValueToObject([context JSGlobalContextRef], result);
725 id valueToNumber(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
728 if (id wrapped = tryUnwrapObjcObject(context, value)) {
729 if ([wrapped isKindOfClass:[NSNumber class]])
733 if (JSValueIsBoolean(context, value))
734 return JSValueToBoolean(context, value) ? @YES : @NO;
736 double result = JSValueToNumber(context, value, exception);
737 return [NSNumber numberWithDouble:*exception ? std::numeric_limits<double>::quiet_NaN() : result];
740 id valueToString(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
743 if (id wrapped = tryUnwrapObjcObject(context, value)) {
744 if ([wrapped isKindOfClass:[NSString class]])
748 JSStringRef jsstring = JSValueToStringCopy(context, value, exception);
754 NSString *stringNS = HardAutorelease(JSStringCopyCFString(kCFAllocatorDefault, jsstring));
755 JSStringRelease(jsstring);
759 id valueToDate(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
762 if (id wrapped = tryUnwrapObjcObject(context, value)) {
763 if ([wrapped isKindOfClass:[NSDate class]])
767 double result = JSValueToNumber(context, value, exception);
768 return *exception ? nil : [NSDate dateWithTimeIntervalSince1970:result];
771 id valueToArray(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
774 if (id wrapped = tryUnwrapObjcObject(context, value)) {
775 if ([wrapped isKindOfClass:[NSArray class]])
779 if (JSValueIsObject(context, value))
780 return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableArray array], ContainerArray});
782 if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value)))
783 *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSArray"));
787 id valueToDictionary(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
790 if (id wrapped = tryUnwrapObjcObject(context, value)) {
791 if ([wrapped isKindOfClass:[NSDictionary class]])
795 if (JSValueIsObject(context, value))
796 return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableDictionary dictionary], ContainerDictionary});
798 if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value)))
799 *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSDictionary"));
803 class ObjcContainerConvertor {
811 ObjcContainerConvertor(JSContext *context)
816 JSValueRef convert(id object);
819 bool isWorkListEmpty() const { return !m_worklist.size(); }
822 JSContext *m_context;
823 HashMap<id, JSValueRef> m_objectMap;
824 Vector<Task> m_worklist;
825 Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
828 JSValueRef ObjcContainerConvertor::convert(id object)
832 auto it = m_objectMap.find(object);
833 if (it != m_objectMap.end())
836 ObjcContainerConvertor::Task task = objectToValueWithoutCopy(m_context, object);
841 void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task)
843 JSC::ExecState* exec = toJS(m_context.JSGlobalContextRef);
844 m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
845 m_objectMap.add(task.objc, task.js);
846 if (task.type != ContainerNone)
847 m_worklist.append(task);
850 ObjcContainerConvertor::Task ObjcContainerConvertor::take()
852 ASSERT(!isWorkListEmpty());
853 Task last = m_worklist.last();
854 m_worklist.removeLast();
858 inline bool isNSBoolean(id object)
860 ASSERT([@YES class] == [@NO class]);
861 ASSERT([@YES class] != [NSNumber class]);
862 ASSERT([[@YES class] isSubclassOfClass:[NSNumber class]]);
863 return [object isKindOfClass:[@YES class]];
866 static ObjcContainerConvertor::Task objectToValueWithoutCopy(JSContext *context, id object)
868 JSGlobalContextRef contextRef = [context JSGlobalContextRef];
871 return (ObjcContainerConvertor::Task){ object, JSValueMakeUndefined(contextRef), ContainerNone };
873 if (!class_conformsToProtocol(object_getClass(object), getJSExportProtocol())) {
874 if ([object isKindOfClass:[NSArray class]])
875 return (ObjcContainerConvertor::Task){ object, JSObjectMakeArray(contextRef, 0, NULL, 0), ContainerArray };
877 if ([object isKindOfClass:[NSDictionary class]])
878 return (ObjcContainerConvertor::Task){ object, JSObjectMake(contextRef, 0, 0), ContainerDictionary };
880 if ([object isKindOfClass:[NSNull class]])
881 return (ObjcContainerConvertor::Task){ object, JSValueMakeNull(contextRef), ContainerNone };
883 if ([object isKindOfClass:[JSValue class]])
884 return (ObjcContainerConvertor::Task){ object, ((JSValue *)object)->m_value, ContainerNone };
886 if ([object isKindOfClass:[NSString class]]) {
887 JSStringRef string = JSStringCreateWithCFString((CFStringRef)object);
888 JSValueRef js = JSValueMakeString(contextRef, string);
889 JSStringRelease(string);
890 return (ObjcContainerConvertor::Task){ object, js, ContainerNone };
893 if ([object isKindOfClass:[NSNumber class]]) {
894 if (isNSBoolean(object))
895 return (ObjcContainerConvertor::Task){ object, JSValueMakeBoolean(contextRef, [object boolValue]), ContainerNone };
896 return (ObjcContainerConvertor::Task){ object, JSValueMakeNumber(contextRef, [object doubleValue]), ContainerNone };
899 if ([object isKindOfClass:[NSDate class]]) {
900 JSValueRef argument = JSValueMakeNumber(contextRef, [object timeIntervalSince1970]);
901 JSObjectRef result = JSObjectMakeDate(contextRef, 1, &argument, 0);
902 return (ObjcContainerConvertor::Task){ object, result, ContainerNone };
905 if ([object isKindOfClass:[JSManagedValue class]]) {
906 JSValue *value = [static_cast<JSManagedValue *>(object) value];
908 return (ObjcContainerConvertor::Task) { object, JSValueMakeUndefined(contextRef), ContainerNone };
909 return (ObjcContainerConvertor::Task){ object, value->m_value, ContainerNone };
913 return (ObjcContainerConvertor::Task){ object, valueInternalValue([context wrapperForObjCObject:object]), ContainerNone };
916 JSValueRef objectToValue(JSContext *context, id object)
918 JSGlobalContextRef contextRef = [context JSGlobalContextRef];
920 ObjcContainerConvertor::Task task = objectToValueWithoutCopy(context, object);
921 if (task.type == ContainerNone)
924 JSC::APIEntryShim entryShim(toJS(contextRef));
925 ObjcContainerConvertor convertor(context);
927 ASSERT(!convertor.isWorkListEmpty());
930 ObjcContainerConvertor::Task current = convertor.take();
931 ASSERT(JSValueIsObject(contextRef, current.js));
932 JSObjectRef js = JSValueToObject(contextRef, current.js, 0);
934 if (current.type == ContainerArray) {
935 ASSERT([current.objc isKindOfClass:[NSArray class]]);
936 NSArray *array = (NSArray *)current.objc;
937 NSUInteger count = [array count];
938 for (NSUInteger index = 0; index < count; ++index)
939 JSObjectSetPropertyAtIndex(contextRef, js, index, convertor.convert([array objectAtIndex:index]), 0);
941 ASSERT(current.type == ContainerDictionary);
942 ASSERT([current.objc isKindOfClass:[NSDictionary class]]);
943 NSDictionary *dictionary = (NSDictionary *)current.objc;
944 for (id key in [dictionary keyEnumerator]) {
945 if ([key isKindOfClass:[NSString class]]) {
946 JSStringRef propertyName = JSStringCreateWithCFString((CFStringRef)key);
947 JSObjectSetProperty(contextRef, js, propertyName, convertor.convert([dictionary objectForKey:key]), 0, 0);
948 JSStringRelease(propertyName);
953 } while (!convertor.isWorkListEmpty());
958 JSValueRef valueInternalValue(JSValue * value)
960 return value->m_value;
963 + (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context
965 return [context wrapperForJSObject:value];
973 - (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context
975 if (!value || !context)
982 _context = [context retain];
984 JSValueProtect([_context JSGlobalContextRef], m_value);
988 struct StructTagHandler {
992 typedef HashMap<String, StructTagHandler> StructHandlers;
994 static StructHandlers* createStructHandlerMap()
996 StructHandlers* structHandlers = new StructHandlers();
998 size_t valueWithXinContextLength = strlen("valueWithX:inContext:");
999 size_t toXLength = strlen("toX");
1001 // Step 1: find all valueWith<Foo>:inContext: class methods in JSValue.
1002 forEachMethodInClass(object_getClass([JSValue class]), ^(Method method){
1003 SEL selector = method_getName(method);
1004 const char* name = sel_getName(selector);
1005 size_t nameLength = strlen(name);
1006 // Check for valueWith<Foo>:context:
1007 if (nameLength < valueWithXinContextLength || memcmp(name, "valueWith", 9) || memcmp(name + nameLength - 11, ":inContext:", 11))
1009 // Check for [ id, SEL, <type>, <contextType> ]
1010 if (method_getNumberOfArguments(method) != 4)
1013 // Check 2nd argument type is "@"
1014 char* secondType = method_copyArgumentType(method, 3);
1015 if (strcmp(secondType, "@") != 0) {
1020 // Check result type is also "@"
1021 method_getReturnType(method, idType, 3);
1022 if (strcmp(idType, "@") != 0)
1024 char* type = method_copyArgumentType(method, 2);
1025 structHandlers->add(StringImpl::create(type), (StructTagHandler){ selector, 0 });
1029 // Step 2: find all to<Foo> instance methods in JSValue.
1030 forEachMethodInClass([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 to<Foo>
1035 if (nameLength < toXLength || memcmp(name, "to", 2))
1037 // Check for [ id, SEL ]
1038 if (method_getNumberOfArguments(method) != 2)
1040 // Try to find a matching valueWith<Foo>:context: method.
1041 char* type = method_copyReturnType(method);
1043 StructHandlers::iterator iter = structHandlers->find(type);
1045 if (iter == structHandlers->end())
1047 StructTagHandler& handler = iter->value;
1049 // check that strlen(<foo>) == strlen(<Foo>)
1050 const char* valueWithName = sel_getName(handler.typeToValueSEL);
1051 size_t valueWithLength = strlen(valueWithName);
1052 if (valueWithLength - valueWithXinContextLength != nameLength - toXLength)
1054 // Check that <Foo> == <Foo>
1055 if (memcmp(valueWithName + 9, name + 2, nameLength - toXLength - 1))
1057 handler.valueToTypeSEL = selector;
1060 // Step 3: clean up - remove entries where we found prospective valueWith<Foo>:inContext: conversions, but no matching to<Foo> methods.
1061 typedef HashSet<String> RemoveSet;
1062 RemoveSet removeSet;
1063 for (StructHandlers::iterator iter = structHandlers->begin(); iter != structHandlers->end(); ++iter) {
1064 StructTagHandler& handler = iter->value;
1065 if (!handler.valueToTypeSEL)
1066 removeSet.add(iter->key);
1069 for (RemoveSet::iterator iter = removeSet.begin(); iter != removeSet.end(); ++iter)
1070 structHandlers->remove(*iter);
1072 return structHandlers;
1075 static StructTagHandler* handerForStructTag(const char* encodedType)
1077 static SpinLock handerForStructTagLock = SPINLOCK_INITIALIZER;
1078 SpinLockHolder lockHolder(&handerForStructTagLock);
1080 static StructHandlers* structHandlers = createStructHandlerMap();
1082 StructHandlers::iterator iter = structHandlers->find(encodedType);
1083 if (iter == structHandlers->end())
1085 return &iter->value;
1088 + (SEL)selectorForStructToValue:(const char *)structTag
1090 StructTagHandler* handler = handerForStructTag(structTag);
1091 return handler ? handler->typeToValueSEL : nil;
1094 + (SEL)selectorForValueToStruct:(const char *)structTag
1096 StructTagHandler* handler = handerForStructTag(structTag);
1097 return handler ? handler->valueToTypeSEL : nil;
1102 JSValueUnprotect([_context JSGlobalContextRef], m_value);
1108 - (NSString *)description
1110 if (id wrapped = tryUnwrapObjcObject([_context JSGlobalContextRef], m_value))
1111 return [wrapped description];
1112 return [self toString];
1115 NSInvocation *typeToValueInvocationFor(const char* encodedType)
1117 SEL selector = [JSValue selectorForStructToValue:encodedType];
1121 const char* methodTypes = method_getTypeEncoding(class_getClassMethod([JSValue class], selector));
1122 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
1123 [invocation setSelector:selector];
1127 NSInvocation *valueToTypeInvocationFor(const char* encodedType)
1129 SEL selector = [JSValue selectorForValueToStruct:encodedType];
1133 const char* methodTypes = method_getTypeEncoding(class_getInstanceMethod([JSValue class], selector));
1134 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
1135 [invocation setSelector:selector];