BUILD FIX (r146558): Call [super dealloc] from -[TinyDOMNode dealloc]
[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 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     [super dealloc];
207 }
208
209 - (void)appendChild:(TinyDOMNode *)child
210 {
211     [[TinyDOMNode sharedVirtualMachine] addManagedReference:child withOwner:self];
212     [m_children addObject:child];
213 }
214
215 - (NSUInteger)numberOfChildren
216 {
217     return [m_children count];
218 }
219
220 - (TinyDOMNode *)childAtIndex:(NSUInteger)index
221 {
222     if (index >= [m_children count])
223         return nil;
224     return [m_children objectAtIndex:index];
225 }
226
227 - (void)removeChildAtIndex:(NSUInteger)index
228 {
229     if (index >= [m_children count])
230         return;
231     [[TinyDOMNode sharedVirtualMachine] removeManagedReference:[m_children objectAtIndex:index] withOwner:self];
232     [m_children removeObjectAtIndex:index];
233 }
234
235 @end
236
237 static void checkResult(NSString *description, bool passed)
238 {
239     NSLog(@"TEST: \"%@\": %@", description, passed ? @"PASSED" : @"FAILED");
240     if (!passed)
241         failed = 1;
242 }
243
244 static bool blockSignatureContainsClass()
245 {
246     static bool containsClass = ^{
247         id block = ^(NSString *string){ return string; };
248         return _Block_has_signature(block) && strstr(_Block_signature(block), "NSString");
249     }();
250     return containsClass;
251 }
252
253 void testObjectiveCAPI()
254 {
255     NSLog(@"Testing Objective-C API");
256
257     @autoreleasepool {
258         JSContext *context = [[JSContext alloc] init];
259         JSValue *result = [context evaluateScript:@"2 + 2"];
260         checkResult(@"2 + 2", [result isNumber] && [result toInt32] == 4);
261     }
262
263     @autoreleasepool {
264         JSContext *context = [[JSContext alloc] init];
265         NSString *result = [NSString stringWithFormat:@"Two plus two is %@", [context evaluateScript:@"2 + 2"]];
266         checkResult(@"stringWithFormat", [result isEqual:@"Two plus two is 4"]);
267     }
268
269     @autoreleasepool {
270         JSContext *context = [[JSContext alloc] init];
271         context[@"message"] = @"Hello";
272         JSValue *result = [context evaluateScript:@"message + ', World!'"];
273         checkResult(@"Hello, World!", [result isString] && [result isEqualToObject:@"Hello, World!"]);
274     }
275
276     @autoreleasepool {
277         JSContext *context = [[JSContext alloc] init];
278         JSValue *result = [context evaluateScript:@"({ x:42 })"];
279         checkResult(@"({ x:42 })", [result isObject] && [result[@"x"] isEqualToObject:@42]);
280         id obj = [result toObject];
281         checkResult(@"Check dictionary literal", [obj isKindOfClass:[NSDictionary class]]);
282         id num = (NSDictionary *)obj[@"x"];
283         checkResult(@"Check numeric literal", [num isKindOfClass:[NSNumber class]]);
284     }
285
286     @autoreleasepool {
287         JSContext *context = [[JSContext alloc] init];
288         __block int result;
289         context[@"blockCallback"] = ^(int value){
290             result = value;
291         };
292         [context evaluateScript:@"blockCallback(42)"];
293         checkResult(@"blockCallback", result == 42);
294     }
295
296     if (blockSignatureContainsClass()) {
297         @autoreleasepool {
298             JSContext *context = [[JSContext alloc] init];
299             __block bool result = false;
300             context[@"blockCallback"] = ^(NSString *value){
301                 result = [@"42" isEqualToString:value] == YES;
302             };
303             [context evaluateScript:@"blockCallback(42)"];
304             checkResult(@"blockCallback(NSString *)", result);
305         }
306     } else
307         NSLog(@"Skipping 'blockCallback(NSString *)' test case");
308
309     @autoreleasepool {
310         JSContext *context = [[JSContext alloc] init];
311         checkResult(@"!context.exception", !context.exception);
312         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
313         checkResult(@"context.exception", context.exception);
314     }
315
316     @autoreleasepool {
317         JSContext *context = [[JSContext alloc] init];
318         __block bool caught = false;
319         context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
320             (void)context;
321             (void)exception;
322             caught = true;
323         };
324         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
325         checkResult(@"JSContext.exceptionHandler", caught);
326     }
327
328     @autoreleasepool {
329         JSContext *context = [[JSContext alloc] init];
330         context[@"callback"] = ^{
331             JSContext *context = [JSContext currentContext];
332             context.exception = [JSValue valueWithNewErrorFromMessage:@"Something went wrong." inContext:context];
333         };
334         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
335         checkResult(@"Explicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
336         checkResult(@"Explicit throw in callback - not thrown to Objective-C", !context.exception);
337     }
338
339     @autoreleasepool {
340         JSContext *context = [[JSContext alloc] init];
341         context[@"callback"] = ^{
342             JSContext *context = [JSContext currentContext];
343             [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
344         };
345         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
346         checkResult(@"Implicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
347         checkResult(@"Implicit throw in callback - not thrown to Objective-C", !context.exception);
348     }
349
350     @autoreleasepool {
351         JSContext *context = [[JSContext alloc] init];
352         [context evaluateScript:
353             @"function sum(array) { \
354                 var result = 0; \
355                 for (var i in array) \
356                     result += array[i]; \
357                 return result; \
358             }"];
359         JSValue *array = [JSValue valueWithObject:@[@13, @2, @7] inContext:context];
360         JSValue *sumFunction = context[@"sum"];
361         JSValue *result = [sumFunction callWithArguments:@[ array ]];
362         checkResult(@"sum([13, 2, 7])", [result toInt32] == 22);
363     }
364
365     @autoreleasepool {
366         JSContext *context = [[JSContext alloc] init];
367         JSValue *mulAddFunction = [context evaluateScript:
368             @"(function(array, object) { \
369                 var result = []; \
370                 for (var i in array) \
371                     result.push(array[i] * object.x + object.y); \
372                 return result; \
373             })"];
374         JSValue *result = [mulAddFunction callWithArguments:@[ @[ @2, @4, @8 ], @{ @"x":@0.5, @"y":@42 } ]];
375         checkResult(@"mulAddFunction", [result isObject] && [[result toString] isEqual:@"43,44,46"]);
376     }
377
378     @autoreleasepool {
379         JSContext *context = [[JSContext alloc] init];        
380         JSValue *array = [JSValue valueWithNewArrayInContext:context];
381         checkResult(@"arrayLengthEmpty", [[array[@"length"] toNumber] unsignedIntegerValue] == 0);
382         JSValue *value1 = [JSValue valueWithInt32:42 inContext:context];
383         JSValue *value2 = [JSValue valueWithInt32:24 inContext:context];
384         NSUInteger lowIndex = 5;
385         NSUInteger maxLength = UINT_MAX;
386
387         [array setValue:value1 atIndex:lowIndex];
388         checkResult(@"array.length after put to low index", [[array[@"length"] toNumber] unsignedIntegerValue] == (lowIndex + 1));
389
390         [array setValue:value1 atIndex:(maxLength - 1)];
391         checkResult(@"array.length after put to maxLength - 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
392
393         [array setValue:value2 atIndex:maxLength];
394         checkResult(@"array.length after put to maxLength", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
395
396         [array setValue:value2 atIndex:(maxLength + 1)];
397         checkResult(@"array.length after put to maxLength + 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
398
399         checkResult(@"valueAtIndex:0 is undefined", [[array valueAtIndex:0] isUndefined]);
400         checkResult(@"valueAtIndex:lowIndex", [[array valueAtIndex:lowIndex] toInt32] == 42);
401         checkResult(@"valueAtIndex:maxLength - 1", [[array valueAtIndex:(maxLength - 1)] toInt32] == 42);
402         checkResult(@"valueAtIndex:maxLength", [[array valueAtIndex:maxLength] toInt32] == 24);
403         checkResult(@"valueAtIndex:maxLength + 1", [[array valueAtIndex:(maxLength + 1)] toInt32] == 24);
404     }
405
406     @autoreleasepool {
407         JSContext *context = [[JSContext alloc] init];
408         JSValue *object = [JSValue valueWithNewObjectInContext:context];
409
410         object[@"point"] = @{ @"x":@1, @"y":@2 };
411         object[@"point"][@"x"] = @3;
412         CGPoint point = [object[@"point"] toPoint];
413         checkResult(@"toPoint", point.x == 3 && point.y == 2);
414
415         object[@{ @"toString":^{ return @"foo"; } }] = @"bar";
416         checkResult(@"toString in object literal used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
417
418         object[[@"foobar" substringToIndex:3]] = @"bar";
419         checkResult(@"substring used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
420     }
421
422     @autoreleasepool {
423         JSContext *context = [[JSContext alloc] init];
424         TextXYZ *testXYZ = [[TextXYZ alloc] init];
425         context[@"testXYZ"] = testXYZ;
426         testXYZ.x = 3;
427         testXYZ.y = 4;
428         testXYZ.z = 5;
429         [context evaluateScript:@"testXYZ.x = 13; testXYZ.y = 14;"];
430         [context evaluateScript:@"testXYZ.test('test')"];
431         checkResult(@"TextXYZ - testXYZTested", testXYZTested);
432         JSValue *result = [context evaluateScript:@"testXYZ.x + ',' + testXYZ.y + ',' + testXYZ.z"];
433         checkResult(@"TextXYZ - result", [result isEqualToObject:@"13,4,undefined"]);
434     }
435
436     @autoreleasepool {
437         JSContext *context = [[JSContext alloc] init];
438         [context[@"Object"][@"prototype"] defineProperty:@"getterProperty" descriptor:@{
439             JSPropertyDescriptorGetKey:^{
440                 return [JSContext currentThis][@"x"];
441             }
442         }];
443         JSValue *object = [JSValue valueWithObject:@{ @"x":@101 } inContext:context];
444         int result = [object [@"getterProperty"] toInt32];
445         checkResult(@"getterProperty", result == 101);
446     }
447
448     @autoreleasepool {
449         JSContext *context = [[JSContext alloc] init];
450         context[@"concatenate"] = ^{
451             NSArray *arguments = [JSContext currentArguments];
452             if (![arguments count])
453                 return @"";
454             NSString *message = [arguments[0] description];
455             for (NSUInteger index = 1; index < [arguments count]; ++index)
456                 message = [NSString stringWithFormat:@"%@ %@", message, arguments[index]];
457             return message;
458         };
459         JSValue *result = [context evaluateScript:@"concatenate('Hello,', 'World!')"];
460         checkResult(@"concatenate", [result isEqualToObject:@"Hello, World!"]);
461     }
462
463     @autoreleasepool {
464         JSContext *context = [[JSContext alloc] init];
465         context[@"foo"] = @YES;
466         checkResult(@"@YES is boolean", [context[@"foo"] isBoolean]);
467         JSValue *result = [context evaluateScript:@"typeof foo"];
468         checkResult(@"@YES is boolean", [result isEqualToObject:@"boolean"]);
469     }
470
471     @autoreleasepool {
472         JSContext *context = [[JSContext alloc] init];
473         TestObject* testObject = [TestObject testObject];
474         context[@"testObject"] = testObject;
475         JSValue *result = [context evaluateScript:@"String(testObject)"];
476         checkResult(@"String(testObject)", [result isEqualToObject:@"[object TestObject]"]);
477     }
478
479     @autoreleasepool {
480         JSContext *context = [[JSContext alloc] init];
481         TestObject* testObject = [TestObject testObject];
482         context[@"testObject"] = testObject;
483         JSValue *result = [context evaluateScript:@"String(testObject.__proto__)"];
484         checkResult(@"String(testObject.__proto__)", [result isEqualToObject:@"[object TestObjectPrototype]"]);
485     }
486
487     @autoreleasepool {
488         JSContext *context = [[JSContext alloc] init];
489         context[@"TestObject"] = [TestObject class];
490         JSValue *result = [context evaluateScript:@"String(TestObject)"];
491         checkResult(@"String(TestObject)", [result isEqualToObject:@"[object TestObjectConstructor]"]);
492     }
493
494     @autoreleasepool {
495         JSContext *context = [[JSContext alloc] init];
496         JSValue* value = [JSValue valueWithObject:[TestObject class] inContext:context];
497         checkResult(@"[value toObject] == [TestObject class]", [value toObject] == [TestObject class]);
498     }
499
500     @autoreleasepool {
501         JSContext *context = [[JSContext alloc] init];
502         context[@"TestObject"] = [TestObject class];
503         JSValue *result = [context evaluateScript:@"TestObject.parentTest()"];
504         checkResult(@"TestObject.parentTest()", [result isEqualToObject:@"TestObject"]);
505     }
506
507     @autoreleasepool {
508         JSContext *context = [[JSContext alloc] init];
509         TestObject* testObject = [TestObject testObject];
510         context[@"testObjectA"] = testObject;
511         context[@"testObjectB"] = testObject;
512         JSValue *result = [context evaluateScript:@"testObjectA == testObjectB"];
513         checkResult(@"testObjectA == testObjectB", [result isBoolean] && [result toBool]);
514     }
515
516     @autoreleasepool {
517         JSContext *context = [[JSContext alloc] init];
518         TestObject* testObject = [TestObject testObject];
519         context[@"testObject"] = testObject;
520         testObject.point = (CGPoint){3,4};
521         JSValue *result = [context evaluateScript:@"var result = JSON.stringify(testObject.point); testObject.point = {x:12,y:14}; result"];
522         checkResult(@"testObject.point - result", [result isEqualToObject:@"{\"x\":3,\"y\":4}"]);
523         checkResult(@"testObject.point - {x:12,y:14}", testObject.point.x == 12 && testObject.point.y == 14);
524     }
525
526     @autoreleasepool {
527         JSContext *context = [[JSContext alloc] init];
528         TestObject* testObject = [TestObject testObject];
529         testObject.six = 6;
530         context[@"testObject"] = testObject;
531         context[@"mul"] = ^(int x, int y){ return x * y; };
532         JSValue *result = [context evaluateScript:@"mul(testObject.six, 7)"];
533         checkResult(@"mul(testObject.six, 7)", [result isNumber] && [result toInt32] == 42);
534     }
535
536     @autoreleasepool {
537         JSContext *context = [[JSContext alloc] init];
538         TestObject* testObject = [TestObject testObject];
539         context[@"testObject"] = testObject;
540         context[@"testObject"][@"variable"] = @4;
541         [context evaluateScript:@"++testObject.variable"];
542         checkResult(@"++testObject.variable", testObject.variable == 5);
543     }
544
545     @autoreleasepool {
546         JSContext *context = [[JSContext alloc] init];
547         context[@"point"] = @{ @"x":@6, @"y":@7 };
548         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
549         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
550     }
551
552     @autoreleasepool {
553         JSContext *context = [[JSContext alloc] init];
554         context[@"point"] = @{ @"x":@6, @"y":@7 };
555         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
556         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
557     }
558
559     @autoreleasepool {
560         JSContext *context = [[JSContext alloc] init];
561         TestObject* testObject = [TestObject testObject];
562         context[@"testObject"] = testObject;
563         JSValue *result = [context evaluateScript:@"testObject.getString()"];
564         checkResult(@"testObject.getString()", [result isString] && [result toInt32] == 42);
565     }
566
567     @autoreleasepool {
568         JSContext *context = [[JSContext alloc] init];
569         TestObject* testObject = [TestObject testObject];
570         context[@"testObject"] = testObject;
571         JSValue *result = [context evaluateScript:@"testObject.testArgumentTypes(101,0.5,true,'foo',666,[false,'bar',false],{x:'baz'})"];
572         checkResult(@"testObject.testArgumentTypes", [result isEqualToObject:@"101,0.5,1,foo,666,bar,baz"]);
573     }
574
575     @autoreleasepool {
576         JSContext *context = [[JSContext alloc] init];
577         TestObject* testObject = [TestObject testObject];
578         context[@"testObject"] = testObject;
579         JSValue *result = [context evaluateScript:@"testObject.getString.call(testObject)"];
580         checkResult(@"testObject.getString.call(testObject)", [result isString] && [result toInt32] == 42);
581     }
582
583     @autoreleasepool {
584         JSContext *context = [[JSContext alloc] init];
585         TestObject* testObject = [TestObject testObject];
586         context[@"testObject"] = testObject;
587         checkResult(@"testObject.getString.call({}) pre", !context.exception);
588         [context evaluateScript:@"testObject.getString.call({})"];
589         checkResult(@"testObject.getString.call({}) post", context.exception);
590     }
591
592     @autoreleasepool {
593         JSContext *context = [[JSContext alloc] init];
594         TestObject* testObject = [TestObject testObject];
595         context[@"testObject"] = testObject;
596         JSValue *result = [context evaluateScript:@"var result = 0; testObject.callback(function(x){ result = x; }); result"];
597         checkResult(@"testObject.callback", [result isNumber] && [result toInt32] == 42);
598         result = [context evaluateScript:@"testObject.bogusCallback"];
599         checkResult(@"testObject.bogusCallback == undefined", [result isUndefined]);
600     }
601
602     @autoreleasepool {
603         JSContext *context = [[JSContext alloc] init];
604         TestObject *testObject = [TestObject testObject];
605         context[@"testObject"] = testObject;
606         JSValue *result = [context evaluateScript:@"Function.prototype.toString.call(testObject.callback)"];
607         checkResult(@"Function.prototype.toString", !context.exception && ![result isUndefined]);
608     }
609
610     @autoreleasepool {
611         JSContext *context1 = [[JSContext alloc] init];
612         JSContext *context2 = [[JSContext alloc] initWithVirtualMachine:context1.virtualMachine];
613         JSValue *value = [JSValue valueWithDouble:42 inContext:context2];
614         context1[@"passValueBetweenContexts"] = value;
615         JSValue *result = [context1 evaluateScript:@"passValueBetweenContexts"];
616         checkResult(@"[value isEqualToObject:result]", [value isEqualToObject:result]);
617     }
618
619     @autoreleasepool {
620         JSContext *context = [[JSContext alloc] init];
621         context[@"handleTheDictionary"] = ^(NSDictionary *dict) {
622             NSDictionary *expectedDict = @{
623                 @"foo" : [NSNumber numberWithInt:1],
624                 @"bar" : @{
625                     @"baz": [NSNumber numberWithInt:2]
626                 }
627             };
628             checkResult(@"recursively convert nested dictionaries", [dict isEqualToDictionary:expectedDict]);
629         };
630         [context evaluateScript:@"var myDict = { \
631             'foo': 1, \
632             'bar': {'baz': 2} \
633         }; \
634         handleTheDictionary(myDict);"];
635
636         context[@"handleTheArray"] = ^(NSArray *array) {
637             NSArray *expectedArray = @[@"foo", @"bar", @[@"baz"]];
638             checkResult(@"recursively convert nested arrays", [array isEqualToArray:expectedArray]);
639         };
640         [context evaluateScript:@"var myArray = ['foo', 'bar', ['baz']]; handleTheArray(myArray);"];
641     }
642
643     @autoreleasepool {
644         JSContext *context = [[JSContext alloc] init];
645         TestObject *testObject = [TestObject testObject];
646         @autoreleasepool {
647             context[@"testObject"] = testObject;
648             [context evaluateScript:@"var constructor = Object.getPrototypeOf(testObject).constructor; constructor.prototype = undefined;"];
649             [context evaluateScript:@"testObject = undefined"];
650         }
651         
652         JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
653
654         @autoreleasepool {
655             context[@"testObject"] = testObject;
656         }
657     }
658
659     @autoreleasepool {
660         JSContext *context = [[JSContext alloc] init];
661         TextXYZ *testXYZ = [[TextXYZ alloc] init];
662
663         @autoreleasepool {
664             context[@"testXYZ"] = testXYZ;
665
666             [context evaluateScript:@" \
667                 didClick = false; \
668                 testXYZ.onclick = function() { \
669                     didClick = true; \
670                 }; \
671                  \
672                 testXYZ.weakOnclick = function() { \
673                     return 'foo'; \
674                 }; \
675             "];
676         }
677
678         @autoreleasepool {
679             [testXYZ click];
680             JSValue *result = [context evaluateScript:@"didClick"];
681             checkResult(@"Event handler onclick", [result toBool]);
682         }
683
684         JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
685
686         @autoreleasepool {
687             JSValue *result = [context evaluateScript:@"testXYZ.onclick"];
688             checkResult(@"onclick still around after GC", !([result isNull] || [result isUndefined]));
689         }
690
691
692         @autoreleasepool {
693             JSValue *result = [context evaluateScript:@"testXYZ.weakOnclick"];
694             checkResult(@"weakOnclick not around after GC", [result isNull] || [result isUndefined]);
695         }
696
697         @autoreleasepool {
698             [context evaluateScript:@" \
699                 didClick = false; \
700                 testXYZ = null; \
701             "];
702         }
703
704         JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
705
706         @autoreleasepool {
707             [testXYZ click];
708             JSValue *result = [context evaluateScript:@"didClick"];
709             checkResult(@"Event handler onclick doesn't fire", ![result toBool]);
710         }
711     }
712
713     @autoreleasepool {
714         JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
715         TestObject *testObject = [TestObject testObject];
716         JSManagedValue *weakValue;
717         @autoreleasepool {
718             JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
719             context[@"testObject"] = testObject;
720             weakValue = [[JSManagedValue alloc] initWithValue:context[@"testObject"]];
721         }
722
723         @autoreleasepool {
724             JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
725             context[@"testObject"] = testObject;
726             JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
727             checkResult(@"weak value == nil", ![weakValue value]);
728             checkResult(@"root is still alive", ![context[@"testObject"] isUndefined]);
729         }
730     }
731
732     @autoreleasepool {
733         JSVirtualMachine *vm = [TinyDOMNode sharedVirtualMachine];
734         JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
735         TinyDOMNode *root = [[TinyDOMNode alloc] init];
736         TinyDOMNode *lastNode = root;
737         for (NSUInteger i = 0; i < 3; i++) {
738             TinyDOMNode *newNode = [[TinyDOMNode alloc] init];
739             [lastNode appendChild:newNode];
740             lastNode = newNode;
741         }
742
743         @autoreleasepool {
744             context[@"root"] = root;
745             context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
746                 TinyDOMNode *lastNode = nil;
747                 while (head) {
748                     lastNode = head;
749                     head = [lastNode childAtIndex:0];
750                 }
751                 return lastNode;
752             };
753             [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
754         }
755
756         JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
757
758         JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
759         checkResult(@"My custom property == 42", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42);
760
761         [TinyDOMNode clearSharedVirtualMachine];
762     }
763
764     @autoreleasepool {
765         JSVirtualMachine *vm = [TinyDOMNode sharedVirtualMachine];
766         JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
767         TinyDOMNode *root = [[TinyDOMNode alloc] init];
768         TinyDOMNode *lastNode = root;
769         for (NSUInteger i = 0; i < 3; i++) {
770             TinyDOMNode *newNode = [[TinyDOMNode alloc] init];
771             [lastNode appendChild:newNode];
772             lastNode = newNode;
773         }
774
775         @autoreleasepool {
776             context[@"root"] = root;
777             context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
778                 TinyDOMNode *lastNode = nil;
779                 while (head) {
780                     lastNode = head;
781                     head = [lastNode childAtIndex:0];
782                 }
783                 return lastNode;
784             };
785             [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
786
787             [root appendChild:[root childAtIndex:0]];
788             [root removeChildAtIndex:0];
789         }
790
791         JSSynchronousGarbageCollectForDebugging([context globalContextRef]);
792
793         JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
794         checkResult(@"duplicate calls to addManagedReference don't cause things to die", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42);
795
796         [TinyDOMNode clearSharedVirtualMachine];
797     }
798 }
799
800 #else
801
802 void testObjectiveCAPI()
803 {
804 }
805
806 #endif