]> git.saurik.com Git - apple/javascriptcore.git/blame - API/JSValue.mm
JavaScriptCore-1218.34.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"
12899fa2 40#import "StrongInlines.h"
93a37866
A
41#import <wtf/HashMap.h>
42#import <wtf/HashSet.h>
12899fa2 43#import <wtf/ObjcRuntimeExtras.h>
93a37866
A
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
51NSString * const JSPropertyDescriptorWritableKey = @"writable";
52NSString * const JSPropertyDescriptorEnumerableKey = @"enumerable";
53NSString * const JSPropertyDescriptorConfigurableKey = @"configurable";
54NSString * const JSPropertyDescriptorValueKey = @"value";
55NSString * const JSPropertyDescriptorGetKey = @"get";
56NSString * 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
557inline bool isDate(JSObjectRef object, JSGlobalContextRef context)
558{
559 JSC::APIEntryShim entryShim(toJS(context));
560 return toJS(object)->inherits(&JSC::DateInstance::s_info);
561}
562
563inline 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
571enum ConversionType {
572 ContainerNone,
573 ContainerArray,
574 ContainerDictionary
575};
576
577class JSContainerConvertor {
578public:
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
595private:
596 JSGlobalContextRef m_context;
597 HashMap<JSValueRef, id> m_objectMap;
598 Vector<Task> m_worklist;
12899fa2 599 Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
93a37866
A
600};
601
602inline 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
614void JSContainerConvertor::add(Task task)
615{
12899fa2
A
616 JSC::ExecState* exec = toJS(m_context);
617 m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
93a37866
A
618 m_objectMap.add(task.js, task.objc);
619 if (task.type != ContainerNone)
620 m_worklist.append(task);
621}
622
623JSContainerConvertor::Task JSContainerConvertor::take()
624{
625 ASSERT(!isWorkListEmpty());
626 Task last = m_worklist.last();
627 m_worklist.removeLast();
628 return last;
629}
630
631static 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
671static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task)
672{
673 ASSERT(task.type != ContainerNone);
12899fa2 674 JSC::APIEntryShim entryShim(toJS(context));
93a37866
A
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
717id 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
725id 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
740id 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
12899fa2 754 NSString *stringNS = HardAutorelease(JSStringCopyCFString(kCFAllocatorDefault, jsstring));
93a37866
A
755 JSStringRelease(jsstring);
756 return stringNS;
757}
758
759id 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
771id 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
787id 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
803class ObjcContainerConvertor {
804public:
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
821private:
822 JSContext *m_context;
823 HashMap<id, JSValueRef> m_objectMap;
824 Vector<Task> m_worklist;
12899fa2 825 Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
93a37866
A
826};
827
828JSValueRef 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
841void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task)
842{
12899fa2
A
843 JSC::ExecState* exec = toJS(m_context.JSGlobalContextRef);
844 m_jsValues.append(JSC::Strong<JSC::Unknown>(exec->vm(), toJSForGC(exec, task.js)));
93a37866
A
845 m_objectMap.add(task.objc, task.js);
846 if (task.type != ContainerNone)
847 m_worklist.append(task);
848}
849
850ObjcContainerConvertor::Task ObjcContainerConvertor::take()
851{
852 ASSERT(!isWorkListEmpty());
853 Task last = m_worklist.last();
854 m_worklist.removeLast();
855 return last;
856}
857
858inline 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
866static 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
916JSValueRef 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
12899fa2 924 JSC::APIEntryShim entryShim(toJS(contextRef));
93a37866
A
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
958JSValueRef 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
988struct StructTagHandler {
989 SEL typeToValueSEL;
990 SEL valueToTypeSEL;
991};
992typedef HashMap<String, StructTagHandler> StructHandlers;
993
994static 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
1075static 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
1115NSInvocation *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
1127NSInvocation *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