/* * 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 "DateInstance.h" #import "Error.h" #import "Exception.h" #import "JavaScriptCore.h" #import "JSContextInternal.h" #import "JSVirtualMachineInternal.h" #import "JSValueInternal.h" #import "JSWrapperMap.h" #import "ObjcRuntimeExtras.h" #import "JSCInlines.h" #import "JSCJSValue.h" #import "Strong.h" #import "StrongInlines.h" #import #import #import #import #import #import #import #if ENABLE(REMOTE_INSPECTOR) #import "CallFrame.h" #import "JSGlobalObject.h" #import "JSGlobalObjectInspectorController.h" #endif #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::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)isArray { return JSValueIsArray([_context JSGlobalContextRef], m_value); } - (BOOL)isDate { return JSValueIsDate([_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([self[@"x"] toDouble]), static_cast([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([self[@"width"] toDouble]), static_cast([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 *)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::JSLockHolder locker(toJS(context)); return toJS(object)->inherits(JSC::DateInstance::info()); } inline bool isArray(JSObjectRef object, JSGlobalContextRef context) { JSC::JSLockHolder locker(toJS(context)); return toJS(object)->inherits(JSC::JSArray::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 m_objectMap; Vector m_worklist; Vector> m_jsValues; }; inline id JSContainerConvertor::convert(JSValueRef value) { HashMap::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) { JSC::ExecState* exec = toJS(m_context); m_jsValues.append(JSC::Strong(exec->vm(), toJSForGC(exec, task.js))); 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; } #if ENABLE(REMOTE_INSPECTOR) static void reportExceptionToInspector(JSGlobalContextRef context, JSC::JSValue exceptionValue) { JSC::ExecState* exec = toJS(context); JSC::Exception* exception = JSC::Exception::create(exec->vm(), exceptionValue); exec->vmEntryGlobalObject()->inspectorController().reportAPIException(exec, exception); } #endif 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) / 1000.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); JSC::JSLockHolder locker(toJS(context)); 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; JSC::JSLockHolder locker(toJS(context)); 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::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; } RetainPtr stringCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, jsstring)); JSStringRelease(jsstring); return (NSString *)stringCF.autorelease(); } 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) / 1000.0; 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}); JSC::JSLockHolder locker(toJS(context)); if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) { JSC::JSObject* exceptionObject = JSC::createTypeError(toJS(context), ASCIILiteral("Cannot convert primitive to NSArray")); *exception = toRef(exceptionObject); #if ENABLE(REMOTE_INSPECTOR) reportExceptionToInspector(context, exceptionObject); #endif } 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}); JSC::JSLockHolder locker(toJS(context)); if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) { JSC::JSObject* exceptionObject = JSC::createTypeError(toJS(context), ASCIILiteral("Cannot convert primitive to NSDictionary")); *exception = toRef(exceptionObject); #if ENABLE(REMOTE_INSPECTOR) reportExceptionToInspector(context, exceptionObject); #endif } 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 m_objectMap; Vector m_worklist; Vector> m_jsValues; }; 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) { JSC::ExecState* exec = toJS(m_context.JSGlobalContextRef); m_jsValues.append(JSC::Strong(exec->vm(), toJSForGC(exec, task.js))); 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] * 1000.0); JSObjectRef result = JSObjectMakeDate(contextRef, 1, &argument, 0); return (ObjcContainerConvertor::Task){ object, result, ContainerNone }; } if ([object isKindOfClass:[JSManagedValue class]]) { JSValue *value = [static_cast(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; JSC::JSLockHolder locker(toJS(contextRef)); 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 StructHandlers; static StructHandlers* createStructHandlerMap() { StructHandlers* structHandlers = new StructHandlers(); size_t valueWithXinContextLength = strlen("valueWithX:inContext:"); size_t toXLength = strlen("toX"); // Step 1: find all valueWith: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:context: if (nameLength < valueWithXinContextLength || memcmp(name, "valueWith", 9) || memcmp(name + nameLength - 11, ":inContext:", 11)) return; // Check for [ id, SEL, , ] 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 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 if (nameLength < toXLength || memcmp(name, "to", 2)) return; // Check for [ id, SEL ] if (method_getNumberOfArguments(method) != 2) return; // Try to find a matching valueWith: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() == strlen() const char* valueWithName = sel_getName(handler.typeToValueSEL); size_t valueWithLength = strlen(valueWithName); if (valueWithLength - valueWithXinContextLength != nameLength - toXLength) return; // Check that == if (memcmp(valueWithName + 9, name + 2, nameLength - toXLength - 1)) return; handler.valueToTypeSEL = selector; }); // Step 3: clean up - remove entries where we found prospective valueWith:inContext: conversions, but no matching to methods. typedef HashSet 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 StaticLock handerForStructTagLock; LockHolder 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