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