]> git.saurik.com Git - apple/javascriptcore.git/blobdiff - API/JSValue.mm
JavaScriptCore-1218.tar.gz
[apple/javascriptcore.git] / API / JSValue.mm
diff --git a/API/JSValue.mm b/API/JSValue.mm
new file mode 100644 (file)
index 0000000..a380964
--- /dev/null
@@ -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){
+        static_cast<CGFloat>([self[@"x"] toDouble]),
+        static_cast<CGFloat>([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){
+        static_cast<CGFloat>([self[@"width"] toDouble]),
+        static_cast<CGFloat>([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