]> git.saurik.com Git - apple/javascriptcore.git/blame_incremental - API/JSValue.mm
JavaScriptCore-7601.1.46.3.tar.gz
[apple/javascriptcore.git] / API / JSValue.mm
... / ...
CommitLineData
1/*
2 * Copyright (C) 2013 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#import "APICast.h"
29#import "DateInstance.h"
30#import "Error.h"
31#import "Exception.h"
32#import "JavaScriptCore.h"
33#import "JSContextInternal.h"
34#import "JSVirtualMachineInternal.h"
35#import "JSValueInternal.h"
36#import "JSWrapperMap.h"
37#import "ObjcRuntimeExtras.h"
38#import "JSCInlines.h"
39#import "JSCJSValue.h"
40#import "Strong.h"
41#import "StrongInlines.h"
42#import <wtf/HashMap.h>
43#import <wtf/HashSet.h>
44#import <wtf/ObjcRuntimeExtras.h>
45#import <wtf/SpinLock.h>
46#import <wtf/Vector.h>
47#import <wtf/text/WTFString.h>
48#import <wtf/text/StringHash.h>
49
50#if ENABLE(REMOTE_INSPECTOR)
51#import "CallFrame.h"
52#import "JSGlobalObject.h"
53#import "JSGlobalObjectInspectorController.h"
54#endif
55
56#if JSC_OBJC_API_ENABLED
57
58NSString * const JSPropertyDescriptorWritableKey = @"writable";
59NSString * const JSPropertyDescriptorEnumerableKey = @"enumerable";
60NSString * const JSPropertyDescriptorConfigurableKey = @"configurable";
61NSString * const JSPropertyDescriptorValueKey = @"value";
62NSString * const JSPropertyDescriptorGetKey = @"get";
63NSString * const JSPropertyDescriptorSetKey = @"set";
64
65@implementation JSValue {
66 JSValueRef m_value;
67}
68
69- (JSValueRef)JSValueRef
70{
71 return m_value;
72}
73
74+ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context
75{
76 return [JSValue valueWithJSValueRef:objectToValue(context, value) inContext:context];
77}
78
79+ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context
80{
81 return [JSValue valueWithJSValueRef:JSValueMakeBoolean([context JSGlobalContextRef], value) inContext:context];
82}
83
84+ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context
85{
86 return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
87}
88
89+ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context
90{
91 return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
92}
93
94+ (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context
95{
96 return [JSValue valueWithJSValueRef:JSValueMakeNumber([context JSGlobalContextRef], value) inContext:context];
97}
98
99+ (JSValue *)valueWithNewObjectInContext:(JSContext *)context
100{
101 return [JSValue valueWithJSValueRef:JSObjectMake([context JSGlobalContextRef], 0, 0) inContext:context];
102}
103
104+ (JSValue *)valueWithNewArrayInContext:(JSContext *)context
105{
106 return [JSValue valueWithJSValueRef:JSObjectMakeArray([context JSGlobalContextRef], 0, NULL, 0) inContext:context];
107}
108
109+ (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context
110{
111 JSStringRef patternString = JSStringCreateWithCFString((CFStringRef)pattern);
112 JSStringRef flagsString = JSStringCreateWithCFString((CFStringRef)flags);
113 JSValueRef arguments[2] = { JSValueMakeString([context JSGlobalContextRef], patternString), JSValueMakeString([context JSGlobalContextRef], flagsString) };
114 JSStringRelease(patternString);
115 JSStringRelease(flagsString);
116
117 return [JSValue valueWithJSValueRef:JSObjectMakeRegExp([context JSGlobalContextRef], 2, arguments, 0) inContext:context];
118}
119
120+ (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context
121{
122 JSStringRef string = JSStringCreateWithCFString((CFStringRef)message);
123 JSValueRef argument = JSValueMakeString([context JSGlobalContextRef], string);
124 JSStringRelease(string);
125
126 return [JSValue valueWithJSValueRef:JSObjectMakeError([context JSGlobalContextRef], 1, &argument, 0) inContext:context];
127}
128
129+ (JSValue *)valueWithNullInContext:(JSContext *)context
130{
131 return [JSValue valueWithJSValueRef:JSValueMakeNull([context JSGlobalContextRef]) inContext:context];
132}
133
134+ (JSValue *)valueWithUndefinedInContext:(JSContext *)context
135{
136 return [JSValue valueWithJSValueRef:JSValueMakeUndefined([context JSGlobalContextRef]) inContext:context];
137}
138
139- (id)toObject
140{
141 return valueToObject(_context, m_value);
142}
143
144- (id)toObjectOfClass:(Class)expectedClass
145{
146 id result = [self toObject];
147 return [result isKindOfClass:expectedClass] ? result : nil;
148}
149
150- (BOOL)toBool
151{
152 return JSValueToBoolean([_context JSGlobalContextRef], m_value);
153}
154
155- (double)toDouble
156{
157 JSValueRef exception = 0;
158 double result = JSValueToNumber([_context JSGlobalContextRef], m_value, &exception);
159 if (exception) {
160 [_context notifyException:exception];
161 return std::numeric_limits<double>::quiet_NaN();
162 }
163
164 return result;
165}
166
167- (int32_t)toInt32
168{
169 return JSC::toInt32([self toDouble]);
170}
171
172- (uint32_t)toUInt32
173{
174 return JSC::toUInt32([self toDouble]);
175}
176
177- (NSNumber *)toNumber
178{
179 JSValueRef exception = 0;
180 id result = valueToNumber([_context JSGlobalContextRef], m_value, &exception);
181 if (exception)
182 [_context notifyException:exception];
183 return result;
184}
185
186- (NSString *)toString
187{
188 JSValueRef exception = 0;
189 id result = valueToString([_context JSGlobalContextRef], m_value, &exception);
190 if (exception)
191 [_context notifyException:exception];
192 return result;
193}
194
195- (NSDate *)toDate
196{
197 JSValueRef exception = 0;
198 id result = valueToDate([_context JSGlobalContextRef], m_value, &exception);
199 if (exception)
200 [_context notifyException:exception];
201 return result;
202}
203
204- (NSArray *)toArray
205{
206 JSValueRef exception = 0;
207 id result = valueToArray([_context JSGlobalContextRef], m_value, &exception);
208 if (exception)
209 [_context notifyException:exception];
210 return result;
211}
212
213- (NSDictionary *)toDictionary
214{
215 JSValueRef exception = 0;
216 id result = valueToDictionary([_context JSGlobalContextRef], m_value, &exception);
217 if (exception)
218 [_context notifyException:exception];
219 return result;
220}
221
222- (JSValue *)valueForProperty:(NSString *)propertyName
223{
224 JSValueRef exception = 0;
225 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
226 if (exception)
227 return [_context valueFromNotifyException:exception];
228
229 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
230 JSValueRef result = JSObjectGetProperty([_context JSGlobalContextRef], object, name, &exception);
231 JSStringRelease(name);
232 if (exception)
233 return [_context valueFromNotifyException:exception];
234
235 return [JSValue valueWithJSValueRef:result inContext:_context];
236}
237
238- (void)setValue:(id)value forProperty:(NSString *)propertyName
239{
240 JSValueRef exception = 0;
241 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
242 if (exception) {
243 [_context notifyException:exception];
244 return;
245 }
246
247 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
248 JSObjectSetProperty([_context JSGlobalContextRef], object, name, objectToValue(_context, value), 0, &exception);
249 JSStringRelease(name);
250 if (exception) {
251 [_context notifyException:exception];
252 return;
253 }
254}
255
256- (BOOL)deleteProperty:(NSString *)propertyName
257{
258 JSValueRef exception = 0;
259 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
260 if (exception)
261 return [_context boolFromNotifyException:exception];
262
263 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
264 BOOL result = JSObjectDeleteProperty([_context JSGlobalContextRef], object, name, &exception);
265 JSStringRelease(name);
266 if (exception)
267 return [_context boolFromNotifyException:exception];
268
269 return result;
270}
271
272- (BOOL)hasProperty:(NSString *)propertyName
273{
274 JSValueRef exception = 0;
275 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
276 if (exception)
277 return [_context boolFromNotifyException:exception];
278
279 JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
280 BOOL result = JSObjectHasProperty([_context JSGlobalContextRef], object, name);
281 JSStringRelease(name);
282 return result;
283}
284
285- (void)defineProperty:(NSString *)property descriptor:(id)descriptor
286{
287 [[_context globalObject][@"Object"] invokeMethod:@"defineProperty" withArguments:@[ self, property, descriptor ]];
288}
289
290- (JSValue *)valueAtIndex:(NSUInteger)index
291{
292 // Properties that are higher than an unsigned value can hold are converted to a double then inserted as a normal property.
293 // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in get().
294 if (index != (unsigned)index)
295 return [self valueForProperty:[[JSValue valueWithDouble:index inContext:_context] toString]];
296
297 JSValueRef exception = 0;
298 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
299 if (exception)
300 return [_context valueFromNotifyException:exception];
301
302 JSValueRef result = JSObjectGetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, &exception);
303 if (exception)
304 return [_context valueFromNotifyException:exception];
305
306 return [JSValue valueWithJSValueRef:result inContext:_context];
307}
308
309- (void)setValue:(id)value atIndex:(NSUInteger)index
310{
311 // Properties that are higher than an unsigned value can hold are converted to a double, then inserted as a normal property.
312 // Indices that are bigger than the max allowed index size (UINT_MAX - 1) will be handled internally in putByIndex().
313 if (index != (unsigned)index)
314 return [self setValue:value forProperty:[[JSValue valueWithDouble:index inContext:_context] toString]];
315
316 JSValueRef exception = 0;
317 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
318 if (exception) {
319 [_context notifyException:exception];
320 return;
321 }
322
323 JSObjectSetPropertyAtIndex([_context JSGlobalContextRef], object, (unsigned)index, objectToValue(_context, value), &exception);
324 if (exception) {
325 [_context notifyException:exception];
326 return;
327 }
328}
329
330- (BOOL)isUndefined
331{
332 return JSValueIsUndefined([_context JSGlobalContextRef], m_value);
333}
334
335- (BOOL)isNull
336{
337 return JSValueIsNull([_context JSGlobalContextRef], m_value);
338}
339
340- (BOOL)isBoolean
341{
342 return JSValueIsBoolean([_context JSGlobalContextRef], m_value);
343}
344
345- (BOOL)isNumber
346{
347 return JSValueIsNumber([_context JSGlobalContextRef], m_value);
348}
349
350- (BOOL)isString
351{
352 return JSValueIsString([_context JSGlobalContextRef], m_value);
353}
354
355- (BOOL)isObject
356{
357 return JSValueIsObject([_context JSGlobalContextRef], m_value);
358}
359
360- (BOOL)isArray
361{
362 return JSValueIsArray([_context JSGlobalContextRef], m_value);
363}
364
365- (BOOL)isDate
366{
367 return JSValueIsDate([_context JSGlobalContextRef], m_value);
368}
369
370- (BOOL)isEqualToObject:(id)value
371{
372 return JSValueIsStrictEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value));
373}
374
375- (BOOL)isEqualWithTypeCoercionToObject:(id)value
376{
377 JSValueRef exception = 0;
378 BOOL result = JSValueIsEqual([_context JSGlobalContextRef], m_value, objectToValue(_context, value), &exception);
379 if (exception)
380 return [_context boolFromNotifyException:exception];
381
382 return result;
383}
384
385- (BOOL)isInstanceOf:(id)value
386{
387 JSValueRef exception = 0;
388 JSObjectRef constructor = JSValueToObject([_context JSGlobalContextRef], objectToValue(_context, value), &exception);
389 if (exception)
390 return [_context boolFromNotifyException:exception];
391
392 BOOL result = JSValueIsInstanceOfConstructor([_context JSGlobalContextRef], m_value, constructor, &exception);
393 if (exception)
394 return [_context boolFromNotifyException:exception];
395
396 return result;
397}
398
399- (JSValue *)callWithArguments:(NSArray *)argumentArray
400{
401 NSUInteger argumentCount = [argumentArray count];
402 JSValueRef arguments[argumentCount];
403 for (unsigned i = 0; i < argumentCount; ++i)
404 arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]);
405
406 JSValueRef exception = 0;
407 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
408 if (exception)
409 return [_context valueFromNotifyException:exception];
410
411 JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, 0, argumentCount, arguments, &exception);
412 if (exception)
413 return [_context valueFromNotifyException:exception];
414
415 return [JSValue valueWithJSValueRef:result inContext:_context];
416}
417
418- (JSValue *)constructWithArguments:(NSArray *)argumentArray
419{
420 NSUInteger argumentCount = [argumentArray count];
421 JSValueRef arguments[argumentCount];
422 for (unsigned i = 0; i < argumentCount; ++i)
423 arguments[i] = objectToValue(_context, [argumentArray objectAtIndex:i]);
424
425 JSValueRef exception = 0;
426 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
427 if (exception)
428 return [_context valueFromNotifyException:exception];
429
430 JSObjectRef result = JSObjectCallAsConstructor([_context JSGlobalContextRef], object, argumentCount, arguments, &exception);
431 if (exception)
432 return [_context valueFromNotifyException:exception];
433
434 return [JSValue valueWithJSValueRef:result inContext:_context];
435}
436
437- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments
438{
439 NSUInteger argumentCount = [arguments count];
440 JSValueRef argumentArray[argumentCount];
441 for (unsigned i = 0; i < argumentCount; ++i)
442 argumentArray[i] = objectToValue(_context, [arguments objectAtIndex:i]);
443
444 JSValueRef exception = 0;
445 JSObjectRef thisObject = JSValueToObject([_context JSGlobalContextRef], m_value, &exception);
446 if (exception)
447 return [_context valueFromNotifyException:exception];
448
449 JSStringRef name = JSStringCreateWithCFString((CFStringRef)method);
450 JSValueRef function = JSObjectGetProperty([_context JSGlobalContextRef], thisObject, name, &exception);
451 JSStringRelease(name);
452 if (exception)
453 return [_context valueFromNotifyException:exception];
454
455 JSObjectRef object = JSValueToObject([_context JSGlobalContextRef], function, &exception);
456 if (exception)
457 return [_context valueFromNotifyException:exception];
458
459 JSValueRef result = JSObjectCallAsFunction([_context JSGlobalContextRef], object, thisObject, argumentCount, argumentArray, &exception);
460 if (exception)
461 return [_context valueFromNotifyException:exception];
462
463 return [JSValue valueWithJSValueRef:result inContext:_context];
464}
465
466@end
467
468@implementation JSValue(StructSupport)
469
470- (CGPoint)toPoint
471{
472 return (CGPoint){
473 static_cast<CGFloat>([self[@"x"] toDouble]),
474 static_cast<CGFloat>([self[@"y"] toDouble])
475 };
476}
477
478- (NSRange)toRange
479{
480 return (NSRange){
481 [[self[@"location"] toNumber] unsignedIntegerValue],
482 [[self[@"length"] toNumber] unsignedIntegerValue]
483 };
484}
485
486- (CGRect)toRect
487{
488 return (CGRect){
489 [self toPoint],
490 [self toSize]
491 };
492}
493
494- (CGSize)toSize
495{
496 return (CGSize){
497 static_cast<CGFloat>([self[@"width"] toDouble]),
498 static_cast<CGFloat>([self[@"height"] toDouble])
499 };
500}
501
502+ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context
503{
504 return [JSValue valueWithObject:@{
505 @"x":@(point.x),
506 @"y":@(point.y)
507 } inContext:context];
508}
509
510+ (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context
511{
512 return [JSValue valueWithObject:@{
513 @"location":@(range.location),
514 @"length":@(range.length)
515 } inContext:context];
516}
517
518+ (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context
519{
520 return [JSValue valueWithObject:@{
521 @"x":@(rect.origin.x),
522 @"y":@(rect.origin.y),
523 @"width":@(rect.size.width),
524 @"height":@(rect.size.height)
525 } inContext:context];
526}
527
528+ (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context
529{
530 return [JSValue valueWithObject:@{
531 @"width":@(size.width),
532 @"height":@(size.height)
533 } inContext:context];
534}
535
536@end
537
538@implementation JSValue(SubscriptSupport)
539
540- (JSValue *)objectForKeyedSubscript:(id)key
541{
542 if (![key isKindOfClass:[NSString class]]) {
543 key = [[JSValue valueWithObject:key inContext:_context] toString];
544 if (!key)
545 return [JSValue valueWithUndefinedInContext:_context];
546 }
547
548 return [self valueForProperty:(NSString *)key];
549}
550
551- (JSValue *)objectAtIndexedSubscript:(NSUInteger)index
552{
553 return [self valueAtIndex:index];
554}
555
556- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key
557{
558 if (![key isKindOfClass:[NSString class]]) {
559 key = [[JSValue valueWithObject:key inContext:_context] toString];
560 if (!key)
561 return;
562 }
563
564 [self setValue:object forProperty:(NSString *)key];
565}
566
567- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index
568{
569 [self setValue:object atIndex:index];
570}
571
572@end
573
574inline bool isDate(JSObjectRef object, JSGlobalContextRef context)
575{
576 JSC::JSLockHolder locker(toJS(context));
577 return toJS(object)->inherits(JSC::DateInstance::info());
578}
579
580inline bool isArray(JSObjectRef object, JSGlobalContextRef context)
581{
582 JSC::JSLockHolder locker(toJS(context));
583 return toJS(object)->inherits(JSC::JSArray::info());
584}
585
586@implementation JSValue(Internal)
587
588enum ConversionType {
589 ContainerNone,
590 ContainerArray,
591 ContainerDictionary
592};
593
594class JSContainerConvertor {
595public:
596 struct Task {
597 JSValueRef js;
598 id objc;
599 ConversionType type;
600 };
601
602 JSContainerConvertor(JSGlobalContextRef context)
603 : m_context(context)
604 {
605 }
606
607 id convert(JSValueRef property);
608 void add(Task);
609 Task take();
610 bool isWorkListEmpty() const { return !m_worklist.size(); }
611
612private:
613 JSGlobalContextRef m_context;
614 HashMap<JSValueRef, id> m_objectMap;
615 Vector<Task> m_worklist;
616 Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
617};
618
619inline id JSContainerConvertor::convert(JSValueRef value)
620{
621 HashMap<JSValueRef, id>::iterator iter = m_objectMap.find(value);
622 if (iter != m_objectMap.end())
623 return iter->value;
624
625 Task result = valueToObjectWithoutCopy(m_context, value);
626 if (result.js)
627 add(result);
628 return result.objc;
629}
630
631void JSContainerConvertor::add(Task task)
632{
633 JSC::ExecState* exec = toJS(m_context);
634 m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
635 m_objectMap.add(task.js, task.objc);
636 if (task.type != ContainerNone)
637 m_worklist.append(task);
638}
639
640JSContainerConvertor::Task JSContainerConvertor::take()
641{
642 ASSERT(!isWorkListEmpty());
643 Task last = m_worklist.last();
644 m_worklist.removeLast();
645 return last;
646}
647
648#if ENABLE(REMOTE_INSPECTOR)
649static void reportExceptionToInspector(JSGlobalContextRef context, JSC::JSValue exceptionValue)
650{
651 JSC::ExecState* exec = toJS(context);
652 JSC::Exception* exception = JSC::Exception::create(exec->vm(), exceptionValue);
653 exec->vmEntryGlobalObject()->inspectorController().reportAPIException(exec, exception);
654}
655#endif
656
657static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef context, JSValueRef value)
658{
659 if (!JSValueIsObject(context, value)) {
660 id primitive;
661 if (JSValueIsBoolean(context, value))
662 primitive = JSValueToBoolean(context, value) ? @YES : @NO;
663 else if (JSValueIsNumber(context, value)) {
664 // Normalize the number, so it will unique correctly in the hash map -
665 // it's nicer not to leak this internal implementation detail!
666 value = JSValueMakeNumber(context, JSValueToNumber(context, value, 0));
667 primitive = [NSNumber numberWithDouble:JSValueToNumber(context, value, 0)];
668 } else if (JSValueIsString(context, value)) {
669 // Would be nice to unique strings, too.
670 JSStringRef jsstring = JSValueToStringCopy(context, value, 0);
671 NSString * stringNS = (NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring);
672 JSStringRelease(jsstring);
673 primitive = [stringNS autorelease];
674 } else if (JSValueIsNull(context, value))
675 primitive = [NSNull null];
676 else {
677 ASSERT(JSValueIsUndefined(context, value));
678 primitive = nil;
679 }
680 return (JSContainerConvertor::Task){ value, primitive, ContainerNone };
681 }
682
683 JSObjectRef object = JSValueToObject(context, value, 0);
684
685 if (id wrapped = tryUnwrapObjcObject(context, object))
686 return (JSContainerConvertor::Task){ object, wrapped, ContainerNone };
687
688 if (isDate(object, context))
689 return (JSContainerConvertor::Task){ object, [NSDate dateWithTimeIntervalSince1970:JSValueToNumber(context, object, 0) / 1000.0], ContainerNone };
690
691 if (isArray(object, context))
692 return (JSContainerConvertor::Task){ object, [NSMutableArray array], ContainerArray };
693
694 return (JSContainerConvertor::Task){ object, [NSMutableDictionary dictionary], ContainerDictionary };
695}
696
697static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task)
698{
699 ASSERT(task.type != ContainerNone);
700 JSC::JSLockHolder locker(toJS(context));
701 JSContainerConvertor convertor(context);
702 convertor.add(task);
703 ASSERT(!convertor.isWorkListEmpty());
704
705 do {
706 JSContainerConvertor::Task current = convertor.take();
707 ASSERT(JSValueIsObject(context, current.js));
708 JSObjectRef js = JSValueToObject(context, current.js, 0);
709
710 if (current.type == ContainerArray) {
711 ASSERT([current.objc isKindOfClass:[NSMutableArray class]]);
712 NSMutableArray *array = (NSMutableArray *)current.objc;
713
714 JSStringRef lengthString = JSStringCreateWithUTF8CString("length");
715 unsigned length = JSC::toUInt32(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString, 0), 0));
716 JSStringRelease(lengthString);
717
718 for (unsigned i = 0; i < length; ++i) {
719 id objc = convertor.convert(JSObjectGetPropertyAtIndex(context, js, i, 0));
720 [array addObject:objc ? objc : [NSNull null]];
721 }
722 } else {
723 ASSERT([current.objc isKindOfClass:[NSMutableDictionary class]]);
724 NSMutableDictionary *dictionary = (NSMutableDictionary *)current.objc;
725
726 JSC::JSLockHolder locker(toJS(context));
727
728 JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js);
729 size_t length = JSPropertyNameArrayGetCount(propertyNameArray);
730
731 for (size_t i = 0; i < length; ++i) {
732 JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNameArray, i);
733 if (id objc = convertor.convert(JSObjectGetProperty(context, js, propertyName, 0)))
734 dictionary[[(NSString *)JSStringCopyCFString(kCFAllocatorDefault, propertyName) autorelease]] = objc;
735 }
736
737 JSPropertyNameArrayRelease(propertyNameArray);
738 }
739
740 } while (!convertor.isWorkListEmpty());
741
742 return task.objc;
743}
744
745id valueToObject(JSContext *context, JSValueRef value)
746{
747 JSContainerConvertor::Task result = valueToObjectWithoutCopy([context JSGlobalContextRef], value);
748 if (result.type == ContainerNone)
749 return result.objc;
750 return containerValueToObject([context JSGlobalContextRef], result);
751}
752
753id valueToNumber(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
754{
755 ASSERT(!*exception);
756 if (id wrapped = tryUnwrapObjcObject(context, value)) {
757 if ([wrapped isKindOfClass:[NSNumber class]])
758 return wrapped;
759 }
760
761 if (JSValueIsBoolean(context, value))
762 return JSValueToBoolean(context, value) ? @YES : @NO;
763
764 double result = JSValueToNumber(context, value, exception);
765 return [NSNumber numberWithDouble:*exception ? std::numeric_limits<double>::quiet_NaN() : result];
766}
767
768id valueToString(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
769{
770 ASSERT(!*exception);
771 if (id wrapped = tryUnwrapObjcObject(context, value)) {
772 if ([wrapped isKindOfClass:[NSString class]])
773 return wrapped;
774 }
775
776 JSStringRef jsstring = JSValueToStringCopy(context, value, exception);
777 if (*exception) {
778 ASSERT(!jsstring);
779 return nil;
780 }
781
782 RetainPtr<CFStringRef> stringCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, jsstring));
783 JSStringRelease(jsstring);
784 return (NSString *)stringCF.autorelease();
785}
786
787id valueToDate(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
788{
789 ASSERT(!*exception);
790 if (id wrapped = tryUnwrapObjcObject(context, value)) {
791 if ([wrapped isKindOfClass:[NSDate class]])
792 return wrapped;
793 }
794
795 double result = JSValueToNumber(context, value, exception) / 1000.0;
796 return *exception ? nil : [NSDate dateWithTimeIntervalSince1970:result];
797}
798
799id valueToArray(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
800{
801 ASSERT(!*exception);
802 if (id wrapped = tryUnwrapObjcObject(context, value)) {
803 if ([wrapped isKindOfClass:[NSArray class]])
804 return wrapped;
805 }
806
807 if (JSValueIsObject(context, value))
808 return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableArray array], ContainerArray});
809
810 JSC::JSLockHolder locker(toJS(context));
811 if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) {
812 JSC::JSObject* exceptionObject = JSC::createTypeError(toJS(context), ASCIILiteral("Cannot convert primitive to NSArray"));
813 *exception = toRef(exceptionObject);
814#if ENABLE(REMOTE_INSPECTOR)
815 reportExceptionToInspector(context, exceptionObject);
816#endif
817 }
818 return nil;
819}
820
821id valueToDictionary(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
822{
823 ASSERT(!*exception);
824 if (id wrapped = tryUnwrapObjcObject(context, value)) {
825 if ([wrapped isKindOfClass:[NSDictionary class]])
826 return wrapped;
827 }
828
829 if (JSValueIsObject(context, value))
830 return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableDictionary dictionary], ContainerDictionary});
831
832 JSC::JSLockHolder locker(toJS(context));
833 if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value))) {
834 JSC::JSObject* exceptionObject = JSC::createTypeError(toJS(context), ASCIILiteral("Cannot convert primitive to NSDictionary"));
835 *exception = toRef(exceptionObject);
836#if ENABLE(REMOTE_INSPECTOR)
837 reportExceptionToInspector(context, exceptionObject);
838#endif
839 }
840 return nil;
841}
842
843class ObjcContainerConvertor {
844public:
845 struct Task {
846 id objc;
847 JSValueRef js;
848 ConversionType type;
849 };
850
851 ObjcContainerConvertor(JSContext *context)
852 : m_context(context)
853 {
854 }
855
856 JSValueRef convert(id object);
857 void add(Task);
858 Task take();
859 bool isWorkListEmpty() const { return !m_worklist.size(); }
860
861private:
862 JSContext *m_context;
863 HashMap<id, JSValueRef> m_objectMap;
864 Vector<Task> m_worklist;
865 Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
866};
867
868JSValueRef ObjcContainerConvertor::convert(id object)
869{
870 ASSERT(object);
871
872 auto it = m_objectMap.find(object);
873 if (it != m_objectMap.end())
874 return it->value;
875
876 ObjcContainerConvertor::Task task = objectToValueWithoutCopy(m_context, object);
877 add(task);
878 return task.js;
879}
880
881void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task)
882{
883 JSC::ExecState* exec = toJS(m_context.JSGlobalContextRef);
884 m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
885 m_objectMap.add(task.objc, task.js);
886 if (task.type != ContainerNone)
887 m_worklist.append(task);
888}
889
890ObjcContainerConvertor::Task ObjcContainerConvertor::take()
891{
892 ASSERT(!isWorkListEmpty());
893 Task last = m_worklist.last();
894 m_worklist.removeLast();
895 return last;
896}
897
898inline bool isNSBoolean(id object)
899{
900 ASSERT([@YES class] == [@NO class]);
901 ASSERT([@YES class] != [NSNumber class]);
902 ASSERT([[@YES class] isSubclassOfClass:[NSNumber class]]);
903 return [object isKindOfClass:[@YES class]];
904}
905
906static ObjcContainerConvertor::Task objectToValueWithoutCopy(JSContext *context, id object)
907{
908 JSGlobalContextRef contextRef = [context JSGlobalContextRef];
909
910 if (!object)
911 return (ObjcContainerConvertor::Task){ object, JSValueMakeUndefined(contextRef), ContainerNone };
912
913 if (!class_conformsToProtocol(object_getClass(object), getJSExportProtocol())) {
914 if ([object isKindOfClass:[NSArray class]])
915 return (ObjcContainerConvertor::Task){ object, JSObjectMakeArray(contextRef, 0, NULL, 0), ContainerArray };
916
917 if ([object isKindOfClass:[NSDictionary class]])
918 return (ObjcContainerConvertor::Task){ object, JSObjectMake(contextRef, 0, 0), ContainerDictionary };
919
920 if ([object isKindOfClass:[NSNull class]])
921 return (ObjcContainerConvertor::Task){ object, JSValueMakeNull(contextRef), ContainerNone };
922
923 if ([object isKindOfClass:[JSValue class]])
924 return (ObjcContainerConvertor::Task){ object, ((JSValue *)object)->m_value, ContainerNone };
925
926 if ([object isKindOfClass:[NSString class]]) {
927 JSStringRef string = JSStringCreateWithCFString((CFStringRef)object);
928 JSValueRef js = JSValueMakeString(contextRef, string);
929 JSStringRelease(string);
930 return (ObjcContainerConvertor::Task){ object, js, ContainerNone };
931 }
932
933 if ([object isKindOfClass:[NSNumber class]]) {
934 if (isNSBoolean(object))
935 return (ObjcContainerConvertor::Task){ object, JSValueMakeBoolean(contextRef, [object boolValue]), ContainerNone };
936 return (ObjcContainerConvertor::Task){ object, JSValueMakeNumber(contextRef, [object doubleValue]), ContainerNone };
937 }
938
939 if ([object isKindOfClass:[NSDate class]]) {
940 JSValueRef argument = JSValueMakeNumber(contextRef, [object timeIntervalSince1970] * 1000.0);
941 JSObjectRef result = JSObjectMakeDate(contextRef, 1, &argument, 0);
942 return (ObjcContainerConvertor::Task){ object, result, ContainerNone };
943 }
944
945 if ([object isKindOfClass:[JSManagedValue class]]) {
946 JSValue *value = [static_cast<JSManagedValue *>(object) value];
947 if (!value)
948 return (ObjcContainerConvertor::Task) { object, JSValueMakeUndefined(contextRef), ContainerNone };
949 return (ObjcContainerConvertor::Task){ object, value->m_value, ContainerNone };
950 }
951 }
952
953 return (ObjcContainerConvertor::Task){ object, valueInternalValue([context wrapperForObjCObject:object]), ContainerNone };
954}
955
956JSValueRef objectToValue(JSContext *context, id object)
957{
958 JSGlobalContextRef contextRef = [context JSGlobalContextRef];
959
960 ObjcContainerConvertor::Task task = objectToValueWithoutCopy(context, object);
961 if (task.type == ContainerNone)
962 return task.js;
963
964 JSC::JSLockHolder locker(toJS(contextRef));
965 ObjcContainerConvertor convertor(context);
966 convertor.add(task);
967 ASSERT(!convertor.isWorkListEmpty());
968
969 do {
970 ObjcContainerConvertor::Task current = convertor.take();
971 ASSERT(JSValueIsObject(contextRef, current.js));
972 JSObjectRef js = JSValueToObject(contextRef, current.js, 0);
973
974 if (current.type == ContainerArray) {
975 ASSERT([current.objc isKindOfClass:[NSArray class]]);
976 NSArray *array = (NSArray *)current.objc;
977 NSUInteger count = [array count];
978 for (NSUInteger index = 0; index < count; ++index)
979 JSObjectSetPropertyAtIndex(contextRef, js, index, convertor.convert([array objectAtIndex:index]), 0);
980 } else {
981 ASSERT(current.type == ContainerDictionary);
982 ASSERT([current.objc isKindOfClass:[NSDictionary class]]);
983 NSDictionary *dictionary = (NSDictionary *)current.objc;
984 for (id key in [dictionary keyEnumerator]) {
985 if ([key isKindOfClass:[NSString class]]) {
986 JSStringRef propertyName = JSStringCreateWithCFString((CFStringRef)key);
987 JSObjectSetProperty(contextRef, js, propertyName, convertor.convert([dictionary objectForKey:key]), 0, 0);
988 JSStringRelease(propertyName);
989 }
990 }
991 }
992
993 } while (!convertor.isWorkListEmpty());
994
995 return task.js;
996}
997
998JSValueRef valueInternalValue(JSValue * value)
999{
1000 return value->m_value;
1001}
1002
1003+ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context
1004{
1005 return [context wrapperForJSObject:value];
1006}
1007
1008- (JSValue *)init
1009{
1010 return nil;
1011}
1012
1013- (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context
1014{
1015 if (!value || !context)
1016 return nil;
1017
1018 self = [super init];
1019 if (!self)
1020 return nil;
1021
1022 _context = [context retain];
1023 m_value = value;
1024 JSValueProtect([_context JSGlobalContextRef], m_value);
1025 return self;
1026}
1027
1028struct StructTagHandler {
1029 SEL typeToValueSEL;
1030 SEL valueToTypeSEL;
1031};
1032typedef HashMap<String, StructTagHandler> StructHandlers;
1033
1034static StructHandlers* createStructHandlerMap()
1035{
1036 StructHandlers* structHandlers = new StructHandlers();
1037
1038 size_t valueWithXinContextLength = strlen("valueWithX:inContext:");
1039 size_t toXLength = strlen("toX");
1040
1041 // Step 1: find all valueWith<Foo>:inContext: class methods in JSValue.
1042 forEachMethodInClass(object_getClass([JSValue class]), ^(Method method){
1043 SEL selector = method_getName(method);
1044 const char* name = sel_getName(selector);
1045 size_t nameLength = strlen(name);
1046 // Check for valueWith<Foo>:context:
1047 if (nameLength < valueWithXinContextLength || memcmp(name, "valueWith", 9) || memcmp(name + nameLength - 11, ":inContext:", 11))
1048 return;
1049 // Check for [ id, SEL, <type>, <contextType> ]
1050 if (method_getNumberOfArguments(method) != 4)
1051 return;
1052 char idType[3];
1053 // Check 2nd argument type is "@"
1054 char* secondType = method_copyArgumentType(method, 3);
1055 if (strcmp(secondType, "@") != 0) {
1056 free(secondType);
1057 return;
1058 }
1059 free(secondType);
1060 // Check result type is also "@"
1061 method_getReturnType(method, idType, 3);
1062 if (strcmp(idType, "@") != 0)
1063 return;
1064 char* type = method_copyArgumentType(method, 2);
1065 structHandlers->add(StringImpl::create(type), (StructTagHandler){ selector, 0 });
1066 free(type);
1067 });
1068
1069 // Step 2: find all to<Foo> instance methods in JSValue.
1070 forEachMethodInClass([JSValue class], ^(Method method){
1071 SEL selector = method_getName(method);
1072 const char* name = sel_getName(selector);
1073 size_t nameLength = strlen(name);
1074 // Check for to<Foo>
1075 if (nameLength < toXLength || memcmp(name, "to", 2))
1076 return;
1077 // Check for [ id, SEL ]
1078 if (method_getNumberOfArguments(method) != 2)
1079 return;
1080 // Try to find a matching valueWith<Foo>:context: method.
1081 char* type = method_copyReturnType(method);
1082
1083 StructHandlers::iterator iter = structHandlers->find(type);
1084 free(type);
1085 if (iter == structHandlers->end())
1086 return;
1087 StructTagHandler& handler = iter->value;
1088
1089 // check that strlen(<foo>) == strlen(<Foo>)
1090 const char* valueWithName = sel_getName(handler.typeToValueSEL);
1091 size_t valueWithLength = strlen(valueWithName);
1092 if (valueWithLength - valueWithXinContextLength != nameLength - toXLength)
1093 return;
1094 // Check that <Foo> == <Foo>
1095 if (memcmp(valueWithName + 9, name + 2, nameLength - toXLength - 1))
1096 return;
1097 handler.valueToTypeSEL = selector;
1098 });
1099
1100 // Step 3: clean up - remove entries where we found prospective valueWith<Foo>:inContext: conversions, but no matching to<Foo> methods.
1101 typedef HashSet<String> RemoveSet;
1102 RemoveSet removeSet;
1103 for (StructHandlers::iterator iter = structHandlers->begin(); iter != structHandlers->end(); ++iter) {
1104 StructTagHandler& handler = iter->value;
1105 if (!handler.valueToTypeSEL)
1106 removeSet.add(iter->key);
1107 }
1108
1109 for (RemoveSet::iterator iter = removeSet.begin(); iter != removeSet.end(); ++iter)
1110 structHandlers->remove(*iter);
1111
1112 return structHandlers;
1113}
1114
1115static StructTagHandler* handerForStructTag(const char* encodedType)
1116{
1117 static StaticSpinLock handerForStructTagLock;
1118 SpinLockHolder lockHolder(&handerForStructTagLock);
1119
1120 static StructHandlers* structHandlers = createStructHandlerMap();
1121
1122 StructHandlers::iterator iter = structHandlers->find(encodedType);
1123 if (iter == structHandlers->end())
1124 return 0;
1125 return &iter->value;
1126}
1127
1128+ (SEL)selectorForStructToValue:(const char *)structTag
1129{
1130 StructTagHandler* handler = handerForStructTag(structTag);
1131 return handler ? handler->typeToValueSEL : nil;
1132}
1133
1134+ (SEL)selectorForValueToStruct:(const char *)structTag
1135{
1136 StructTagHandler* handler = handerForStructTag(structTag);
1137 return handler ? handler->valueToTypeSEL : nil;
1138}
1139
1140- (void)dealloc
1141{
1142 JSValueUnprotect([_context JSGlobalContextRef], m_value);
1143 [_context release];
1144 _context = nil;
1145 [super dealloc];
1146}
1147
1148- (NSString *)description
1149{
1150 if (id wrapped = tryUnwrapObjcObject([_context JSGlobalContextRef], m_value))
1151 return [wrapped description];
1152 return [self toString];
1153}
1154
1155NSInvocation *typeToValueInvocationFor(const char* encodedType)
1156{
1157 SEL selector = [JSValue selectorForStructToValue:encodedType];
1158 if (!selector)
1159 return 0;
1160
1161 const char* methodTypes = method_getTypeEncoding(class_getClassMethod([JSValue class], selector));
1162 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
1163 [invocation setSelector:selector];
1164 return invocation;
1165}
1166
1167NSInvocation *valueToTypeInvocationFor(const char* encodedType)
1168{
1169 SEL selector = [JSValue selectorForValueToStruct:encodedType];
1170 if (!selector)
1171 return 0;
1172
1173 const char* methodTypes = method_getTypeEncoding(class_getInstanceMethod([JSValue class], selector));
1174 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:methodTypes]];
1175 [invocation setSelector:selector];
1176 return invocation;
1177}
1178
1179@end
1180
1181#endif