JSValue.mm 37 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/*
 * Copyright (C) 2013 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include "config.h"
27

28 29 30 31 32 33 34 35 36 37
#import "APICast.h"
#import "APIShims.h"
#import "DateInstance.h"
#import "Error.h"
#import "JavaScriptCore.h"
#import "JSContextInternal.h"
#import "JSVirtualMachineInternal.h"
#import "JSValueInternal.h"
#import "JSWrapperMap.h"
#import "ObjcRuntimeExtras.h"
38
#import "Operations.h"
39
#import "JSCJSValue.h"
40 41 42
#import <wtf/HashMap.h>
#import <wtf/HashSet.h>
#import <wtf/Vector.h>
43
#import <wtf/TCSpinLock.h>
44
#import <wtf/text/WTFString.h>
45 46
#import <wtf/text/StringHash.h>

47
#if JSC_OBJC_API_ENABLED
48 49 50 51 52 53 54 55 56 57 58 59

NSString * const JSPropertyDescriptorWritableKey = @"writable";
NSString * const JSPropertyDescriptorEnumerableKey = @"enumerable";
NSString * const JSPropertyDescriptorConfigurableKey = @"configurable";
NSString * const JSPropertyDescriptorValueKey = @"value";
NSString * const JSPropertyDescriptorGetKey = @"get";
NSString * const JSPropertyDescriptorSetKey = @"set";

@implementation JSValue {
    JSValueRef m_value;
}

60 61 62 63 64
- (JSValueRef)JSValueRef
{
    return m_value;
}

65 66 67 68
+ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context
{
    return [JSValue valueWithValue:objectToValue(context, value) inContext:context];
}
69

70 71
+ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context
{
72
    return [JSValue valueWithValue:JSValueMakeBoolean([context globalContextRef], value) inContext:context];
73
}
74

75 76
+ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context
{
77
    return [JSValue valueWithValue:JSValueMakeNumber([context globalContextRef], value) inContext:context];
78
}
79

80 81
+ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context
{
82
    return [JSValue valueWithValue:JSValueMakeNumber([context globalContextRef], value) inContext:context];
83
}
84

85 86
+ (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context
{
87
    return [JSValue valueWithValue:JSValueMakeNumber([context globalContextRef], value) inContext:context];
88
}
89

90 91
+ (JSValue *)valueWithNewObjectInContext:(JSContext *)context
{
92
    return [JSValue valueWithValue:JSObjectMake([context globalContextRef], 0, 0) inContext:context];
93
}
94

95 96
+ (JSValue *)valueWithNewArrayInContext:(JSContext *)context
{
97
    return [JSValue valueWithValue:JSObjectMakeArray([context globalContextRef], 0, NULL, 0) inContext:context];
98
}
99

100 101 102 103
+ (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context
{
    JSStringRef patternString = JSStringCreateWithCFString((CFStringRef)pattern);
    JSStringRef flagsString = JSStringCreateWithCFString((CFStringRef)flags);
104
    JSValueRef arguments[2] = { JSValueMakeString([context globalContextRef], patternString), JSValueMakeString([context globalContextRef], flagsString) };
105 106 107
    JSStringRelease(patternString);
    JSStringRelease(flagsString);

108
    return [JSValue valueWithValue:JSObjectMakeRegExp([context globalContextRef], 2, arguments, 0) inContext:context];
109
}
110

111 112 113
+ (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context
{
    JSStringRef string = JSStringCreateWithCFString((CFStringRef)message);
114
    JSValueRef argument = JSValueMakeString([context globalContextRef], string);
115 116
    JSStringRelease(string);

117
    return [JSValue valueWithValue:JSObjectMakeError([context globalContextRef], 1, &argument, 0) inContext:context];
118
}
119

120 121
+ (JSValue *)valueWithNullInContext:(JSContext *)context
{
122
    return [JSValue valueWithValue:JSValueMakeNull([context globalContextRef]) inContext:context];
123
}
124

125 126
+ (JSValue *)valueWithUndefinedInContext:(JSContext *)context
{
127
    return [JSValue valueWithValue:JSValueMakeUndefined([context globalContextRef]) inContext:context];
128 129 130 131
}

- (id)toObject
{
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 142
}

- (BOOL)toBool
{
143
    return JSValueToBoolean([_context globalContextRef], m_value);
144
}
145

146 147 148
- (double)toDouble
{
    JSValueRef exception = 0;
149
    double result = JSValueToNumber([_context globalContextRef], m_value, &exception);
150
    if (exception) {
151
        [_context notifyException:exception];
152 153 154 155 156
        return std::numeric_limits<double>::quiet_NaN();
    }

    return result;
}
157

158 159 160 161
- (int32_t)toInt32
{
    return JSC::toInt32([self toDouble]);
}
162

163 164 165 166
- (uint32_t)toUInt32
{
    return JSC::toUInt32([self toDouble]);
}
167

168 169 170
- (NSNumber *)toNumber
{
    JSValueRef exception = 0;
171
    id result = valueToNumber([_context globalContextRef], m_value, &exception);
172
    if (exception)
173
        [_context notifyException:exception];
174 175
    return result;
}
176

177 178 179
- (NSString *)toString
{
    JSValueRef exception = 0;
180
    id result = valueToString([_context globalContextRef], m_value, &exception);
181
    if (exception)
182
        [_context notifyException:exception];
183 184
    return result;
}
185

186 187 188
- (NSDate *)toDate
{
    JSValueRef exception = 0;
189
    id result = valueToDate([_context globalContextRef], m_value, &exception);
190
    if (exception)
191
        [_context notifyException:exception];
192 193
    return result;
}
194

195 196 197
- (NSArray *)toArray
{
    JSValueRef exception = 0;
198
    id result = valueToArray([_context globalContextRef], m_value, &exception);
199
    if (exception)
200
        [_context notifyException:exception];
201 202
    return result;
}
203

204 205 206
- (NSDictionary *)toDictionary
{
    JSValueRef exception = 0;
207
    id result = valueToDictionary([_context globalContextRef], m_value, &exception);
208
    if (exception)
209
        [_context notifyException:exception];
210 211 212 213 214 215
    return result;
}

- (JSValue *)valueForProperty:(NSString *)propertyName
{
    JSValueRef exception = 0;
216
    JSObjectRef object = JSValueToObject([_context globalContextRef], m_value, &exception);
217
    if (exception)
218
        return [_context valueFromNotifyException:exception];
219 220

    JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
221
    JSValueRef result = JSObjectGetProperty([_context globalContextRef], object, name, &exception);
222 223
    JSStringRelease(name);
    if (exception)
224
        return [_context valueFromNotifyException:exception];
225

226
    return [JSValue valueWithValue:result inContext:_context];
227
}
228

229 230 231
- (void)setValue:(id)value forProperty:(NSString *)propertyName
{
    JSValueRef exception = 0;
232
    JSObjectRef object = JSValueToObject([_context globalContextRef], m_value, &exception);
233
    if (exception) {
234
        [_context notifyException:exception];
235 236 237 238
        return;
    }

    JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
239
    JSObjectSetProperty([_context globalContextRef], object, name, objectToValue(_context, value), 0, &exception);
240 241
    JSStringRelease(name);
    if (exception) {
242
        [_context notifyException:exception];
243 244 245
        return;
    }
}
246

247 248 249
- (BOOL)deleteProperty:(NSString *)propertyName
{
    JSValueRef exception = 0;
250
    JSObjectRef object = JSValueToObject([_context globalContextRef], m_value, &exception);
251
    if (exception)
252
        return [_context boolFromNotifyException:exception];
253 254

    JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
255
    BOOL result = JSObjectDeleteProperty([_context globalContextRef], object, name, &exception);
256 257
    JSStringRelease(name);
    if (exception)
258
        return [_context boolFromNotifyException:exception];
259 260 261

    return result;
}
262

263 264 265
- (BOOL)hasProperty:(NSString *)propertyName
{
    JSValueRef exception = 0;
266
    JSObjectRef object = JSValueToObject([_context globalContextRef], m_value, &exception);
267
    if (exception)
268
        return [_context boolFromNotifyException:exception];
269 270

    JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
271
    BOOL result = JSObjectHasProperty([_context globalContextRef], object, name);
272 273 274
    JSStringRelease(name);
    return result;
}
275

276 277
- (void)defineProperty:(NSString *)property descriptor:(id)descriptor
{
278
    [[_context globalObject][@"Object"] invokeMethod:@"defineProperty" withArguments:@[ self, property, descriptor ]];
279 280 281 282
}

- (JSValue *)valueAtIndex:(NSUInteger)index
{
283 284
    // Properties that are higher than an unsigned value can hold are converted to a double then inserted as a normal property.
    // 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 globalContextRef], m_value, &exception);
290
    if (exception)
291
        return [_context valueFromNotifyException:exception];
292

293
    JSValueRef result = JSObjectGetPropertyAtIndex([_context globalContextRef], object, (unsigned)index, &exception);
294
    if (exception)
295
        return [_context valueFromNotifyException:exception];
296

297
    return [JSValue valueWithValue:result inContext:_context];
298
}
299

300 301
- (void)setValue:(id)value atIndex:(NSUInteger)index
{
302 303
    // Properties that are higher than an unsigned value can hold are converted to a double, then inserted as a normal property.
    // 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 globalContextRef], m_value, &exception);
309
    if (exception) {
310
        [_context notifyException:exception];
311 312 313
        return;
    }

314
    JSObjectSetPropertyAtIndex([_context globalContextRef], object, (unsigned)index, objectToValue(_context, value), &exception);
315
    if (exception) {
316
        [_context notifyException:exception];
317 318 319 320 321 322
        return;
    }
}

- (BOOL)isUndefined
{
323
    return JSValueIsUndefined([_context globalContextRef], m_value);
324
}
325

326 327
- (BOOL)isNull
{
328
    return JSValueIsNull([_context globalContextRef], m_value);
329
}
330

331 332
- (BOOL)isBoolean
{
333
    return JSValueIsBoolean([_context globalContextRef], m_value);
334
}
335

336 337
- (BOOL)isNumber
{
338
    return JSValueIsNumber([_context globalContextRef], m_value);
339
}
340

341 342
- (BOOL)isString
{
343
    return JSValueIsString([_context globalContextRef], m_value);
344
}
345

346 347
- (BOOL)isObject
{
348
    return JSValueIsObject([_context globalContextRef], m_value);
349 350 351 352
}

- (BOOL)isEqualToObject:(id)value
{
353
    return JSValueIsStrictEqual([_context globalContextRef], m_value, objectToValue(_context, value));
354
}
355

356 357 358
- (BOOL)isEqualWithTypeCoercionToObject:(id)value
{
    JSValueRef exception = 0;
359
    BOOL result = JSValueIsEqual([_context globalContextRef], m_value, objectToValue(_context, value), &exception);
360
    if (exception)
361
        return [_context boolFromNotifyException:exception];
362 363 364

    return result;
}
365

366 367 368
- (BOOL)isInstanceOf:(id)value
{
    JSValueRef exception = 0;
369
    JSObjectRef constructor = JSValueToObject([_context globalContextRef], objectToValue(_context, value), &exception);
370
    if (exception)
371
        return [_context boolFromNotifyException:exception];
372

373
    BOOL result = JSValueIsInstanceOfConstructor([_context globalContextRef], m_value, constructor, &exception);
374
    if (exception)
375
        return [_context boolFromNotifyException:exception];
376 377 378 379

    return result;
}

380
- (JSValue *)callWithArguments:(NSArray *)argumentArray
381
{
382
    NSUInteger argumentCount = [argumentArray count];
383 384
    JSValueRef arguments[argumentCount];
    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 globalContextRef], m_value, &exception);
389
    if (exception)
390
        return [_context valueFromNotifyException:exception];
391

392
    JSValueRef result = JSObjectCallAsFunction([_context globalContextRef], object, 0, argumentCount, arguments, &exception);
393
    if (exception)
394
        return [_context valueFromNotifyException:exception];
395

396
    return [JSValue valueWithValue:result inContext:_context];
397
}
398 399

- (JSValue *)constructWithArguments:(NSArray *)argumentArray
400
{
401
    NSUInteger argumentCount = [argumentArray count];
402 403
    JSValueRef arguments[argumentCount];
    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 globalContextRef], m_value, &exception);
408
    if (exception)
409
        return [_context valueFromNotifyException:exception];
410

411
    JSObjectRef result = JSObjectCallAsConstructor([_context globalContextRef], object, argumentCount, arguments, &exception);
412
    if (exception)
413
        return [_context valueFromNotifyException:exception];
414

415
    return [JSValue valueWithValue:result inContext:_context];
416
}
417

418 419 420
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments
{
    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 globalContextRef], m_value, &exception);
427
    if (exception)
428
        return [_context valueFromNotifyException:exception];
429 430

    JSStringRef name = JSStringCreateWithCFString((CFStringRef)method);
431
    JSValueRef function = JSObjectGetProperty([_context globalContextRef], thisObject, name, &exception);
432 433
    JSStringRelease(name);
    if (exception)
434
        return [_context valueFromNotifyException:exception];
435

436
    JSObjectRef object = JSValueToObject([_context globalContextRef], function, &exception);
437
    if (exception)
438
        return [_context valueFromNotifyException:exception];
439

440
    JSValueRef result = JSObjectCallAsFunction([_context globalContextRef], object, thisObject, argumentCount, argumentArray, &exception);
441
    if (exception)
442
        return [_context valueFromNotifyException:exception];
443

444
    return [JSValue valueWithValue:result inContext:_context];
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
}

@end

@implementation JSValue(StructSupport)

- (CGPoint)toPoint
{
    return (CGPoint){
        [self[@"x"] toDouble],
        [self[@"y"] toDouble]
    };
}

- (NSRange)toRange
{
    return (NSRange){
462 463
        [[self[@"location"] toNumber] unsignedIntegerValue],
        [[self[@"length"] toNumber] unsignedIntegerValue]
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
    };
}

- (CGRect)toRect
{
    return (CGRect){
        [self toPoint],
        [self toSize]
    };
}

- (CGSize)toSize
{
    return (CGSize){
        [self[@"width"] toDouble],
        [self[@"height"] toDouble]
    };
}

+ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context
{
    return [JSValue valueWithObject:@{
486 487
        @"x":@(point.x),
        @"y":@(point.y)
488 489 490 491 492 493
    } inContext:context];
}

+ (JSValue *)valueWithRange:(NSRange)range inContext:(JSContext *)context
{
    return [JSValue valueWithObject:@{
494 495
        @"location":@(range.location),
        @"length":@(range.length)
496 497 498 499 500 501
    } inContext:context];
}

+ (JSValue *)valueWithRect:(CGRect)rect inContext:(JSContext *)context
{
    return [JSValue valueWithObject:@{
502 503 504 505
        @"x":@(rect.origin.x),
        @"y":@(rect.origin.y),
        @"width":@(rect.size.width),
        @"height":@(rect.size.height)
506 507 508 509 510 511
    } inContext:context];
}

+ (JSValue *)valueWithSize:(CGSize)size inContext:(JSContext *)context
{
    return [JSValue valueWithObject:@{
512 513
        @"width":@(size.width),
        @"height":@(size.height)
514 515 516 517 518 519 520 521 522
    } inContext:context];
}

@end

@implementation JSValue(SubscriptSupport)

- (JSValue *)objectForKeyedSubscript:(id)key
{
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 530 531 532 533 534 535 536
    }

    return [self valueForProperty:(NSString *)key];
}

- (JSValue *)objectAtIndexedSubscript:(NSUInteger)index
{
    return [self valueAtIndex:index];
}

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 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
        if (!key)
            return;
    }

    [self setValue:object forProperty:(NSString *)key];
}

- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index
{
    [self setValue:object atIndex:index];
}

@end

inline bool isDate(JSObjectRef object, JSGlobalContextRef context)
{
    JSC::APIEntryShim entryShim(toJS(context));
    return toJS(object)->inherits(&JSC::DateInstance::s_info);
}

inline bool isArray(JSObjectRef object, JSGlobalContextRef context)
{
    JSC::APIEntryShim entryShim(toJS(context));
    return toJS(object)->inherits(&JSC::JSArray::s_info);
}

@implementation JSValue(Internal)

enum ConversionType {
    ContainerNone,
    ContainerArray,
    ContainerDictionary
};

class JSContainerConvertor {
public:
    struct Task {
        JSValueRef js;
        id objc;
        ConversionType type;
    };

    JSContainerConvertor(JSGlobalContextRef context)
        : m_context(context)
    {
    }

    id convert(JSValueRef property);
589
    void add(Task);
590
    Task take();
591
    bool isWorkListEmpty() const { return !m_worklist.size(); }
592 593 594

private:
    JSGlobalContextRef m_context;
595 596
    HashMap<JSValueRef, id> m_objectMap;
    Vector<Task> m_worklist;
597 598 599 600
};

inline id JSContainerConvertor::convert(JSValueRef value)
{
601
    HashMap<JSValueRef, id>::iterator iter = m_objectMap.find(value);
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712
    if (iter != m_objectMap.end())
        return iter->value;

    Task result = valueToObjectWithoutCopy(m_context, value);
    if (result.js)
        add(result);
    return result.objc;
}

void JSContainerConvertor::add(Task task)
{
    m_objectMap.add(task.js, task.objc);
    if (task.type != ContainerNone)
        m_worklist.append(task);
}

JSContainerConvertor::Task JSContainerConvertor::take()
{
    ASSERT(!isWorkListEmpty());
    Task last = m_worklist.last();
    m_worklist.removeLast();
    return last;
}

static JSContainerConvertor::Task valueToObjectWithoutCopy(JSGlobalContextRef context, JSValueRef value)
{
    if (!JSValueIsObject(context, value)) {
        id primitive;
        if (JSValueIsBoolean(context, value))
            primitive = JSValueToBoolean(context, value) ? @YES : @NO;
        else if (JSValueIsNumber(context, value)) {
            // Normalize the number, so it will unique correctly in the hash map -
            // it's nicer not to leak this internal implementation detail!
            value = JSValueMakeNumber(context, JSValueToNumber(context, value, 0));
            primitive = [NSNumber numberWithDouble:JSValueToNumber(context, value, 0)];
        } else if (JSValueIsString(context, value)) {
            // Would be nice to unique strings, too.
            JSStringRef jsstring = JSValueToStringCopy(context, value, 0);
            NSString * stringNS = (NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring);
            JSStringRelease(jsstring);
            primitive = [stringNS autorelease];
        } else if (JSValueIsNull(context, value))
            primitive = [NSNull null];
        else {
            ASSERT(JSValueIsUndefined(context, value));
            primitive = nil;
        }
        return (JSContainerConvertor::Task){ value, primitive, ContainerNone };
    }

    JSObjectRef object = JSValueToObject(context, value, 0);

    if (id wrapped = tryUnwrapObjcObject(context, object))
        return (JSContainerConvertor::Task){ object, wrapped, ContainerNone };

    if (isDate(object, context))
        return (JSContainerConvertor::Task){ object, [NSDate dateWithTimeIntervalSince1970:JSValueToNumber(context, object, 0)], ContainerNone };

    if (isArray(object, context))
        return (JSContainerConvertor::Task){ object, [NSMutableArray array], ContainerArray };

    return (JSContainerConvertor::Task){ object, [NSMutableDictionary dictionary], ContainerDictionary };
}

static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task)
{
    ASSERT(task.type != ContainerNone);
    JSContainerConvertor convertor(context);
    convertor.add(task);
    ASSERT(!convertor.isWorkListEmpty());
    
    do {
        JSContainerConvertor::Task current = task = convertor.take();
        ASSERT(JSValueIsObject(context, current.js));
        JSObjectRef js = JSValueToObject(context, current.js, 0);

        if (current.type == ContainerArray) {
            ASSERT([current.objc isKindOfClass:[NSMutableArray class]]);
            NSMutableArray *array = (NSMutableArray *)current.objc;
        
            JSStringRef lengthString = JSStringCreateWithUTF8CString("length");
            unsigned length = JSC::toUInt32(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString, 0), 0));
            JSStringRelease(lengthString);

            for (unsigned i = 0; i < length; ++i) {
                id objc = convertor.convert(JSObjectGetPropertyAtIndex(context, js, i, 0));
                [array addObject:objc ? objc : [NSNull null]];
            }
        } else {
            ASSERT([current.objc isKindOfClass:[NSMutableDictionary class]]);
            NSMutableDictionary *dictionary = (NSMutableDictionary *)current.objc;

            JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js);
            size_t length = JSPropertyNameArrayGetCount(propertyNameArray);

            for (size_t i = 0; i < length; ++i) {
                JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNameArray, i);
                if (id objc = convertor.convert(JSObjectGetProperty(context, js, propertyName, 0)))
                    dictionary[[(NSString *)JSStringCopyCFString(kCFAllocatorDefault, propertyName) autorelease]] = objc;
            }

            JSPropertyNameArrayRelease(propertyNameArray);
        }

    } while (!convertor.isWorkListEmpty());

    return task.objc;
}

id valueToObject(JSContext *context, JSValueRef value)
{
713
    JSContainerConvertor::Task result = valueToObjectWithoutCopy([context globalContextRef], value);
714 715
    if (result.type == ContainerNone)
        return result.objc;
716
    return containerValueToObject([context globalContextRef], result);
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732
}

id valueToNumber(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
{
    ASSERT(!*exception);
    if (id wrapped = tryUnwrapObjcObject(context, value)) {
        if ([wrapped isKindOfClass:[NSNumber class]])
            return wrapped;
    }

    if (JSValueIsBoolean(context, value))
        return JSValueToBoolean(context, value) ? @YES : @NO;

    double result = JSValueToNumber(context, value, exception);
    return [NSNumber numberWithDouble:*exception ? std::numeric_limits<double>::quiet_NaN() : result];
}
733

734 735 736 737 738 739 740 741 742 743 744 745 746 747
id valueToString(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
{
    ASSERT(!*exception);
    if (id wrapped = tryUnwrapObjcObject(context, value)) {
        if ([wrapped isKindOfClass:[NSString class]])
            return wrapped;
    }

    JSStringRef jsstring = JSValueToStringCopy(context, value, exception);
    if (*exception) {
        ASSERT(!jsstring);
        return nil;
    }

748
    NSString *stringNS = [(NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsstring) autorelease];
749 750 751
    JSStringRelease(jsstring);
    return stringNS;
}
752

753 754 755 756 757 758 759 760 761 762 763
id valueToDate(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
{
    ASSERT(!*exception);
    if (id wrapped = tryUnwrapObjcObject(context, value)) {
        if ([wrapped isKindOfClass:[NSDate class]])
            return wrapped;
    }

    double result = JSValueToNumber(context, value, exception);
    return *exception ? nil : [NSDate dateWithTimeIntervalSince1970:result];
}
764

765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
id valueToArray(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
{
    ASSERT(!*exception);
    if (id wrapped = tryUnwrapObjcObject(context, value)) {
        if ([wrapped isKindOfClass:[NSArray class]])
            return wrapped;
    }

    if (JSValueIsObject(context, value))
        return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableArray array], ContainerArray});

    if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value)))
        *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSArray"));
    return nil;
}
780

781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810
id valueToDictionary(JSGlobalContextRef context, JSValueRef value, JSValueRef* exception)
{
    ASSERT(!*exception);
    if (id wrapped = tryUnwrapObjcObject(context, value)) {
        if ([wrapped isKindOfClass:[NSDictionary class]])
            return wrapped;
    }

    if (JSValueIsObject(context, value))
        return containerValueToObject(context, (JSContainerConvertor::Task){ value, [NSMutableDictionary dictionary], ContainerDictionary});

    if (!(JSValueIsNull(context, value) || JSValueIsUndefined(context, value)))
        *exception = toRef(JSC::createTypeError(toJS(context), "Cannot convert primitive to NSDictionary"));
    return nil;
}

class ObjcContainerConvertor {
public:
    struct Task {
        id objc;
        JSValueRef js;
        ConversionType type;
    };

    ObjcContainerConvertor(JSContext *context)
        : m_context(context)
    {
    }

    JSValueRef convert(id object);
811
    void add(Task);
812
    Task take();
813
    bool isWorkListEmpty() const { return !m_worklist.size(); }
814 815 816

private:
    JSContext *m_context;
817 818
    HashMap<id, JSValueRef> m_objectMap;
    Vector<Task> m_worklist;
819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
};

JSValueRef ObjcContainerConvertor::convert(id object)
{
    ASSERT(object);

    auto it = m_objectMap.find(object);
    if (it != m_objectMap.end())
        return it->value;

    ObjcContainerConvertor::Task task = objectToValueWithoutCopy(m_context, object);
    add(task);
    return task.js;
}

void ObjcContainerConvertor::add(ObjcContainerConvertor::Task task)
{
    m_objectMap.add(task.objc, task.js);
    if (task.type != ContainerNone)
        m_worklist.append(task);
}

ObjcContainerConvertor::Task ObjcContainerConvertor::take()
{
    ASSERT(!isWorkListEmpty());
    Task last = m_worklist.last();
    m_worklist.removeLast();
    return last;
}

849 850 851 852 853 854 855 856
inline bool isNSBoolean(id object)
{
    ASSERT([@YES class] == [@NO class]);
    ASSERT([@YES class] != [NSNumber class]);
    ASSERT([[@YES class] isSubclassOfClass:[NSNumber class]]);
    return [object isKindOfClass:[@YES class]];
}

857 858
static ObjcContainerConvertor::Task objectToValueWithoutCopy(JSContext *context, id object)
{
859
    JSGlobalContextRef contextRef = [context globalContextRef];
860

861
    if (!object)
862
        return (ObjcContainerConvertor::Task){ object, JSValueMakeUndefined(contextRef), ContainerNone };
863 864 865

    if (!class_conformsToProtocol(object_getClass(object), getJSExportProtocol())) {
        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 875 876 877 878

        if ([object isKindOfClass:[JSValue class]])
            return (ObjcContainerConvertor::Task){ object, ((JSValue *)object)->m_value, ContainerNone };

        if ([object isKindOfClass:[NSString class]]) {
            JSStringRef string = JSStringCreateWithCFString((CFStringRef)object);
879
            JSValueRef js = JSValueMakeString(contextRef, string);
880 881 882 883 884
            JSStringRelease(string);
            return (ObjcContainerConvertor::Task){ object, js, ContainerNone };
        }

        if ([object isKindOfClass:[NSNumber class]]) {
885 886 887
            if (isNSBoolean(object))
                return (ObjcContainerConvertor::Task){ object, JSValueMakeBoolean(contextRef, [object boolValue]), ContainerNone };
            return (ObjcContainerConvertor::Task){ object, JSValueMakeNumber(contextRef, [object doubleValue]), ContainerNone };
888 889 890
        }

        if ([object isKindOfClass:[NSDate class]]) {
891 892
            JSValueRef argument = JSValueMakeNumber(contextRef, [object timeIntervalSince1970]);
            JSObjectRef result = JSObjectMakeDate(contextRef, 1, &argument, 0);
893 894
            return (ObjcContainerConvertor::Task){ object, result, ContainerNone };
        }
895 896 897 898 899 900 901

        if ([object isKindOfClass:[JSManagedValue class]]) {
            JSValue *value = [static_cast<JSManagedValue *>(object) value];
            if (!value)
                return (ObjcContainerConvertor::Task) { object, JSValueMakeUndefined(contextRef), ContainerNone };
            return (ObjcContainerConvertor::Task){ object, value->m_value, ContainerNone };
        }
902 903
    }

904
    return (ObjcContainerConvertor::Task){ object, valueInternalValue([context wrapperForObjCObject:object]), ContainerNone };
905 906 907 908
}

JSValueRef objectToValue(JSContext *context, id object)
{
909
    JSGlobalContextRef contextRef = [context globalContextRef];
910

911 912 913 914 915 916 917 918 919 920
    ObjcContainerConvertor::Task task = objectToValueWithoutCopy(context, object);
    if (task.type == ContainerNone)
        return task.js;

    ObjcContainerConvertor convertor(context);
    convertor.add(task);
    ASSERT(!convertor.isWorkListEmpty());

    do {
        ObjcContainerConvertor::Task current = convertor.take();
921 922
        ASSERT(JSValueIsObject(contextRef, current.js));
        JSObjectRef js = JSValueToObject(contextRef, current.js, 0);
923 924 925

        if (current.type == ContainerArray) {
            ASSERT([current.objc isKindOfClass:[NSArray class]]);
926
            NSArray *array = (NSArray *)current.objc;
927 928
            NSUInteger count = [array count];
            for (NSUInteger index = 0; index < count; ++index)
929
                JSObjectSetPropertyAtIndex(contextRef, js, index, convertor.convert([array objectAtIndex:index]), 0);
930 931 932
        } else {
            ASSERT(current.type == ContainerDictionary);
            ASSERT([current.objc isKindOfClass:[NSDictionary class]]);
933
            NSDictionary *dictionary = (NSDictionary *)current.objc;
934
            for (id key in [dictionary keyEnumerator]) {
935 936
                if ([key isKindOfClass:[NSString class]]) {
                    JSStringRef propertyName = JSStringCreateWithCFString((CFStringRef)key);
937
                    JSObjectSetProperty(contextRef, js, propertyName, convertor.convert([dictionary objectForKey:key]), 0, 0);
938 939 940 941 942 943 944 945 946 947 948 949 950 951 952
                    JSStringRelease(propertyName);
                }
            }
        }
        
    } while (!convertor.isWorkListEmpty());

    return task.js;
}

JSValueRef valueInternalValue(JSValue * value)
{
    return value->m_value;
}

953
+ (JSValue *)valueWithValue:(JSValueRef)value inContext:(JSContext *)context
954
{