3acdd0810d0cdbbef058355159d1ec1afe2abbf8
[WebKit-https.git] / Source / JavaScriptCore / API / tests / testapi.mm
1 /*
2  * Copyright (C) 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import <JavaScriptCore/JavaScriptCore.h>
27
28 extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef);
29
30 extern "C" bool _Block_has_signature(id);
31 extern "C" const char * _Block_signature(id);
32
33 extern int failed;
34 extern "C" void testObjectiveCAPI(void);
35
36 #if JSC_OBJC_API_ENABLED
37
38 @protocol ParentObject <JSExport>
39 @end
40
41 @interface ParentObject : NSObject<ParentObject>
42 + (NSString *)parentTest;
43 @end
44
45 @implementation ParentObject
46 + (NSString *)parentTest
47 {
48     return [self description];
49 }
50 @end
51
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
61 );
62 - (void)callback:(JSValue *)function;
63 - (void)bogusCallback:(void(^)(int))function;
64 @end
65
66 @interface TestObject : ParentObject <TestObject>
67 @property int six;
68 + (id)testObject;
69 @end
70
71 @implementation TestObject
72 @synthesize variable;
73 @synthesize six;
74 @synthesize point;
75 + (id)testObject
76 {
77     return [[TestObject alloc] init];
78 }
79 + (NSString *)classTest
80 {
81     return @"classTest - okay";
82 }
83 - (NSString *)getString
84 {
85     return @"42";
86 }
87 - (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
88 {
89     return [NSString stringWithFormat:@"%d,%g,%d,%@,%d,%@,%@", i, d, b==YES?true:false,s,[n intValue],a[1],o[@"x"]];
90 }
91 - (void)callback:(JSValue *)function
92 {
93     [function callWithArguments:[NSArray arrayWithObject:[NSNumber numberWithInt:42]]];
94 }
95 - (void)bogusCallback:(void(^)(int))function
96 {
97     function(42);
98 }
99 @end
100
101 bool testXYZTested = false;
102
103 @protocol TextXYZ <JSExport>
104 @property int x;
105 @property (readonly) int y;
106 @property JSValue *onclick;
107 @property JSValue *weakOnclick;
108 - (void)test:(NSString *)message;
109 @end
110
111 @interface TextXYZ : NSObject <TextXYZ>
112 @property int x;
113 @property int y;
114 @property int z;
115 - (void)click;
116 @end
117
118 @implementation TextXYZ {
119     JSManagedValue *m_weakOnclickHandler;
120     JSManagedValue *m_onclickHandler;
121 }
122 @synthesize x;
123 @synthesize y;
124 @synthesize z;
125 - (void)test:(NSString *)message
126 {
127     testXYZTested = [message isEqual:@"test"] && x == 13 & y == 4 && z == 5;
128 }
129 - (void)setWeakOnclick:(JSValue *)value
130 {
131     m_weakOnclickHandler = [JSManagedValue managedValueWithValue:value];
132 }
133
134 - (void)setOnclick:(JSValue *)value
135 {
136     m_onclickHandler = [JSManagedValue managedValueWithValue:value owner:self];
137 }
138 - (JSValue *)weakOnclick
139 {
140     return [m_weakOnclickHandler value];
141 }
142 - (JSValue *)onclick
143 {
144     return [m_onclickHandler value];
145 }
146 - (void)click
147 {
148     if (!m_onclickHandler)
149         return;
150
151     JSValue *function = [m_onclickHandler value];
152     [function callWithArguments:[NSArray array]];
153 }
154 @end
155
156 @class TinyDOMNode;
157
158 @protocol TinyDOMNode <JSExport>
159 - (void)appendChild:(TinyDOMNode *)child;
160 - (NSUInteger)numberOfChildren;
161 - (TinyDOMNode *)childAtIndex:(NSUInteger)index;
162 - (void)removeChildAtIndex:(NSUInteger)index;
163 @end
164
165 @interface TinyDOMNode : NSObject<TinyDOMNode>
166 + (JSVirtualMachine *)sharedVirtualMachine;
167 + (void)clearSharedVirtualMachine;
168 @end
169
170 @implementation TinyDOMNode {
171     NSMutableArray *m_children;
172 }
173
174 static JSVirtualMachine *sharedInstance = nil;
175
176 + (JSVirtualMachine *)sharedVirtualMachine
177 {
178     if (!sharedInstance)
179         sharedInstance = [[JSVirtualMachine alloc] init];
180     return sharedInstance;
181 }
182
183 + (void)clearSharedVirtualMachine
184 {
185     sharedInstance = nil;
186 }
187
188 - (id)init
189 {
190     self = [super init];
191     if (!self)
192         return nil;
193
194     m_children = [[NSMutableArray alloc] initWithCapacity:0];
195
196     return self;
197 }
198
199 - (void)dealloc
200 {
201     NSEnumerator *enumerator = [m_children objectEnumerator];
202     id nextChild;
203     while ((nextChild = [enumerator nextObject]))
204         [[TinyDOMNode sharedVirtualMachine] removeManagedReference:nextChild withOwner:self];
205
206 #if !__has_feature(objc_arc)
207     [super dealloc];
208 #endif
209 }
210
211 - (void)appendChild:(TinyDOMNode *)child
212 {
213     [[TinyDOMNode sharedVirtualMachine] addManagedReference:child withOwner:self];
214     [m_children addObject:child];
215 }
216
217 - (NSUInteger)numberOfChildren
218 {
219     return [m_children count];
220 }
221
222 - (TinyDOMNode *)childAtIndex:(NSUInteger)index
223 {
224     if (index >= [m_children count])
225         return nil;
226     return [m_children objectAtIndex:index];
227 }
228
229 - (void)removeChildAtIndex:(NSUInteger)index
230 {
231     if (index >= [m_children count])
232         return;
233     [[TinyDOMNode sharedVirtualMachine] removeManagedReference:[m_children objectAtIndex:index] withOwner:self];
234     [m_children removeObjectAtIndex:index];
235 }
236
237 @end
238
239 static void checkResult(NSString *description, bool passed)
240 {
241     NSLog(@"TEST: \"%@\": %@", description, passed ? @"PASSED" : @"FAILED");
242     if (!passed)
243         failed = 1;
244 }
245
246 static bool blockSignatureContainsClass()
247 {
248     static bool containsClass = ^{
249         id block = ^(NSString *string){ return string; };
250         return _Block_has_signature(block) && strstr(_Block_signature(block), "NSString");
251     }();
252     return containsClass;
253 }
254
255 void testObjectiveCAPI()
256 {
257     NSLog(@"Testing Objective-C API");
258
259     @autoreleasepool {
260         JSContext *context = [[JSContext alloc] init];
261         JSValue *result = [context evaluateScript:@"2 + 2"];
262         checkResult(@"2 + 2", [result isNumber] && [result toInt32] == 4);
263     }
264
265     @autoreleasepool {
266         JSContext *context = [[JSContext alloc] init];
267         NSString *result = [NSString stringWithFormat:@"Two plus two is %@", [context evaluateScript:@"2 + 2"]];
268         checkResult(@"stringWithFormat", [result isEqual:@"Two plus two is 4"]);
269     }
270
271     @autoreleasepool {
272         JSContext *context = [[JSContext alloc] init];
273         context[@"message"] = @"Hello";
274         JSValue *result = [context evaluateScript:@"message + ', World!'"];
275         checkResult(@"Hello, World!", [result isString] && [result isEqualToObject:@"Hello, World!"]);
276     }
277
278     @autoreleasepool {
279         JSContext *context = [[JSContext alloc] init];
280         JSValue *result = [context evaluateScript:@"({ x:42 })"];
281         checkResult(@"({ x:42 })", [result isObject] && [result[@"x"] isEqualToObject:@42]);
282         id obj = [result toObject];
283         checkResult(@"Check dictionary literal", [obj isKindOfClass:[NSDictionary class]]);
284         id num = (NSDictionary *)obj[@"x"];
285         checkResult(@"Check numeric literal", [num isKindOfClass:[NSNumber class]]);
286     }
287
288     @autoreleasepool {
289         JSContext *context = [[JSContext alloc] init];
290         __block int result;
291         context[@"blockCallback"] = ^(int value){
292             result = value;
293         };
294         [context evaluateScript:@"blockCallback(42)"];
295         checkResult(@"blockCallback", result == 42);
296     }
297
298     if (blockSignatureContainsClass()) {
299         @autoreleasepool {
300             JSContext *context = [[JSContext alloc] init];
301             __block bool result = false;
302             context[@"blockCallback"] = ^(NSString *value){
303                 result = [@"42" isEqualToString:value] == YES;
304             };
305             [context evaluateScript:@"blockCallback(42)"];
306             checkResult(@"blockCallback(NSString *)", result);
307         }
308     } else
309         NSLog(@"Skipping 'blockCallback(NSString *)' test case");
310
311     @autoreleasepool {
312         JSContext *context = [[JSContext alloc] init];
313         checkResult(@"!context.exception", !context.exception);
314         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
315         checkResult(@"context.exception", context.exception);
316     }
317
318     @autoreleasepool {
319         JSContext *context = [[JSContext alloc] init];
320         __block bool caught = false;
321         context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
322             (void)context;
323             (void)exception;
324             caught = true;
325         };
326         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
327         checkResult(@"JSContext.exceptionHandler", caught);
328     }
329
330     @autoreleasepool {
331         JSContext *context = [[JSContext alloc] init];
332         context[@"callback"] = ^{
333             JSContext *context = [JSContext currentContext];
334             context.exception = [JSValue valueWithNewErrorFromMessage:@"Something went wrong." inContext:context];
335         };
336         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
337         checkResult(@"Explicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
338         checkResult(@"Explicit throw in callback - not thrown to Objective-C", !context.exception);
339     }
340
341     @autoreleasepool {
342         JSContext *context = [[JSContext alloc] init];
343         context[@"callback"] = ^{
344             JSContext *context = [JSContext currentContext];
345             [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
346         };
347         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
348         checkResult(@"Implicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
349         checkResult(@"Implicit throw in callback - not thrown to Objective-C", !context.exception);
350     }
351
352     @autoreleasepool {
353         JSContext *context = [[JSContext alloc] init];
354         [context evaluateScript:
355             @"function sum(array) { \
356                 var result = 0; \
357                 for (var i in array) \
358                     result += array[i]; \
359                 return result; \
360             }"];
361         JSValue *array = [JSValue valueWithObject:@[@13, @2, @7] inContext:context];
362         JSValue *sumFunction = context[@"sum"];
363         JSValue *result = [sumFunction callWithArguments:@[ array ]];
364         checkResult(@"sum([13, 2, 7])", [result toInt32] == 22);
365     }
366
367     @autoreleasepool {
368         JSContext *context = [[JSContext alloc] init];
369         JSValue *mulAddFunction = [context evaluateScript:
370             @"(function(array, object) { \
371                 var result = []; \
372                 for (var i in array) \
373                     result.push(array[i] * object.x + object.y); \
374                 return result; \
375             })"];
376         JSValue *result = [mulAddFunction callWithArguments:@[ @[ @2, @4, @8 ], @{ @"x":@0.5, @"y":@42 } ]];
377         checkResult(@"mulAddFunction", [result isObject] && [[result toString] isEqual:@"43,44,46"]);
378     }
379
380     @autoreleasepool {
381         JSContext *context = [[JSContext alloc] init];        
382         JSValue *array = [JSValue valueWithNewArrayInContext:context];
383         checkResult(@"arrayLengthEmpty", [[array[@"length"] toNumber] unsignedIntegerValue] == 0);
384         JSValue *value1 = [JSValue valueWithInt32:42 inContext:context];
385         JSValue *value2 = [JSValue valueWithInt32:24 inContext:context];
386         NSUInteger lowIndex = 5;
387         NSUInteger maxLength = UINT_MAX;
388
389         [array setValue:value1 atIndex:lowIndex];
390         checkResult(@"array.length after put to low index", [[array[@"length"] toNumber] unsignedIntegerValue] == (lowIndex + 1));
391
392         [array setValue:value1 atIndex:(maxLength - 1)];
393         checkResult(@"array.length after put to maxLength - 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
394
395         [array setValue:value2 atIndex:maxLength];
396         checkResult(@"array.length after put to maxLength", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
397
398         [array setValue:value2 atIndex:(maxLength + 1)];
399         checkResult(@"array.length after put to maxLength + 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
400
401         checkResult(@"valueAtIndex:0 is undefined", [[array valueAtIndex:0] isUndefined]);
402         checkResult(@"valueAtIndex:lowIndex", [[array valueAtIndex:lowIndex] toInt32] == 42);
403         checkResult(@"valueAtIndex:maxLength - 1", [[array valueAtIndex:(maxLength - 1)] toInt32] == 42);
404         checkResult(@"valueAtIndex:maxLength", [[array valueAtIndex:maxLength] toInt32] == 24);
405         checkResult(@"valueAtIndex:maxLength + 1", [[array valueAtIndex:(maxLength + 1)] toInt32] == 24);
406     }
407
408     @autoreleasepool {
409         JSContext *context = [[JSContext alloc] init];
410         JSValue *object = [JSValue valueWithNewObjectInContext:context];
411
412         object[@"point"] = @{ @"x":@1, @"y":@2 };
413         object[@"point"][@"x"] = @3;
414         CGPoint point = [object[@"point"] toPoint];
415         checkResult(@"toPoint", point.x == 3 && point.y == 2);
416
417         object[@{ @"toString":^{ return @"foo"; } }] = @"bar";
418         checkResult(@"toString in object literal used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
419
420         object[[@"foobar" substringToIndex:3]] = @"bar";
421         checkResult(@"substring used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
422     }
423
424     @autoreleasepool {
425         JSContext *context = [[JSContext alloc] init];
426         TextXYZ *testXYZ = [[TextXYZ alloc] init];
427         context[@"testXYZ"] = testXYZ;
428         testXYZ.x = 3;
429         testXYZ.y = 4;
430         testXYZ.z = 5;
431         [context evaluateScript:@"testXYZ.x = 13; testXYZ.y = 14;"];
432         [context evaluateScript:@"testXYZ.test('test')"];
433         checkResult(@"TextXYZ - testXYZTested", testXYZTested);
434         JSValue *result = [context evaluateScript:@"testXYZ.x + ',' + testXYZ.y + ',' + testXYZ.z"];
435         checkResult(@"TextXYZ - result", [result isEqualToObject:@"13,4,undefined"]);
436     }
437
438     @autoreleasepool {
439         JSContext *context = [[JSContext alloc] init];
440         [context[@"Object"][@"prototype"] defineProperty:@"getterProperty" descriptor:@{
441             JSPropertyDescriptorGetKey:^{
442                 return [JSContext currentThis][@"x"];
443             }
444         }];
445         JSValue *object = [JSValue valueWithObject:@{ @"x":@101 } inContext:context];
446         int result = [object [@"getterProperty"] toInt32];
447         checkResult(@"getterProperty", result == 101);
448     }
449
450     @autoreleasepool {
451         JSContext *context = [[JSContext alloc] init];
452         context[@"concatenate"] = ^{
453             NSArray *arguments = [JSContext currentArguments];
454             if (![arguments count])
455                 return @"";
456             NSString *message = [arguments[0] description];
457             for (NSUInteger index = 1; index < [arguments count]; ++index)
458                 message = [NSString stringWithFormat:@"%@ %@", message, arguments[index]];
459             return message;
460         };
461         JSValue *result = [context evaluateScript:@"concatenate('Hello,', 'World!')"];
462         checkResult(@"concatenate", [result isEqualToObject:@"Hello, World!"]);
463     }
464
465     @autoreleasepool {
466         JSContext *context = [[JSContext alloc] init];
467         context[@"foo"] = @YES;
468         checkResult(@"@YES is boolean", [context[@"foo"] isBoolean]);
469         JSValue *result = [context evaluateScript:@"typeof foo"];
470         checkResult(@"@YES is boolean", [result isEqualToObject:@"boolean"]);
471     }
472
473     @autoreleasepool {
474         JSContext *context = [[JSContext alloc] init];
475         TestObject* testObject = [TestObject testObject];
476         context[@"testObject"] = testObject;
477         JSValue *result = [context evaluateScript:@"String(testObject)"];
478         checkResult(@"String(testObject)", [result isEqualToObject:@"[object TestObject]"]);
479     }
480
481     @autoreleasepool {
482         JSContext *context = [[JSContext alloc] init];
483         TestObject* testObject = [TestObject testObject];
484         context[@"testObject"] = testObject;
485         JSValue *result = [context evaluateScript:@"String(testObject.__proto__)"];
486         checkResult(@"String(testObject.__proto__)", [result isEqualToObject:@"[object TestObjectPrototype]"]);
487     }
488
489     @autoreleasepool {
490         JSContext *context = [[JSContext alloc] init];
491         context[@"TestObject"] = [TestObject class];
492         JSValue *result = [context evaluateScript:@"String(TestObject)"];
493         checkResult(@"String(TestObject)", [result isEqualToObject:@"[object TestObjectConstructor]"]);
494     }
495
496     @autoreleasepool {
497         JSContext *context = [[JSContext alloc] init];
498         JSValue* value = [JSValue valueWithObject:[TestObject class] inContext:context];
499         checkResult(@"[value toObject] == [TestObject class]", [value toObject] == [TestObject class]);
500     }
501
502     @autoreleasepool {
503         JSContext *context = [[JSContext alloc] init];
504         context[@"TestObject"] = [TestObject class];
505         JSValue *result = [context evaluateScript:@"TestObject.parentTest()"];
506         checkResult(@"TestObject.parentTest()", [result isEqualToObject:@"TestObject"]);
507     }
508
509     @autoreleasepool {
510         JSContext *context = [[JSContext alloc] init];
511         TestObject* testObject = [TestObject testObject];
512         context[@"testObjectA"] = testObject;
513         context[@"testObjectB"] = testObject;
514         JSValue *result = [context evaluateScript:@"testObjectA == testObjectB"];
515         checkResult(@"testObjectA == testObjectB", [result isBoolean] && [result toBool]);
516     }
517
518     @autoreleasepool {
519         JSContext *context = [[JSContext alloc] init];
520         TestObject* testObject = [TestObject testObject];
521         context[@"testObject"] = testObject;
522         testObject.point = (CGPoint){3,4};
523         JSValue *result = [context evaluateScript:@"var result = JSON.stringify(testObject.point); testObject.point = {x:12,y:14}; result"];
524         checkResult(@"testObject.point - result", [result isEqualToObject:@"{\"x\":3,\"y\":4}"]);
525         checkResult(@"testObject.point - {x:12,y:14}", testObject.point.x == 12 && testObject.point.y == 14);
526     }
527
528     @autoreleasepool {
529         JSContext *context = [[JSContext alloc] init];
530         TestObject* testObject = [TestObject testObject];
531         testObject.six = 6;
532         context[@"testObject"] = testObject;
533         context[@"mul"] = ^(int x, int y){ return x * y; };
534         JSValue *result = [context evaluateScript:@"mul(testObject.six, 7)"];
535         checkResult(@"mul(testObject.six, 7)", [result isNumber] && [result toInt32] == 42);
536     }
537
538     @autoreleasepool {
539         JSContext *context = [[JSContext alloc] init];
540         TestObject* testObject = [TestObject testObject];
541         context[@"testObject"] = testObject;
542         context[@"testObject"][@"variable"] = @4;
543         [context evaluateScript:@"++testObject.variable"];
544         checkResult(@"++testObject.variable", testObject.variable == 5);
545     }
546
547     @autoreleasepool {
548         JSContext *context = [[JSContext alloc] init];
549         context[@"point"] = @{ @"x":@6, @"y":@7 };
550         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
551         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
552     }
553
554     @autoreleasepool {
555         JSContext *context = [[JSContext alloc] init];
556         context[@"point"] = @{ @"x":@6, @"y":@7 };
557         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
558         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
559     }
560
561     @autoreleasepool {
562         JSContext *context = [[JSContext alloc] init];
563         TestObject* testObject = [TestObject testObject];
564         context[@"testObject"] = testObject;
565         JSValue *result = [context evaluateScript:@"testObject.getString()"];
566         checkResult(@"testObject.getString()", [result isString] && [result toInt32] == 42);
567     }
568
569     @autoreleasepool {
570         JSContext *context = [[JSContext alloc] init];
571         TestObject* testObject = [TestObject testObject];
572         context[@"testObject"] = testObject;
573         JSValue *result = [context evaluateScript:@"testObject.testArgumentTypes(101,0.5,true,'foo',666,[false,'bar',false],{x:'baz'})"];
574         checkResult(@"testObject.testArgumentTypes", [result isEqualToObject:@"101,0.5,1,foo,666,bar,baz"]);
575     }
576
577     @autoreleasepool {
578         JSContext *context = [[JSContext alloc] init];
579         TestObject* testObject = [TestObject testObject];
580         context[@"testObject"] = testObject;
581         JSValue *result = [context evaluateScript:@"testObject.getString.call(testObject)"];
582         checkResult(@"testObject.getString.call(testObject)", [result isString] && [result toInt32] == 42);
583     }
584
585     @autoreleasepool {
586         JSContext *context = [[JSContext alloc] init];
587         TestObject* testObject = [TestObject testObject];
588         context[@"testObject"] = testObject;
589         checkResult(@"testObject.getString.call({}) pre", !context.exception);
590         [context evaluateScript:@"testObject.getString.call({})"];
591         checkResult(@"testObject.getString.call({}) post", context.exception);
592     }
593
594     @autoreleasepool {
595         JSContext *context = [[JSContext alloc] init];
596         TestObject* testObject = [TestObject testObject];
597         context[@"testObject"] = testObject;
598         JSValue *result = [context evaluateScript:@"var result = 0; testObject.callback(function(x){ result = x; }); result"];
599         checkResult(@"testObject.callback", [result isNumber] && [result toInt32] == 42);
600         result = [context evaluateScript:@"testObject.bogusCallback"];
601         checkResult(@"testObject.bogusCallback == undefined", [result isUndefined]);
602     }
603
604     @autoreleasepool {
605         JSContext *context = [[JSContext alloc] init];
606         TestObject *testObject = [TestObject testObject];
607         context[@"testObject"] = testObject;
608         JSValue *result = [context evaluateScript:@"Function.prototype.toString.call(testObject.callback)"];
609         checkResult(@"Function.prototype.toString", !context.exception && ![result isUndefined]);
610     }
611
612     @autoreleasepool {
613         JSContext *context1 = [[JSContext alloc] init];
614         JSContext *context2 = [[JSContext alloc] initWithVirtualMachine:context1.virtualMachine];
615         JSValue *value = [JSValue valueWithDouble:42 inContext:context2];
616         context1[@"passValueBetweenContexts"] = value;
617         JSValue *result = [context1 evaluateScript:@"passValueBetweenContexts"];
618         checkResult(@"[value isEqualToObject:result]", [value isEqualToObject:result]);
619     }
620
621     @autoreleasepool {
622         JSContext *context = [[JSContext alloc] init];
623         context[@"handleTheDictionary"] = ^(NSDictionary *dict) {
624             NSDictionary *expectedDict = @{
625                 @"foo" : [NSNumber numberWithInt:1],
626                 @"bar" : @{
627                     @"baz": [NSNumber numberWithInt:2]
628                 }
629             };
630             checkResult(@"recursively convert nested dictionaries", [dict isEqualToDictionary:expectedDict]);
631         };
632         [context evaluateScript:@"var myDict = { \
633             'foo': 1, \
634             'bar': {'baz': 2} \
635         }; \
636         handleTheDictionary(myDict);"];
637
638         context[@"handleTheArray"] = ^(NSArray *array) {
639             NSArray *expectedArray = @[@"foo", @"bar", @[@"baz"]];
640             checkResult(@"recursively convert nested arrays", [array isEqualToArray:expectedArray]);
641         };
642         [context evaluateScript:@"var myArray = ['foo', 'bar', ['baz']]; handleTheArray(myArray);"];
643     }
644
645     @autoreleasepool {
646         JSContext *context = [[JSContext alloc] init];
647         TestObject *testObject = [TestObject testObject];
648         @autoreleasepool {
649             context[@"testObject"] = testObject;
650             [context evaluateScript:@"var constructor = Object.getPrototypeOf(testObject).constructor; constructor.prototype = undefined;"];
651             [context evaluateScript:@"testObject = undefined"];
652         }
653         
654         JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
655
656         @autoreleasepool {
657             context[@"testObject"] = testObject;
658         }
659     }
660
661     @autoreleasepool {
662         JSContext *context = [[JSContext alloc] init];
663         TextXYZ *testXYZ = [[TextXYZ alloc] init];
664
665         @autoreleasepool {
666             context[@"testXYZ"] = testXYZ;
667
668             [context evaluateScript:@" \
669                 didClick = false; \
670                 testXYZ.onclick = function() { \
671                     didClick = true; \
672                 }; \
673                  \
674                 testXYZ.weakOnclick = function() { \
675                     return 'foo'; \
676                 }; \
677             "];
678         }
679
680         @autoreleasepool {
681             [testXYZ click];
682             JSValue *result = [context evaluateScript:@"didClick"];
683             checkResult(@"Event handler onclick", [result toBool]);
684         }
685
686         JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
687
688         @autoreleasepool {
689             JSValue *result = [context evaluateScript:@"testXYZ.onclick"];
690             checkResult(@"onclick still around after GC", !([result isNull] || [result isUndefined]));
691         }
692
693
694         @autoreleasepool {
695             JSValue *result = [context evaluateScript:@"testXYZ.weakOnclick"];
696             checkResult(@"weakOnclick not around after GC", [result isNull] || [result isUndefined]);
697         }
698
699         @autoreleasepool {
700             [context evaluateScript:@" \
701                 didClick = false; \
702                 testXYZ = null; \
703             "];
704         }
705
706         JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
707
708         @autoreleasepool {
709             [testXYZ click];
710             JSValue *result = [context evaluateScript:@"didClick"];
711             checkResult(@"Event handler onclick doesn't fire", ![result toBool]);
712         }
713     }
714
715     @autoreleasepool {
716         JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
717         TestObject *testObject = [TestObject testObject];
718         JSManagedValue *weakValue;
719         @autoreleasepool {
720             JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
721             context[@"testObject"] = testObject;
722             weakValue = [[JSManagedValue alloc] initWithValue:context[@"testObject"]];
723         }
724
725         @autoreleasepool {
726             JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
727             context[@"testObject"] = testObject;
728             JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
729             checkResult(@"weak value == nil", ![weakValue value]);
730             checkResult(@"root is still alive", ![context[@"testObject"] isUndefined]);
731         }
732     }
733
734     @autoreleasepool {
735         JSVirtualMachine *vm = [TinyDOMNode sharedVirtualMachine];
736         JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
737         TinyDOMNode *root = [[TinyDOMNode alloc] init];
738         TinyDOMNode *lastNode = root;
739         for (NSUInteger i = 0; i < 3; i++) {
740             TinyDOMNode *newNode = [[TinyDOMNode alloc] init];
741             [lastNode appendChild:newNode];
742             lastNode = newNode;
743         }
744
745         @autoreleasepool {
746             context[@"root"] = root;
747             context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
748                 TinyDOMNode *lastNode = nil;
749                 while (head) {
750                     lastNode = head;
751                     head = [lastNode childAtIndex:0];
752                 }
753                 return lastNode;
754             };
755             [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
756         }
757
758         JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
759
760         JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
761         checkResult(@"My custom property == 42", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42);
762
763         [TinyDOMNode clearSharedVirtualMachine];
764     }
765
766     @autoreleasepool {
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];
774             lastNode = newNode;
775         }
776
777         @autoreleasepool {
778             context[@"root"] = root;
779             context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
780                 TinyDOMNode *lastNode = nil;
781                 while (head) {
782                     lastNode = head;
783                     head = [lastNode childAtIndex:0];
784                 }
785                 return lastNode;
786             };
787             [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
788
789             [root appendChild:[root childAtIndex:0]];
790             [root removeChildAtIndex:0];
791         }
792
793         JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
794
795         JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
796         checkResult(@"duplicate calls to addManagedReference don't cause things to die", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42);
797
798         [TinyDOMNode clearSharedVirtualMachine];
799     }
800 }
801
802 #else
803
804 void testObjectiveCAPI()
805 {
806 }
807
808 #endif