diff options
author | Allan Sandfeld Jensen <allan.jensen@digia.com> | 2013-09-13 12:51:20 +0200 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-09-19 20:50:05 +0200 |
commit | d441d6f39bb846989d95bcf5caf387b42414718d (patch) | |
tree | e367e64a75991c554930278175d403c072de6bb8 /Source/JavaScriptCore/API/JSValue.mm | |
parent | 0060b2994c07842f4c59de64b5e3e430525c4b90 (diff) | |
download | qtwebkit-d441d6f39bb846989d95bcf5caf387b42414718d.tar.gz |
Import Qt5x2 branch of QtWebkit for Qt 5.2
Importing a new snapshot of webkit.
Change-Id: I2d01ad12cdc8af8cb015387641120a9d7ea5f10c
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@digia.com>
Diffstat (limited to 'Source/JavaScriptCore/API/JSValue.mm')
-rw-r--r-- | Source/JavaScriptCore/API/JSValue.mm | 1131 |
1 files changed, 1131 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/API/JSValue.mm b/Source/JavaScriptCore/API/JSValue.mm new file mode 100644 index 000000000..e708cc674 --- /dev/null +++ b/Source/JavaScriptCore/API/JSValue.mm @@ -0,0 +1,1131 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#import "APICast.h" +#import "APIShims.h" +#import "DateInstance.h" +#import "Error.h" +#import "JavaScriptCore.h" +#import "JSContextInternal.h" +#import "JSVirtualMachineInternal.h" +#import "JSValueInternal.h" +#import "JSWrapperMap.h" +#import "ObjcRuntimeExtras.h" +#import "Operations.h" +#import "JSCJSValue.h" +#import <wtf/HashMap.h> +#import <wtf/HashSet.h> +#import <wtf/Vector.h> +#import <wtf/TCSpinLock.h> +#import <wtf/text/WTFString.h> +#import <wtf/text/StringHash.h> + +#if JSC_OBJC_API_ENABLED + +NSString * const JSPropertyDescriptorWritableKey = @"writable"; +NSString * const JSPropertyDescriptorEnumerableKey = @"enumerable"; +NSString * const JSPropertyDescriptorConfigurableKey = @"configurable"; +NSString * const JSPropertyDescriptorValueKey = @"value"; +NSString * const JSPropertyDescriptorGetKey = @"get"; +NSString * const JSPropertyDescriptorSetKey = @"set"; + +@implementation JSValue { + JSValueRef m_value; +} + +- (JSValueRef)JSValueRef +{ + return m_value; +} + ++ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:objectToValue(context, value) inContext:context]; +} + ++ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeBoolean([context JSGlobalContextRef], value) inContext:context]; +} + ++ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context]; +} + ++ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context]; +} + ++ (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context]; +} + ++ (JSValue *)valueWithNewObjectInContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSObjectMake([context JSGlobalContextRef], 0, 0) inContext:context]; +} + ++ (JSValue *)valueWithNewArrayInContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSObjectMakeArray([context JSGlobalContextRef], 0, NULL, 0) inContext:context]; +} + ++ (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context +{ + JSStringRef patternString = JSStringCreateWithCFString((CFStringRef)pattern); + JSStringRef flagsString = JSStringCreateWithCFString((CFStringRef)flags); + JSValueRef arguments[2] = { JSValueMakeString([context JSGlobalContextRef], patternString), JSValueMakeString([context JSGlobalContextRef], flagsString) }; + JSStringRelease(patternString); + JSStringRelease(flagsString); + + return [JSValue valueWithJSValueRef:JSObjectMakeRegExp([context JSGlobalContextRef], 2, arguments, 0) inContext:context]; +} + ++ (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context +{ + JSStringRef string = JSStringCreateWithCFString((CFStringRef)message); + JSValueRef argument = JSValueMakeString([context JSGlobalContextRef], string); + JSStringRelease(string); + + return [JSValue valueWithJSValueRef:JSObjectMakeError([context JSGlobalContextRef], 1, &argument, 0) inContext:context]; +} + ++ (JSValue *)valueWithNullInContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeNull([context JSGlobalContextRef]) inContext:context]; +} + ++ (JSValue *)valueWithUndefinedInContext:(JSContext *)context +{ + return [JSValue valueWithJSValueRef:JSValueMakeUndefined([context JSGlobalContextRef]) inContext:context]; +} + +- (id)toObject +{ + return valueToObject(_context, m_value); +} + +- (id)toObjectOfClass:(Class)expectedClass +{ + id result = [self toObject]; + return [result isKindOfClass:expectedClass] ? result : nil; +} + +- (BOOL)toBool +{ + return JSValueToBoolean([_context JSGlobalContextRef], m_value); +} + +- (double)toDouble +{ + JSValueRef exception = 0; + double result = JSValueToNumber([_context JSGlobalContextRef], m_value, &exception); + if (exception) { + [_context notifyException:exception]; + return std::numeric_limits<double>::quiet_NaN(); + } + + return result; +} + +- (int32_t)toInt32 +{ + return JSC::toInt32([self toDouble]); +} + +- (uint32_t)toUInt32 +{ + return JSC::toUInt32([self toDouble]); +} + +- (NSNumber *)toNumber +{ + JSValueRef exception = 0; + id result = valueToNumber([_context JSGlobalContextRef], m_value, &exception); + if (exception) + [_context notifyException:exception]; + return result; +} + +- (NSString *)toString +{ + JSValueRef exception = 0; + id result = valueToString([_context JSGlobalContextRef], m_value, &exception); + if (exception) + [_context notifyException:exception]; + return result; +} + +- (NSDate *)toDate +{ + JSValueRef exception = 0; + id result = valueToDate([_context JSGlobalContextRef], m_value, &exception); + if (exception) + [_context notifyException:exception]; + return result; +} + +- (NSArray *)toArray +{ + JSValueRef exception = 0; + id result = valueToArray([_context JSGlobalContextRef], m_value, &exception); + if (exception) + [_context notifyException:exception]; + return result; +} + +- (NSDictionary *)toDictionary +{ + JSValueRef exception = 0; + id result = valueToDictionary([_context JSGlobalContextRef], m_value, &exception); + if (exception) + [_context notifyException:exception]; + return result; +} + +- (JSValue *)valueForProperty:(NSString *)propertyName +{ + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); + JSValueRef result = JSObjectGetProperty([_context JSGlobalContextRef], object, name, &exception); + JSStringRelease(name); + if (exception) + return [_context valueFromNotifyException:exception]; + + return [JSValue valueWithJSValueRef:result inContext:_context]; +} + +- (void)setValue:(id)value forProperty:(NSString *)propertyName +{ + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) { + [_context notifyException:exception]; + return; + } + + JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); + JSObjectSetProperty([_context JSGlobalContextRef], object, name, objectToValue(_context, value), 0, &exception); + JSStringRelease(name); + if (exception) { + [_context notifyException:exception]; + return; + } +} + +- (BOOL)deleteProperty:(NSString *)propertyName +{ + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context boolFromNotifyException:exception]; + + JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); + BOOL result = JSObjectDeleteProperty([_context JSGlobalContextRef], object, name, &exception); + JSStringRelease(name); + if (exception) + return [_context boolFromNotifyException:exception]; + + return result; +} + +- (BOOL)hasProperty:(NSString *)propertyName +{ + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context boolFromNotifyException:exception]; + + JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName); + BOOL result = JSObjectHasProperty([_context JSGlobalContextRef], object, name); + JSStringRelease(name); + return result; +} + +- (void)defineProperty:(NSString *)property descriptor:(id)descriptor +{ + [[_context globalObject][@"Object"] invokeMethod:@"defineProperty" withArguments:@[ self, property, descriptor ]]; +} + +- (JSValue *)valueAtIndex:(NSUInteger)index +{ + // Properties that are higher than an unsigned value can hold are converted to a double then inserted as a normal property. + // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in get(). + if (index != (unsigned)index) + return [self valueForProperty:[[JSValue valueWithDouble:index inContext:_context] toString]]; + + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSValueRef result = JSObjectGetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + return [JSValue valueWithJSValueRef:result inContext:_context]; +} + +- (void)setValue:(id)value atIndex:(NSUInteger)index +{ + // Properties that are higher than an unsigned value can hold are converted to a double, then inserted as a normal property. + // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in putByIndex(). + if (index != (unsigned)index) + return [self setValue:value forProperty:[[JSValue valueWithDouble:index inContext:_context] toString]]; + + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) { + [_context notifyException:exception]; + return; + } + + JSObjectSetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, objectToValue(_context, value), &exception); + if (exception) { + [_context notifyException:exception]; + return; + } +} + +- (BOOL)isUndefined +{ + return JSValueIsUndefined([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isNull +{ + return JSValueIsNull([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isBoolean +{ + return JSValueIsBoolean([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isNumber +{ + return JSValueIsNumber([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isString +{ + return JSValueIsString([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isObject +{ + return JSValueIsObject([_context JSGlobalContextRef], m_value); +} + +- (BOOL)isEqualToObject:(id)value +{ + return JSValueIsStrictEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value)); +} + +- (BOOL)isEqualWithTypeCoercionToObject:(id)value +{ + JSValueRef exception = 0; + BOOL result = JSValueIsEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value), &exception); + if (exception) + return [_context boolFromNotifyException:exception]; + + return result; +} + +- (BOOL)isInstanceOf:(id)value +{ + JSValueRef exception = 0; + JSObjectRef constructor = JSValueToObject([_context JSGlobalContextRef], objectToValue(_context, value), &exception); + if (exception) + return [_context boolFromNotifyException:exception]; + + BOOL result = JSValueIsInstanceOfConstructor([_context JSGlobalContextRef], m_value, constructor, &exception); + if (exception) + return [_context boolFromNotifyException:exception]; + + return result; +} + +- (JSValue *)callWithArguments:(NSArray *)argumentArray +{ + NSUInteger argumentCount = [argumentArray count]; + JSValueRef arguments[argumentCount]; + for (unsigned i = 0; i < argumentCount; ++i) + arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]); + + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, 0, argumentCount, arguments, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + return [JSValue valueWithJSValueRef:result inContext:_context]; +} + +- (JSValue *)constructWithArguments:(NSArray *)argumentArray +{ + NSUInteger argumentCount = [argumentArray count]; + JSValueRef arguments[argumentCount]; + for (unsigned i = 0; i < argumentCount; ++i) + arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]); + + JSValueRef exception = 0; + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSObjectRef result = JSObjectCallAsConstructor([_context JSGlobalContextRef], object, argumentCount, arguments, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + return [JSValue valueWithJSValueRef:result inContext:_context]; +} + +- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments +{ + NSUInteger argumentCount = [arguments count]; + JSValueRef argumentArray[argumentCount]; + for (unsigned i = 0; i < argumentCount; ++i) + argumentArray[i] = objectToValue(_context, [arguments objectAtIndex:i]); + + JSValueRef exception = 0; + JSObjectRef thisObject = JSValueToObject([_context JSGlobalContextRef], m_value, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSStringRef name = JSStringCreateWithCFString((CFStringRef)method); + JSValueRef function = JSObjectGetProperty([_context JSGlobalContextRef], thisObject, name, &exception); + JSStringRelease(name); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], function, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, thisObject, argumentCount, argumentArray, &exception); + if (exception) + return [_context valueFromNotifyException:exception]; + + return [JSValue valueWithJSValueRef:result inContext:_context]; +} + +@end + +@implementation JSValue(StructSupport) + +- (CGPoint)toPoint +{ + return (CGPoint){ + [self[@"x"] toDouble], + [self[@"y"] toDouble] + }; +} + +- (NSRange)toRange +{ + return (NSRange){ + [[self[@"location"] toNumber] unsignedIntegerValue], + [[self[@"length"] toNumber] unsignedIntegerValue] + }; +} + +- (CGRect)toRect +{ + return (CGRect){ + [self toPoint], + [self toSize] + }; +} + +- (CGSize)toSize +{ + return (CGSize){ + [self[@"width"] toDouble], + [self[@"height"] toDouble] + }; +} + ++ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context +{ + return [JSValue valueWithObject:@{ + @"x":@(point.x), + @"y":@(point.y) + } inContext:context]; +} + ++ (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context +{ + return [JSValue valueWithObject:@{ + @"location":@(range.location), + @"length":@(range.length) + } inContext:context]; +} + ++ (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context +{ + return [JSValue valueWithObject:@{ + @"x":@(rect.origin.x), + @"y":@(rect.origin.y), + @"width":@(rect.size.width), + @"height":@(rect.size.height) + } inContext:context]; +} + ++ (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context +{ + return [JSValue valueWithObject:@{ + @"width":@(size.width), + @"height":@(size.height) + } inContext:context]; +} + +@end + +@implementation JSValue(SubscriptSupport) + +- (JSValue *)objectForKeyedSubscript:(id)key +{ + if (![key isKindOfClass:[NSString class]]) { + key = [[JSValue valueWithObject:key inContext:_context] toString]; + if (!key) + return [JSValue valueWithUndefinedInContext:_context]; + } + + return [self valueForProperty:(NSString *)key]; +} + +- (JSValue *)objectAtIndexedSubscript:(NSUInteger)index +{ + return [self valueAtIndex:index]; +} + +- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key +{ + if (![key isKindOfClass:[NSString class]]) { + key = [[JSValue valueWithObject:key inContext:_context] toString]; + if (!key) + return; + } + + [self setValue:object forProperty:(NSString *)key]; +} + +- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index +{ + [self setValue:object atIndex:index]; +} + +@end + +inline bool isDate(JSObjectRef object, JSGlobalContextRef context) +{ + JSC::APIEntryShim entryShim(toJS(context)); + return toJS(object)->inherits(&JSC::DateInstance::s_info); +} + +inline bool isArray(JSObjectRef object, JSGlobalContextRef context) +{ + JSC::APIEntryShim entryShim(toJS(context)); + return toJS(object)->inherits(&JSC::JSArray::s_info); +} + +@implementation JSValue(Internal) + +enum ConversionType { + ContainerNone, + ContainerArray, + ContainerDictionary +}; + +class JSContainerConvertor { +public: + struct Task { + JSValueRef js; + id objc; + ConversionType type; + }; + + JSContainerConvertor(JSGlobalContextRef context) + : m_context(context) + { + } + + id convert(JSValueRef property); + void add(Task); + Task take(); + bool isWorkListEmpty() const { return !m_worklist.size(); } + +private: + JSGlobalContextRef m_context; + HashMap<JSValueRef, id> m_objectMap; + Vector<Task> m_worklist; +}; + +inline id JSContainerConvertor::convert(JSValueRef value) +{ + HashMap<JSValueRef, id>::iterator iter = m_objectMap.find(value); + if (iter != m_objectMap.end()) + return iter->value; + + Task result = valueToObjectWithoutCopy(m_context, value); + if (result.js) + add(result); + return result.objc; +} + +void JSContainerConvertor::add(Task task) +{ + m_objectMap.add(task.js, task.objc); + if (task.type != ContainerNone) + m_worklist.append(task); +} + +JSContainerConvertor::Task JSContainerConvertor::take() +{ + ASSERT(!isWorkListEmpty()); + Task last = m_worklist.last(); + m_worklist.removeLast(); + return last; +} + +static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef context, JSValueRef value) +{ + if (!JSValueIsObject(context, value)) { + id primitive; + if (JSValueIsBoolean(context, value)) + primitive = JSValueToBoolean(context, value) ? @YES : @NO; + else if (JSValueIsNumber(context, value)) { + // Normalize the number, so it will unique correctly in the hash map - + // it's nicer not to leak this internal implementation detail! + value = JSValueMakeNumber(context, JSValueToNumber(context, value, 0)); + primitive = [NSNumber numberWithDouble:JSValueToNumber(context, value, 0)]; + } else if (JSValueIsString(context, value)) { + // Would be nice to unique strings, too. + JSStringRef jsstring = JSValueToStringCopy(context, value, 0); + NSString * stringNS = (NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring); + JSStringRelease(jsstring); + primitive = [stringNS autorelease]; + } else if (JSValueIsNull(context, value)) + primitive = [NSNull null]; + else { + ASSERT(JSValueIsUndefined(context, value)); + primitive = nil; + } + return (JSContainerConvertor::Task){ value, primitive, ContainerNone }; + } + + JSObjectRef object = JSValueToObject(context, value, 0); + + if (id wrapped = tryUnwrapObjcObject(context, object)) + return (JSContainerConvertor::Task){ object, wrapped, ContainerNone }; + + if (isDate(object, context)) + return (JSContainerConvertor::Task){ object, [NSDate dateWithTimeIntervalSince1970:JSValueToNumber(context, object, 0)], ContainerNone }; + + if (isArray(object, context)) + return (JSContainerConvertor::Task){ object, [NSMutableArray array], ContainerArray }; + + return (JSContainerConvertor::Task){ object, [NSMutableDictionary dictionary], ContainerDictionary }; +} + +static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task) +{ + ASSERT(task.type != ContainerNone); + JSContainerConvertor convertor(context); + convertor.add(task); + ASSERT(!convertor.isWorkListEmpty()); + + do { + JSContainerConvertor::Task current = convertor.take(); + ASSERT(JSValueIsObject(context, current.js)); + JSObjectRef js = JSValueToObject(context, current.js, 0); + + if (current.type == ContainerArray) { + ASSERT([current.objc isKindOfClass:[NSMutableArray class]]); + NSMutableArray *array = (NSMutableArray *)current.objc; + + JSStringRef lengthString = JSStringCreateWithUTF8CString("length"); + unsigned length = JSC::toUInt32(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString, 0), 0)); + JSStringRelease(lengthString); + + for (unsigned i = 0; i < length; ++i) { + id objc = convertor.convert(JSObjectGetPropertyAtIndex(context, js, i, 0)); + [array addObject:objc ? objc : [NSNull null]]; + } + } else { + ASSERT([current.objc isKindOfClass:[NSMutableDictionary class]]); + NSMutableDictionary *dictionary = (NSMutableDictionary *)current.objc; + + JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js); + size_t length = JSPropertyNameArrayGetCount(propertyNameArray); + + for (size_t i = 0; i < length; ++i) { + JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNameArray, i); + if (id objc = convertor.convert(JSObjectGetProperty(context, js, propertyName, 0))) + dictionary[[(NSString *)JSStringCopyCFString(kCFAllocatorDefault, propertyName) autorelease]] = objc; + } + + JSPropertyNameArrayRelease(propertyNameArray); + } + + } while (!convertor.isWorkListEmpty()); + + return task.objc; +} + +id valueToObject(JSContext *context, JSValueRef value) +{ + JSContainerConvertor::Task result = valueToObjectWithoutCopy([context JSGlobalContextRef], value); + if (result.type == ContainerNone) + return result.objc; + return containerValueToObject([context JSGlobalContextRef], result); +} + +id valueToNumber(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) +{ + ASSERT(!*exception); + if (id wrapped = tryUnwrapObjcObject(context, value)) { + if ([wrapped isKindOfClass:[NSNumber class]]) + return wrapped; + } + + if (JSValueIsBoolean(context, value)) + return JSValueToBoolean(context, value) ? @YES : @NO; + + double result = JSValueToNumber(context, value, exception); + return [NSNumber numberWithDouble:*exception ? std::numeric_limits<double>::quiet_NaN() : result]; +} + +id valueToString(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) +{ + ASSERT(!*exception); + if (id wrapped = tryUnwrapObjcObject(context, value)) { + if ([wrapped isKindOfClass:[NSString class]]) + return wrapped; + } + + JSStringRef jsstring = JSValueToStringCopy(context, value, exception); + if (*exception) { + ASSERT(!jsstring); + return nil; + } + + NSString *stringNS = [(NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring) autorelease]; + JSStringRelease(jsstring); + return stringNS; +} + +id valueToDate(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) +{ + ASSERT(!*exception); + if (id wrapped = tryUnwrapObjcObject(context, value)) { + if ([wrapped isKindOfClass:[NSDate class]]) + return wrapped; + } + + double result = JSValueToNumber(context, value, exception); + return *exception ? nil : [NSDate dateWithTimeIntervalSince1970:result]; +} + +id valueToArray(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) +{ + ASSERT(!*exception); + if (id wrapped = tryUnwrapObjcObject(context, value)) { + if ([wrapped isKindOfClass:[NSArray class]]) + return wrapped; + } + + if (JSValueIsObject(context, value)) + return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableArray array], ContainerArray}); + + if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) + *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSArray")); + return nil; +} + +id valueToDictionary(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception) +{ + ASSERT(!*exception); + if (id wrapped = tryUnwrapObjcObject(context, value)) { + if ([wrapped isKindOfClass:[NSDictionary class]]) + return wrapped; + } + + if (JSValueIsObject(context, value)) + return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableDictionary dictionary], ContainerDictionary}); + + if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) + *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSDictionary")); + return nil; +} + +class ObjcContainerConvertor { +public: + struct Task { + id objc; + JSValueRef js; + ConversionType type; + }; + + ObjcContainerConvertor(JSContext *context) + : m_context(context) + { + } + + JSValueRef convert(id object); + void add(Task); + Task take(); + bool isWorkListEmpty() const { return !m_worklist.size(); } + +private: + JSContext *m_context; + HashMap<id, JSValueRef> m_objectMap; + Vector<Task> m_worklist; +}; + +JSValueRef ObjcContainerConvertor::convert(id object) +{ + ASSERT(object); + + auto it = m_objectMap.find(object); + if (it != m_objectMap.end()) + return it->value; + + ObjcContainerConvertor::Task task = objectToValueWithoutCopy(m_context, object); + add(task); + return task.js; +} + +void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task) +{ + m_objectMap.add(task.objc, task.js); + if (task.type != ContainerNone) + m_worklist.append(task); +} + +ObjcContainerConvertor::Task ObjcContainerConvertor::take() +{ + ASSERT(!isWorkListEmpty()); + Task last = m_worklist.last(); + m_worklist.removeLast(); + return last; +} + +inline bool isNSBoolean(id object) +{ + ASSERT([@YES class] == [@NO class]); + ASSERT([@YES class] != [NSNumber class]); + ASSERT([[@YES class] isSubclassOfClass:[NSNumber class]]); + return [object isKindOfClass:[@YES class]]; +} + +static ObjcContainerConvertor::Task objectToValueWithoutCopy(JSContext *context, id object) +{ + JSGlobalContextRef contextRef = [context JSGlobalContextRef]; + + if (!object) + return (ObjcContainerConvertor::Task){ object, JSValueMakeUndefined(contextRef), ContainerNone }; + + if (!class_conformsToProtocol(object_getClass(object), getJSExportProtocol())) { + if ([object isKindOfClass:[NSArray class]]) + return (ObjcContainerConvertor::Task){ object, JSObjectMakeArray(contextRef, 0, NULL, 0), ContainerArray }; + + if ([object isKindOfClass:[NSDictionary class]]) + return (ObjcContainerConvertor::Task){ object, JSObjectMake(contextRef, 0, 0), ContainerDictionary }; + + if ([object isKindOfClass:[NSNull class]]) + return (ObjcContainerConvertor::Task){ object, JSValueMakeNull(contextRef), ContainerNone }; + + if ([object isKindOfClass:[JSValue class]]) + return (ObjcContainerConvertor::Task){ object, ((JSValue *)object)->m_value, ContainerNone }; + + if ([object isKindOfClass:[NSString class]]) { + JSStringRef string = JSStringCreateWithCFString((CFStringRef)object); + JSValueRef js = JSValueMakeString(contextRef, string); + JSStringRelease(string); + return (ObjcContainerConvertor::Task){ object, js, ContainerNone }; + } + + if ([object isKindOfClass:[NSNumber class]]) { + if (isNSBoolean(object)) + return (ObjcContainerConvertor::Task){ object, JSValueMakeBoolean(contextRef, [object boolValue]), ContainerNone }; + return (ObjcContainerConvertor::Task){ object, JSValueMakeNumber(contextRef, [object doubleValue]), ContainerNone }; + } + + if ([object isKindOfClass:[NSDate class]]) { + JSValueRef argument = JSValueMakeNumber(contextRef, [object timeIntervalSince1970]); + JSObjectRef result = JSObjectMakeDate(contextRef, 1, &argument, 0); + return (ObjcContainerConvertor::Task){ object, result, ContainerNone }; + } + + if ([object isKindOfClass:[JSManagedValue class]]) { + JSValue *value = [static_cast<JSManagedValue *>(object) value]; + if (!value) + return (ObjcContainerConvertor::Task) { object, JSValueMakeUndefined(contextRef), ContainerNone }; + return (ObjcContainerConvertor::Task){ object, value->m_value, ContainerNone }; + } + } + + return (ObjcContainerConvertor::Task){ object, valueInternalValue([context wrapperForObjCObject:object]), ContainerNone }; +} + +JSValueRef objectToValue(JSContext *context, id object) +{ + JSGlobalContextRef contextRef = [context JSGlobalContextRef]; + + ObjcContainerConvertor::Task task = objectToValueWithoutCopy(context, object); + if (task.type == ContainerNone) + return task.js; + + ObjcContainerConvertor convertor(context); + convertor.add(task); + ASSERT(!convertor.isWorkListEmpty()); + + do { + ObjcContainerConvertor::Task current = convertor.take(); + ASSERT(JSValueIsObject(contextRef, current.js)); + JSObjectRef js = JSValueToObject(contextRef, current.js, 0); + + if (current.type == ContainerArray) { + ASSERT([current.objc isKindOfClass:[NSArray class]]); + NSArray *array = (NSArray *)current.objc; + NSUInteger count = [array count]; + for (NSUInteger index = 0; index < count; ++index) + JSObjectSetPropertyAtIndex(contextRef, js, index, convertor.convert([array objectAtIndex:index]), 0); + } else { + ASSERT(current.type == ContainerDictionary); + ASSERT([current.objc isKindOfClass:[NSDictionary class]]); + NSDictionary *dictionary = (NSDictionary *)current.objc; + for (id key in [dictionary keyEnumerator]) { + if ([key isKindOfClass:[NSString class]]) { + JSStringRef propertyName = JSStringCreateWithCFString((CFStringRef)key); + JSObjectSetProperty(contextRef, js, propertyName, convertor.convert([dictionary objectForKey:key]), 0, 0); + JSStringRelease(propertyName); + } + } + } + + } while (!convertor.isWorkListEmpty()); + + return task.js; +} + +JSValueRef valueInternalValue(JSValue * value) +{ + return value->m_value; +} + ++ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context +{ + return [context wrapperForJSObject:value]; +} + +- (JSValue *)init +{ + return nil; +} + +- (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context +{ + if (!value || !context) + return nil; + + self = [super init]; + if (!self) + return nil; + + _context = [context retain]; + m_value = value; + JSValueProtect([_context JSGlobalContextRef], m_value); + return self; +} + +struct StructTagHandler { + SEL typeToValueSEL; + SEL valueToTypeSEL; +}; +typedef HashMap<String, StructTagHandler> StructHandlers; + +static StructHandlers* createStructHandlerMap() +{ + StructHandlers* structHandlers = new StructHandlers(); + + size_t valueWithXinContextLength = strlen("valueWithX:inContext:"); + size_t toXLength = strlen("toX"); + + // Step 1: find all valueWith<Foo>:inContext: class methods in JSValue. + forEachMethodInClass(object_getClass([JSValue class]), ^(Method method){ + SEL selector = method_getName(method); + const char* name = sel_getName(selector); + size_t nameLength = strlen(name); + // Check for valueWith<Foo>:context: + if (nameLength < valueWithXinContextLength || memcmp(name, "valueWith", 9) || memcmp(name + nameLength - 11, ":inContext:", 11)) + return; + // Check for [ id, SEL, <type>, <contextType> ] + if (method_getNumberOfArguments(method) != 4) + return; + char idType[3]; + // Check 2nd argument type is "@" + char* secondType = method_copyArgumentType(method, 3); + if (strcmp(secondType, "@") != 0) { + free(secondType); + return; + } + free(secondType); + // Check result type is also "@" + method_getReturnType(method, idType, 3); + if (strcmp(idType, "@") != 0) + return; + char* type = method_copyArgumentType(method, 2); + structHandlers->add(StringImpl::create(type), (StructTagHandler){ selector, 0 }); + free(type); + }); + + // Step 2: find all to<Foo> instance methods in JSValue. + forEachMethodInClass([JSValue class], ^(Method method){ + SEL selector = method_getName(method); + const char* name = sel_getName(selector); + size_t nameLength = strlen(name); + // Check for to<Foo> + if (nameLength < toXLength || memcmp(name, "to", 2)) + return; + // Check for [ id, SEL ] + if (method_getNumberOfArguments(method) != 2) + return; + // Try to find a matching valueWith<Foo>:context: method. + char* type = method_copyReturnType(method); + + StructHandlers::iterator iter = structHandlers->find(type); + free(type); + if (iter == structHandlers->end()) + return; + StructTagHandler& handler = iter->value; + + // check that strlen(<foo>) == strlen(<Foo>) + const char* valueWithName = sel_getName(handler.typeToValueSEL); + size_t valueWithLength = strlen(valueWithName); + if (valueWithLength - valueWithXinContextLength != nameLength - toXLength) + return; + // Check that <Foo> == <Foo> + if (memcmp(valueWithName + 9, name + 2, nameLength - toXLength - 1)) + return; + handler.valueToTypeSEL = selector; + }); + + // Step 3: clean up - remove entries where we found prospective valueWith<Foo>:inContext: conversions, but no matching to<Foo> methods. + typedef HashSet<String> RemoveSet; + RemoveSet removeSet; + for (StructHandlers::iterator iter = structHandlers->begin(); iter != structHandlers->end(); ++iter) { + StructTagHandler& handler = iter->value; + if (!handler.valueToTypeSEL) + removeSet.add(iter->key); + } + + for (RemoveSet::iterator iter = removeSet.begin(); iter != removeSet.end(); ++iter) + structHandlers->remove(*iter); + + return structHandlers; +} + +static StructTagHandler* handerForStructTag(const char* encodedType) +{ + static SpinLock handerForStructTagLock = SPINLOCK_INITIALIZER; + SpinLockHolder lockHolder(&handerForStructTagLock); + + static StructHandlers* structHandlers = createStructHandlerMap(); + + StructHandlers::iterator iter = structHandlers->find(encodedType); + if (iter == structHandlers->end()) + return 0; + return &iter->value; +} + ++ (SEL)selectorForStructToValue:(const char *)structTag +{ + StructTagHandler* handler = handerForStructTag(structTag); + return handler ? handler->typeToValueSEL : nil; +} + ++ (SEL)selectorForValueToStruct:(const char *)structTag +{ + StructTagHandler* handler = handerForStructTag(structTag); + return handler ? handler->valueToTypeSEL : nil; +} + +- (void)dealloc +{ + JSValueUnprotect([_context JSGlobalContextRef], m_value); + [_context release]; + _context = nil; + [super dealloc]; +} + +- (NSString *)description +{ + if (id wrapped = tryUnwrapObjcObject([_context JSGlobalContextRef], m_value)) + return [wrapped description]; + return [self toString]; +} + +NSInvocation *typeToValueInvocationFor(const char* encodedType) +{ + SEL selector = [JSValue selectorForStructToValue:encodedType]; + if (!selector) + return 0; + + const char* methodTypes = method_getTypeEncoding(class_getClassMethod([JSValue class], selector)); + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]]; + [invocation setSelector:selector]; + return invocation; +} + +NSInvocation *valueToTypeInvocationFor(const char* encodedType) +{ + SEL selector = [JSValue selectorForValueToStruct:encodedType]; + if (!selector) + return 0; + + const char* methodTypes = method_getTypeEncoding(class_getInstanceMethod([JSValue class], selector)); + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]]; + [invocation setSelector:selector]; + return invocation; +} + +@end + +#endif |