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