2  * Copyright (C) 2013 Apple Inc. All rights reserved.
 
   4  * Redistribution and use in source and binary forms, with or without
 
   5  * modification, are permitted provided that the following conditions
 
   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.
 
  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. 
 
  26 #import <JavaScriptCore/JavaScriptCore.h>
 
  28 extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef);
 
  30 extern "C" bool _Block_has_signature(id);
 
  31 extern "C" const char * _Block_signature(id);
 
  34 extern "C" void testObjectiveCAPI(void);
 
  36 #if JSC_OBJC_API_ENABLED
 
  38 @protocol ParentObject <JSExport>
 
  41 @interface ParentObject : NSObject<ParentObject>
 
  42 + (NSString *)parentTest;
 
  45 @implementation ParentObject
 
  46 + (NSString *)parentTest
 
  48     return [self description];
 
  52 @protocol TestObject <JSExport>
 
  53 @property int variable;
 
  54 @property (readonly) int six;
 
  55 @property CGPoint point;
 
  56 + (NSString *)classTest;
 
  57 + (NSString *)parentTest;
 
  58 - (NSString *)getString;
 
  59 JSExportAs(testArgumentTypes,
 
  60 - (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
 
  62 - (void)callback:(JSValue *)function;
 
  63 - (void)bogusCallback:(void(^)(int))function;
 
  66 @interface TestObject : ParentObject <TestObject>
 
  71 @implementation TestObject
 
  77     return [[TestObject alloc] init];
 
  79 + (NSString *)classTest
 
  81     return @"classTest - okay";
 
  83 - (NSString *)getString
 
  87 - (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
 
  89     return [NSString stringWithFormat:@"%d,%g,%d,%@,%d,%@,%@", i, d, b==YES?true:false,s,[n intValue],a[1],o[@"x"]];
 
  91 - (void)callback:(JSValue *)function
 
  93     [function callWithArguments:[NSArray arrayWithObject:[NSNumber numberWithInt:42]]];
 
  95 - (void)bogusCallback:(void(^)(int))function
 
 101 bool testXYZTested = false;
 
 103 @protocol TextXYZ <JSExport>
 
 105 @property (readonly) int y;
 
 106 @property (assign) JSValue *onclick;
 
 107 @property (assign) JSValue *weakOnclick;
 
 108 - (void)test:(NSString *)message;
 
 111 @interface TextXYZ : NSObject <TextXYZ>
 
 118 @implementation TextXYZ {
 
 119     JSManagedValue *m_weakOnclickHandler;
 
 120     JSManagedValue *m_onclickHandler;
 
 125 - (void)test:(NSString *)message
 
 127     testXYZTested = [message isEqual:@"test"] && x == 13 & y == 4 && z == 5;
 
 129 - (void)setWeakOnclick:(JSValue *)value
 
 131     m_weakOnclickHandler = [JSManagedValue managedValueWithValue:value];
 
 134 - (void)setOnclick:(JSValue *)value
 
 136     m_onclickHandler = [JSManagedValue managedValueWithValue:value];
 
 137     [value.context.virtualMachine addManagedReference:m_onclickHandler withOwner:self];
 
 139 - (JSValue *)weakOnclick
 
 141     return [m_weakOnclickHandler value];
 
 145     return [m_onclickHandler value];
 
 149     if (!m_onclickHandler)
 
 152     JSValue *function = [m_onclickHandler value];
 
 153     [function callWithArguments:[NSArray array]];
 
 157     [[m_onclickHandler value].context.virtualMachine removeManagedReference:m_onclickHandler withOwner:self];
 
 163 @protocol TinyDOMNode <JSExport>
 
 164 - (void)appendChild:(TinyDOMNode *)child;
 
 165 - (NSUInteger)numberOfChildren;
 
 166 - (TinyDOMNode *)childAtIndex:(NSUInteger)index;
 
 167 - (void)removeChildAtIndex:(NSUInteger)index;
 
 170 @interface TinyDOMNode : NSObject<TinyDOMNode>
 
 171 + (JSVirtualMachine *)sharedVirtualMachine;
 
 172 + (void)clearSharedVirtualMachine;
 
 175 @implementation TinyDOMNode {
 
 176     NSMutableArray *m_children;
 
 179 static JSVirtualMachine *sharedInstance = nil;
 
 181 + (JSVirtualMachine *)sharedVirtualMachine
 
 184         sharedInstance = [[JSVirtualMachine alloc] init];
 
 185     return sharedInstance;
 
 188 + (void)clearSharedVirtualMachine
 
 190     sharedInstance = nil;
 
 199     m_children = [[NSMutableArray alloc] initWithCapacity:0];
 
 206     NSEnumerator *enumerator = [m_children objectEnumerator];
 
 208     while ((nextChild = [enumerator nextObject]))
 
 209         [[TinyDOMNode sharedVirtualMachine] removeManagedReference:nextChild withOwner:self];
 
 211 #if !__has_feature(objc_arc)
 
 216 - (void)appendChild:(TinyDOMNode *)child
 
 218     [[TinyDOMNode sharedVirtualMachine] addManagedReference:child withOwner:self];
 
 219     [m_children addObject:child];
 
 222 - (NSUInteger)numberOfChildren
 
 224     return [m_children count];
 
 227 - (TinyDOMNode *)childAtIndex:(NSUInteger)index
 
 229     if (index >= [m_children count])
 
 231     return [m_children objectAtIndex:index];
 
 234 - (void)removeChildAtIndex:(NSUInteger)index
 
 236     if (index >= [m_children count])
 
 238     [[TinyDOMNode sharedVirtualMachine] removeManagedReference:[m_children objectAtIndex:index] withOwner:self];
 
 239     [m_children removeObjectAtIndex:index];
 
 244 static void checkResult(NSString *description, bool passed)
 
 246     NSLog(@"TEST: \"%@\": %@", description, passed ? @"PASSED" : @"FAILED");
 
 251 static bool blockSignatureContainsClass()
 
 253     static bool containsClass = ^{
 
 254         id block = ^(NSString *string){ return string; };
 
 255         return _Block_has_signature(block) && strstr(_Block_signature(block), "NSString");
 
 257     return containsClass;
 
 260 void testObjectiveCAPI()
 
 262     NSLog(@"Testing Objective-C API");
 
 265         JSContext *context = [[JSContext alloc] init];
 
 266         JSValue *result = [context evaluateScript:@"2 + 2"];
 
 267         checkResult(@"2 + 2", [result isNumber] && [result toInt32] == 4);
 
 271         JSContext *context = [[JSContext alloc] init];
 
 272         NSString *result = [NSString stringWithFormat:@"Two plus two is %@", [context evaluateScript:@"2 + 2"]];
 
 273         checkResult(@"stringWithFormat", [result isEqual:@"Two plus two is 4"]);
 
 277         JSContext *context = [[JSContext alloc] init];
 
 278         context[@"message"] = @"Hello";
 
 279         JSValue *result = [context evaluateScript:@"message + ', World!'"];
 
 280         checkResult(@"Hello, World!", [result isString] && [result isEqualToObject:@"Hello, World!"]);
 
 284         JSContext *context = [[JSContext alloc] init];
 
 285         JSValue *result = [context evaluateScript:@"({ x:42 })"];
 
 286         checkResult(@"({ x:42 })", [result isObject] && [result[@"x"] isEqualToObject:@42]);
 
 287         id obj = [result toObject];
 
 288         checkResult(@"Check dictionary literal", [obj isKindOfClass:[NSDictionary class]]);
 
 289         id num = (NSDictionary *)obj[@"x"];
 
 290         checkResult(@"Check numeric literal", [num isKindOfClass:[NSNumber class]]);
 
 294         JSContext *context = [[JSContext alloc] init];
 
 296         context[@"blockCallback"] = ^(int value){
 
 299         [context evaluateScript:@"blockCallback(42)"];
 
 300         checkResult(@"blockCallback", result == 42);
 
 303     if (blockSignatureContainsClass()) {
 
 305             JSContext *context = [[JSContext alloc] init];
 
 306             __block bool result = false;
 
 307             context[@"blockCallback"] = ^(NSString *value){
 
 308                 result = [@"42" isEqualToString:value] == YES;
 
 310             [context evaluateScript:@"blockCallback(42)"];
 
 311             checkResult(@"blockCallback(NSString *)", result);
 
 314         NSLog(@"Skipping 'blockCallback(NSString *)' test case");
 
 317         JSContext *context = [[JSContext alloc] init];
 
 318         checkResult(@"!context.exception", !context.exception);
 
 319         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
 
 320         checkResult(@"context.exception", context.exception);
 
 324         JSContext *context = [[JSContext alloc] init];
 
 325         __block bool caught = false;
 
 326         context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
 
 331         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
 
 332         checkResult(@"JSContext.exceptionHandler", caught);
 
 336         JSContext *context = [[JSContext alloc] init];
 
 337         context[@"callback"] = ^{
 
 338             JSContext *context = [JSContext currentContext];
 
 339             context.exception = [JSValue valueWithNewErrorFromMessage:@"Something went wrong." inContext:context];
 
 341         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
 
 342         checkResult(@"Explicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
 
 343         checkResult(@"Explicit throw in callback - not thrown to Objective-C", !context.exception);
 
 347         JSContext *context = [[JSContext alloc] init];
 
 348         context[@"callback"] = ^{
 
 349             JSContext *context = [JSContext currentContext];
 
 350             [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
 
 352         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
 
 353         checkResult(@"Implicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
 
 354         checkResult(@"Implicit throw in callback - not thrown to Objective-C", !context.exception);
 
 358         JSContext *context = [[JSContext alloc] init];
 
 359         [context evaluateScript:
 
 360             @"function sum(array) { \
 
 362                 for (var i in array) \
 
 363                     result += array[i]; \
 
 366         JSValue *array = [JSValue valueWithObject:@[@13, @2, @7] inContext:context];
 
 367         JSValue *sumFunction = context[@"sum"];
 
 368         JSValue *result = [sumFunction callWithArguments:@[ array ]];
 
 369         checkResult(@"sum([13, 2, 7])", [result toInt32] == 22);
 
 373         JSContext *context = [[JSContext alloc] init];
 
 374         JSValue *mulAddFunction = [context evaluateScript:
 
 375             @"(function(array, object) { \
 
 377                 for (var i in array) \
 
 378                     result.push(array[i] * object.x + object.y); \
 
 381         JSValue *result = [mulAddFunction callWithArguments:@[ @[ @2, @4, @8 ], @{ @"x":@0.5, @"y":@42 } ]];
 
 382         checkResult(@"mulAddFunction", [result isObject] && [[result toString] isEqual:@"43,44,46"]);
 
 386         JSContext *context = [[JSContext alloc] init];        
 
 387         JSValue *array = [JSValue valueWithNewArrayInContext:context];
 
 388         checkResult(@"arrayLengthEmpty", [[array[@"length"] toNumber] unsignedIntegerValue] == 0);
 
 389         JSValue *value1 = [JSValue valueWithInt32:42 inContext:context];
 
 390         JSValue *value2 = [JSValue valueWithInt32:24 inContext:context];
 
 391         NSUInteger lowIndex = 5;
 
 392         NSUInteger maxLength = UINT_MAX;
 
 394         [array setValue:value1 atIndex:lowIndex];
 
 395         checkResult(@"array.length after put to low index", [[array[@"length"] toNumber] unsignedIntegerValue] == (lowIndex + 1));
 
 397         [array setValue:value1 atIndex:(maxLength - 1)];
 
 398         checkResult(@"array.length after put to maxLength - 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
 
 400         [array setValue:value2 atIndex:maxLength];
 
 401         checkResult(@"array.length after put to maxLength", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
 
 403         [array setValue:value2 atIndex:(maxLength + 1)];
 
 404         checkResult(@"array.length after put to maxLength + 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
 
 406         if (sizeof(NSUInteger) == 8)
 
 407             checkResult(@"valueAtIndex:0 is undefined", [[array valueAtIndex:0] isUndefined]);
 
 409             checkResult(@"valueAtIndex:0", [[array valueAtIndex:0] toInt32] == 24);
 
 410         checkResult(@"valueAtIndex:lowIndex", [[array valueAtIndex:lowIndex] toInt32] == 42);
 
 411         checkResult(@"valueAtIndex:maxLength - 1", [[array valueAtIndex:(maxLength - 1)] toInt32] == 42);
 
 412         checkResult(@"valueAtIndex:maxLength", [[array valueAtIndex:maxLength] toInt32] == 24);
 
 413         checkResult(@"valueAtIndex:maxLength + 1", [[array valueAtIndex:(maxLength + 1)] toInt32] == 24);
 
 417         JSContext *context = [[JSContext alloc] init];
 
 418         JSValue *object = [JSValue valueWithNewObjectInContext:context];
 
 420         object[@"point"] = @{ @"x":@1, @"y":@2 };
 
 421         object[@"point"][@"x"] = @3;
 
 422         CGPoint point = [object[@"point"] toPoint];
 
 423         checkResult(@"toPoint", point.x == 3 && point.y == 2);
 
 425         object[@{ @"toString":^{ return @"foo"; } }] = @"bar";
 
 426         checkResult(@"toString in object literal used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
 
 428         object[[@"foobar" substringToIndex:3]] = @"bar";
 
 429         checkResult(@"substring used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
 
 433         JSContext *context = [[JSContext alloc] init];
 
 434         context[@"handleTheDictionary"] = ^(NSDictionary *dict) {
 
 435             NSDictionary *expectedDict = @{
 
 436                 @"foo" : [NSNumber numberWithInt:1],
 
 438                     @"baz": [NSNumber numberWithInt:2]
 
 441             checkResult(@"recursively convert nested dictionaries", [dict isEqualToDictionary:expectedDict]);
 
 443         [context evaluateScript:@"var myDict = { \
 
 447         handleTheDictionary(myDict);"];
 
 449         context[@"handleTheArray"] = ^(NSArray *array) {
 
 450             NSArray *expectedArray = @[@"foo", @"bar", @[@"baz"]];
 
 451             checkResult(@"recursively convert nested arrays", [array isEqualToArray:expectedArray]);
 
 453         [context evaluateScript:@"var myArray = ['foo', 'bar', ['baz']]; handleTheArray(myArray);"];
 
 457         JSContext *context = [[JSContext alloc] init];
 
 458         TextXYZ *testXYZ = [[TextXYZ alloc] init];
 
 459         context[@"testXYZ"] = testXYZ;
 
 463         [context evaluateScript:@"testXYZ.x = 13; testXYZ.y = 14;"];
 
 464         [context evaluateScript:@"testXYZ.test('test')"];
 
 465         checkResult(@"TextXYZ - testXYZTested", testXYZTested);
 
 466         JSValue *result = [context evaluateScript:@"testXYZ.x + ',' + testXYZ.y + ',' + testXYZ.z"];
 
 467         checkResult(@"TextXYZ - result", [result isEqualToObject:@"13,4,undefined"]);
 
 471         JSContext *context = [[JSContext alloc] init];
 
 472         [context[@"Object"][@"prototype"] defineProperty:@"getterProperty" descriptor:@{
 
 473             JSPropertyDescriptorGetKey:^{
 
 474                 return [JSContext currentThis][@"x"];
 
 477         JSValue *object = [JSValue valueWithObject:@{ @"x":@101 } inContext:context];
 
 478         int result = [object [@"getterProperty"] toInt32];
 
 479         checkResult(@"getterProperty", result == 101);
 
 483         JSContext *context = [[JSContext alloc] init];
 
 484         context[@"concatenate"] = ^{
 
 485             NSArray *arguments = [JSContext currentArguments];
 
 486             if (![arguments count])
 
 488             NSString *message = [arguments[0] description];
 
 489             for (NSUInteger index = 1; index < [arguments count]; ++index)
 
 490                 message = [NSString stringWithFormat:@"%@ %@", message, arguments[index]];
 
 493         JSValue *result = [context evaluateScript:@"concatenate('Hello,', 'World!')"];
 
 494         checkResult(@"concatenate", [result isEqualToObject:@"Hello, World!"]);
 
 498         JSContext *context = [[JSContext alloc] init];
 
 499         context[@"foo"] = @YES;
 
 500         checkResult(@"@YES is boolean", [context[@"foo"] isBoolean]);
 
 501         JSValue *result = [context evaluateScript:@"typeof foo"];
 
 502         checkResult(@"@YES is boolean", [result isEqualToObject:@"boolean"]);
 
 506         JSContext *context = [[JSContext alloc] init];
 
 507         TestObject* testObject = [TestObject testObject];
 
 508         context[@"testObject"] = testObject;
 
 509         JSValue *result = [context evaluateScript:@"String(testObject)"];
 
 510         checkResult(@"String(testObject)", [result isEqualToObject:@"[object TestObject]"]);
 
 514         JSContext *context = [[JSContext alloc] init];
 
 515         TestObject* testObject = [TestObject testObject];
 
 516         context[@"testObject"] = testObject;
 
 517         JSValue *result = [context evaluateScript:@"String(testObject.__proto__)"];
 
 518         checkResult(@"String(testObject.__proto__)", [result isEqualToObject:@"[object TestObjectPrototype]"]);
 
 522         JSContext *context = [[JSContext alloc] init];
 
 523         context[@"TestObject"] = [TestObject class];
 
 524         JSValue *result = [context evaluateScript:@"String(TestObject)"];
 
 525         checkResult(@"String(TestObject)", [result isEqualToObject:@"[object TestObjectConstructor]"]);
 
 529         JSContext *context = [[JSContext alloc] init];
 
 530         JSValue* value = [JSValue valueWithObject:[TestObject class] inContext:context];
 
 531         checkResult(@"[value toObject] == [TestObject class]", [value toObject] == [TestObject class]);
 
 535         JSContext *context = [[JSContext alloc] init];
 
 536         context[@"TestObject"] = [TestObject class];
 
 537         JSValue *result = [context evaluateScript:@"TestObject.parentTest()"];
 
 538         checkResult(@"TestObject.parentTest()", [result isEqualToObject:@"TestObject"]);
 
 542         JSContext *context = [[JSContext alloc] init];
 
 543         TestObject* testObject = [TestObject testObject];
 
 544         context[@"testObjectA"] = testObject;
 
 545         context[@"testObjectB"] = testObject;
 
 546         JSValue *result = [context evaluateScript:@"testObjectA == testObjectB"];
 
 547         checkResult(@"testObjectA == testObjectB", [result isBoolean] && [result toBool]);
 
 551         JSContext *context = [[JSContext alloc] init];
 
 552         TestObject* testObject = [TestObject testObject];
 
 553         context[@"testObject"] = testObject;
 
 554         testObject.point = (CGPoint){3,4};
 
 555         JSValue *result = [context evaluateScript:@"var result = JSON.stringify(testObject.point); testObject.point = {x:12,y:14}; result"];
 
 556         checkResult(@"testObject.point - result", [result isEqualToObject:@"{\"x\":3,\"y\":4}"]);
 
 557         checkResult(@"testObject.point - {x:12,y:14}", testObject.point.x == 12 && testObject.point.y == 14);
 
 561         JSContext *context = [[JSContext alloc] init];
 
 562         TestObject* testObject = [TestObject testObject];
 
 564         context[@"testObject"] = testObject;
 
 565         context[@"mul"] = ^(int x, int y){ return x * y; };
 
 566         JSValue *result = [context evaluateScript:@"mul(testObject.six, 7)"];
 
 567         checkResult(@"mul(testObject.six, 7)", [result isNumber] && [result toInt32] == 42);
 
 571         JSContext *context = [[JSContext alloc] init];
 
 572         TestObject* testObject = [TestObject testObject];
 
 573         context[@"testObject"] = testObject;
 
 574         context[@"testObject"][@"variable"] = @4;
 
 575         [context evaluateScript:@"++testObject.variable"];
 
 576         checkResult(@"++testObject.variable", testObject.variable == 5);
 
 580         JSContext *context = [[JSContext alloc] init];
 
 581         context[@"point"] = @{ @"x":@6, @"y":@7 };
 
 582         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
 
 583         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
 
 587         JSContext *context = [[JSContext alloc] init];
 
 588         context[@"point"] = @{ @"x":@6, @"y":@7 };
 
 589         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
 
 590         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
 
 594         JSContext *context = [[JSContext alloc] init];
 
 595         TestObject* testObject = [TestObject testObject];
 
 596         context[@"testObject"] = testObject;
 
 597         JSValue *result = [context evaluateScript:@"testObject.getString()"];
 
 598         checkResult(@"testObject.getString()", [result isString] && [result toInt32] == 42);
 
 602         JSContext *context = [[JSContext alloc] init];
 
 603         TestObject* testObject = [TestObject testObject];
 
 604         context[@"testObject"] = testObject;
 
 605         JSValue *result = [context evaluateScript:@"testObject.testArgumentTypes(101,0.5,true,'foo',666,[false,'bar',false],{x:'baz'})"];
 
 606         checkResult(@"testObject.testArgumentTypes", [result isEqualToObject:@"101,0.5,1,foo,666,bar,baz"]);
 
 610         JSContext *context = [[JSContext alloc] init];
 
 611         TestObject* testObject = [TestObject testObject];
 
 612         context[@"testObject"] = testObject;
 
 613         JSValue *result = [context evaluateScript:@"testObject.getString.call(testObject)"];
 
 614         checkResult(@"testObject.getString.call(testObject)", [result isString] && [result toInt32] == 42);
 
 618         JSContext *context = [[JSContext alloc] init];
 
 619         TestObject* testObject = [TestObject testObject];
 
 620         context[@"testObject"] = testObject;
 
 621         checkResult(@"testObject.getString.call({}) pre", !context.exception);
 
 622         [context evaluateScript:@"testObject.getString.call({})"];
 
 623         checkResult(@"testObject.getString.call({}) post", context.exception);
 
 627         JSContext *context = [[JSContext alloc] init];
 
 628         TestObject* testObject = [TestObject testObject];
 
 629         context[@"testObject"] = testObject;
 
 630         JSValue *result = [context evaluateScript:@"var result = 0; testObject.callback(function(x){ result = x; }); result"];
 
 631         checkResult(@"testObject.callback", [result isNumber] && [result toInt32] == 42);
 
 632         result = [context evaluateScript:@"testObject.bogusCallback"];
 
 633         checkResult(@"testObject.bogusCallback == undefined", [result isUndefined]);
 
 637         JSContext *context = [[JSContext alloc] init];
 
 638         TestObject *testObject = [TestObject testObject];
 
 639         context[@"testObject"] = testObject;
 
 640         JSValue *result = [context evaluateScript:@"Function.prototype.toString.call(testObject.callback)"];
 
 641         checkResult(@"Function.prototype.toString", !context.exception && ![result isUndefined]);
 
 645         JSContext *context1 = [[JSContext alloc] init];
 
 646         JSContext *context2 = [[JSContext alloc] initWithVirtualMachine:context1.virtualMachine];
 
 647         JSValue *value = [JSValue valueWithDouble:42 inContext:context2];
 
 648         context1[@"passValueBetweenContexts"] = value;
 
 649         JSValue *result = [context1 evaluateScript:@"passValueBetweenContexts"];
 
 650         checkResult(@"[value isEqualToObject:result]", [value isEqualToObject:result]);
 
 654         JSContext *context = [[JSContext alloc] init];
 
 655         context[@"handleTheDictionary"] = ^(NSDictionary *dict) {
 
 656             NSDictionary *expectedDict = @{
 
 657                 @"foo" : [NSNumber numberWithInt:1],
 
 659                     @"baz": [NSNumber numberWithInt:2]
 
 662             checkResult(@"recursively convert nested dictionaries", [dict isEqualToDictionary:expectedDict]);
 
 664         [context evaluateScript:@"var myDict = { \
 
 668         handleTheDictionary(myDict);"];
 
 670         context[@"handleTheArray"] = ^(NSArray *array) {
 
 671             NSArray *expectedArray = @[@"foo", @"bar", @[@"baz"]];
 
 672             checkResult(@"recursively convert nested arrays", [array isEqualToArray:expectedArray]);
 
 674         [context evaluateScript:@"var myArray = ['foo', 'bar', ['baz']]; handleTheArray(myArray);"];
 
 678         JSContext *context = [[JSContext alloc] init];
 
 679         TestObject *testObject = [TestObject testObject];
 
 681             context[@"testObject"] = testObject;
 
 682             [context evaluateScript:@"var constructor = Object.getPrototypeOf(testObject).constructor; constructor.prototype = undefined;"];
 
 683             [context evaluateScript:@"testObject = undefined"];
 
 686         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
 
 689             context[@"testObject"] = testObject;
 
 694         JSContext *context = [[JSContext alloc] init];
 
 695         TextXYZ *testXYZ = [[TextXYZ alloc] init];
 
 698             context[@"testXYZ"] = testXYZ;
 
 700             [context evaluateScript:@" \
 
 702                 testXYZ.onclick = function() { \
 
 706                 testXYZ.weakOnclick = function() { \
 
 714             JSValue *result = [context evaluateScript:@"didClick"];
 
 715             checkResult(@"Event handler onclick", [result toBool]);
 
 718         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
 
 721             JSValue *result = [context evaluateScript:@"testXYZ.onclick"];
 
 722             checkResult(@"onclick still around after GC", !([result isNull] || [result isUndefined]));
 
 727             JSValue *result = [context evaluateScript:@"testXYZ.weakOnclick"];
 
 728             checkResult(@"weakOnclick not around after GC", [result isNull] || [result isUndefined]);
 
 732             [context evaluateScript:@" \
 
 738         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
 
 742             JSValue *result = [context evaluateScript:@"didClick"];
 
 743             checkResult(@"Event handler onclick doesn't fire", ![result toBool]);
 
 748         JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
 
 749         TestObject *testObject = [TestObject testObject];
 
 750         JSManagedValue *weakValue;
 
 752             JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
 
 753             context[@"testObject"] = testObject;
 
 754             weakValue = [[JSManagedValue alloc] initWithValue:context[@"testObject"]];
 
 758             JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
 
 759             context[@"testObject"] = testObject;
 
 760             JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
 
 761             checkResult(@"weak value == nil", ![weakValue value]);
 
 762             checkResult(@"root is still alive", ![context[@"testObject"] isUndefined]);
 
 767         JSVirtualMachine *vm = [TinyDOMNode sharedVirtualMachine];
 
 768         JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
 
 769         TinyDOMNode *root = [[TinyDOMNode alloc] init];
 
 770         TinyDOMNode *lastNode = root;
 
 771         for (NSUInteger i = 0; i < 3; i++) {
 
 772             TinyDOMNode *newNode = [[TinyDOMNode alloc] init];
 
 773             [lastNode appendChild:newNode];
 
 778             context[@"root"] = root;
 
 779             context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
 
 780                 TinyDOMNode *lastNode = nil;
 
 783                     head = [lastNode childAtIndex:0];
 
 787             [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
 
 790         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
 
 792         JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
 
 793         checkResult(@"My custom property == 42", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42);
 
 795         [TinyDOMNode clearSharedVirtualMachine];
 
 799         JSVirtualMachine *vm = [TinyDOMNode sharedVirtualMachine];
 
 800         JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
 
 801         TinyDOMNode *root = [[TinyDOMNode alloc] init];
 
 802         TinyDOMNode *lastNode = root;
 
 803         for (NSUInteger i = 0; i < 3; i++) {
 
 804             TinyDOMNode *newNode = [[TinyDOMNode alloc] init];
 
 805             [lastNode appendChild:newNode];
 
 810             context[@"root"] = root;
 
 811             context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
 
 812                 TinyDOMNode *lastNode = nil;
 
 815                     head = [lastNode childAtIndex:0];
 
 819             [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
 
 821             [root appendChild:[root childAtIndex:0]];
 
 822             [root removeChildAtIndex:0];
 
 825         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
 
 827         JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
 
 828         checkResult(@"duplicate calls to addManagedReference don't cause things to die", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42);
 
 830         [TinyDOMNode clearSharedVirtualMachine];
 
 834         JSContext *context = [[JSContext alloc] init];
 
 835         JSValue *o = [JSValue valueWithNewObjectInContext:context];
 
 837         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
 
 839         checkResult(@"JSValue correctly protected its internal value", [[o[@"foo"] toString] isEqualToString:@"foo"]);
 
 843         JSContext *context = [[JSContext alloc] init];
 
 844         TestObject *testObject = [TestObject testObject];
 
 845         context[@"testObject"] = testObject;
 
 846         [context evaluateScript:@"testObject.__lookupGetter__('variable').call({})"];
 
 847         checkResult(@"Make sure we throw an exception when calling getter on incorrect |this|", context.exception);
 
 851         TestObject *testObject = [TestObject testObject];
 
 852         JSManagedValue *managedTestObject;
 
 854             JSContext *context = [[JSContext alloc] init];
 
 855             context[@"testObject"] = testObject;
 
 856             managedTestObject = [JSManagedValue managedValueWithValue:context[@"testObject"]];
 
 857             [context.virtualMachine addManagedReference:managedTestObject withOwner:testObject];
 
 864 void testObjectiveCAPI()