]> git.saurik.com Git - apple/javascriptcore.git/blob - API/JSValue.mm
a380964ad315587ee5635175ddb7850917eb14b5
[apple/javascriptcore.git] / API / JSValue.mm
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
49 NSString * const JSPropertyDescriptorWritableKey = @"writable";
50 NSString * const JSPropertyDescriptorEnumerableKey = @"enumerable";
51 NSString * const JSPropertyDescriptorConfigurableKey = @"configurable";
52 NSString * const JSPropertyDescriptorValueKey = @"value";
53 NSString * const JSPropertyDescriptorGetKey = @"get";
54 NSString * 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
555 inline bool isDate(JSObjectRef object, JSGlobalContextRef context)
556 {
557 JSC::APIEntryShim entryShim(toJS(context));
558 return toJS(object)->inherits(&JSC::DateInstance::s_info);
559 }
560
561 inline 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
569 enum ConversionType {
570 ContainerNone,
571 ContainerArray,
572 ContainerDictionary
573 };
574
575 class JSContainerConvertor {
576 public:
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
593 private:
594 JSGlobalContextRef m_context;
595 HashMap<JSValueRef, id> m_objectMap;
596 Vector<Task> m_worklist;
597 };
598
599 inline 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
611 void 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
618 JSContainerConvertor::Task JSContainerConvertor::take()
619 {
620 ASSERT(!isWorkListEmpty());
621 Task last = m_worklist.last();
622 m_worklist.removeLast();
623 return last;
624 }
625
626 static 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
666 static 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
711 id 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
719 id 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
734 id 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
753 id 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
765 id 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
781 id 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
797 class ObjcContainerConvertor {
798 public:
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
815 private:
816 JSContext *m_context;
817 HashMap<id, JSValueRef> m_objectMap;
818 Vector<Task> m_worklist;
819 };
820
821 JSValueRef 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
834 void 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
841 ObjcContainerConvertor::Task ObjcContainerConvertor::take()
842 {
843 ASSERT(!isWorkListEmpty());
844 Task last = m_worklist.last();
845 m_worklist.removeLast();
846 return last;
847 }
848
849 inline 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
857 static 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
907 JSValueRef 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
948 JSValueRef 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
978 struct StructTagHandler {
979 SEL typeToValueSEL;
980 SEL valueToTypeSEL;
981 };
982 typedef HashMap<String, StructTagHandler> StructHandlers;
983
984 static 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
1065 static 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
1105 NSInvocation *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
1117 NSInvocation *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