summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/API/tests
diff options
context:
space:
mode:
Diffstat (limited to 'Source/JavaScriptCore/API/tests')
-rw-r--r--Source/JavaScriptCore/API/tests/JSNode.c1
-rw-r--r--Source/JavaScriptCore/API/tests/JSNodeList.c1
-rw-r--r--Source/JavaScriptCore/API/tests/minidom.c1
-rw-r--r--Source/JavaScriptCore/API/tests/testapi.c315
-rw-r--r--Source/JavaScriptCore/API/tests/testapi.js4
-rw-r--r--Source/JavaScriptCore/API/tests/testapi.mm841
6 files changed, 1153 insertions, 10 deletions
diff --git a/Source/JavaScriptCore/API/tests/JSNode.c b/Source/JavaScriptCore/API/tests/JSNode.c
index 052c88a02..d9a40bea6 100644
--- a/Source/JavaScriptCore/API/tests/JSNode.c
+++ b/Source/JavaScriptCore/API/tests/JSNode.c
@@ -30,7 +30,6 @@
#include "JSValueRef.h"
#include "Node.h"
#include "NodeList.h"
-#include <wtf/UnusedParam.h>
#include <wtf/Assertions.h>
static JSValueRef JSNode_appendChild(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
diff --git a/Source/JavaScriptCore/API/tests/JSNodeList.c b/Source/JavaScriptCore/API/tests/JSNodeList.c
index 0d194845e..61d7041a4 100644
--- a/Source/JavaScriptCore/API/tests/JSNodeList.c
+++ b/Source/JavaScriptCore/API/tests/JSNodeList.c
@@ -27,7 +27,6 @@
#include "JSNodeList.h"
#include "JSObjectRef.h"
#include "JSValueRef.h"
-#include <wtf/UnusedParam.h>
#include <wtf/Assertions.h>
static JSValueRef JSNodeList_item(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
diff --git a/Source/JavaScriptCore/API/tests/minidom.c b/Source/JavaScriptCore/API/tests/minidom.c
index 43ae2c1a8..8614e51e9 100644
--- a/Source/JavaScriptCore/API/tests/minidom.c
+++ b/Source/JavaScriptCore/API/tests/minidom.c
@@ -31,7 +31,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <wtf/Assertions.h>
-#include <wtf/UnusedParam.h>
static char* createStringWithContentsOfFile(const char* fileName);
static JSValueRef print(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
diff --git a/Source/JavaScriptCore/API/tests/testapi.c b/Source/JavaScriptCore/API/tests/testapi.c
index c2400f7ec..be18b2bf7 100644
--- a/Source/JavaScriptCore/API/tests/testapi.c
+++ b/Source/JavaScriptCore/API/tests/testapi.c
@@ -27,10 +27,17 @@
#include "JSBasePrivate.h"
#include "JSContextRefPrivate.h"
#include "JSObjectRefPrivate.h"
+#include "JSScriptRefPrivate.h"
+#include "JSStringRefPrivate.h"
#include <math.h>
#define ASSERT_DISABLED 0
#include <wtf/Assertions.h>
-#include <wtf/UnusedParam.h>
+
+#if PLATFORM(MAC) || PLATFORM(IOS)
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#include <sys/time.h>
+#endif
#if OS(WINDOWS)
#include <windows.h>
@@ -45,10 +52,19 @@ static double nan(const char*)
return std::numeric_limits<double>::quiet_NaN();
}
+using std::isinf;
+using std::isnan;
+
#endif
+#if JSC_OBJC_API_ENABLED
+void testObjectiveCAPI(void);
+#endif
+
+extern void JSSynchronousGarbageCollectForDebugging(JSContextRef);
+
static JSGlobalContextRef context;
-static int failed;
+int failed;
static void assertEqualsAsBoolean(JSValueRef value, bool expectedValue)
{
if (JSValueToBoolean(context, value) != expectedValue) {
@@ -481,6 +497,11 @@ static bool PropertyCatchalls_setProperty(JSContextRef context, JSObjectRef obje
return true;
}
+ if (JSStringIsEqualToUTF8CString(propertyName, "make_throw") || JSStringIsEqualToUTF8CString(propertyName, "0")) {
+ *exception = JSValueMakeNumber(context, 5);
+ return true;
+ }
+
return false;
}
@@ -1030,6 +1051,68 @@ static void checkConstnessInJSObjectNames()
val.name = "something";
}
+#if PLATFORM(MAC) || PLATFORM(IOS)
+static double currentCPUTime()
+{
+ mach_msg_type_number_t infoCount = THREAD_BASIC_INFO_COUNT;
+ thread_basic_info_data_t info;
+
+ /* Get thread information */
+ mach_port_t threadPort = mach_thread_self();
+ thread_info(threadPort, THREAD_BASIC_INFO, (thread_info_t)(&info), &infoCount);
+ mach_port_deallocate(mach_task_self(), threadPort);
+
+ double time = info.user_time.seconds + info.user_time.microseconds / 1000000.;
+ time += info.system_time.seconds + info.system_time.microseconds / 1000000.;
+
+ return time;
+}
+
+static JSValueRef currentCPUTime_callAsFunction(JSContextRef ctx, JSObjectRef functionObject, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+ UNUSED_PARAM(functionObject);
+ UNUSED_PARAM(thisObject);
+ UNUSED_PARAM(argumentCount);
+ UNUSED_PARAM(arguments);
+ UNUSED_PARAM(exception);
+
+ ASSERT(JSContextGetGlobalContext(ctx) == context);
+ return JSValueMakeNumber(ctx, currentCPUTime());
+}
+
+bool shouldTerminateCallbackWasCalled = false;
+static bool shouldTerminateCallback(JSContextRef ctx, void* context)
+{
+ UNUSED_PARAM(ctx);
+ UNUSED_PARAM(context);
+ shouldTerminateCallbackWasCalled = true;
+ return true;
+}
+
+bool cancelTerminateCallbackWasCalled = false;
+static bool cancelTerminateCallback(JSContextRef ctx, void* context)
+{
+ UNUSED_PARAM(ctx);
+ UNUSED_PARAM(context);
+ cancelTerminateCallbackWasCalled = true;
+ return false;
+}
+
+int extendTerminateCallbackCalled = 0;
+static bool extendTerminateCallback(JSContextRef ctx, void* context)
+{
+ UNUSED_PARAM(context);
+ extendTerminateCallbackCalled++;
+ if (extendTerminateCallbackCalled == 1) {
+ JSContextGroupRef contextGroup = JSContextGetGroup(ctx);
+ JSContextGroupSetExecutionTimeLimit(contextGroup, .200f, extendTerminateCallback, 0);
+ return false;
+ }
+ return true;
+}
+#endif /* PLATFORM(MAC) || PLATFORM(IOS) */
+
+
int main(int argc, char* argv[])
{
#if OS(WINDOWS)
@@ -1039,6 +1122,10 @@ int main(int argc, char* argv[])
::SetErrorMode(0);
#endif
+#if JSC_OBJC_API_ENABLED
+ testObjectiveCAPI();
+#endif
+
const char *scriptPath = "testapi.js";
if (argc > 1) {
scriptPath = argv[1];
@@ -1061,6 +1148,8 @@ int main(int argc, char* argv[])
JSClassRef globalObjectClass = JSClassCreate(&globalObjectClassDefinition);
context = JSGlobalContextCreateInGroup(NULL, globalObjectClass);
+ JSContextGroupRef contextGroup = JSContextGetGroup(context);
+
JSGlobalContextRetain(context);
JSGlobalContextRelease(context);
ASSERT(JSContextGetGlobalContext(context) == context);
@@ -1117,6 +1206,12 @@ int main(int argc, char* argv[])
free(buffer);
JSValueRef jsCFEmptyStringWithCharacters = JSValueMakeString(context, jsCFEmptyIStringWithCharacters);
+ JSChar constantString[] = { 'H', 'e', 'l', 'l', 'o', };
+ JSStringRef constantStringRef = JSStringCreateWithCharactersNoCopy(constantString, sizeof(constantString) / sizeof(constantString[0]));
+ ASSERT(JSStringGetCharactersPtr(constantStringRef) == constantString);
+ JSStringRelease(constantStringRef);
+
+ ASSERT(JSValueGetType(context, NULL) == kJSTypeNull);
ASSERT(JSValueGetType(context, jsUndefined) == kJSTypeUndefined);
ASSERT(JSValueGetType(context, jsNull) == kJSTypeNull);
ASSERT(JSValueGetType(context, jsTrue) == kJSTypeBoolean);
@@ -1131,6 +1226,17 @@ int main(int argc, char* argv[])
ASSERT(JSValueGetType(context, jsCFEmptyString) == kJSTypeString);
ASSERT(JSValueGetType(context, jsCFEmptyStringWithCharacters) == kJSTypeString);
+ ASSERT(!JSValueIsBoolean(context, NULL));
+ ASSERT(!JSValueIsObject(context, NULL));
+ ASSERT(!JSValueIsString(context, NULL));
+ ASSERT(!JSValueIsNumber(context, NULL));
+ ASSERT(!JSValueIsUndefined(context, NULL));
+ ASSERT(JSValueIsNull(context, NULL));
+ ASSERT(!JSObjectCallAsFunction(context, NULL, NULL, 0, NULL, NULL));
+ ASSERT(!JSObjectCallAsConstructor(context, NULL, 0, NULL, NULL));
+ ASSERT(!JSObjectIsConstructor(context, NULL));
+ ASSERT(!JSObjectIsFunction(context, NULL));
+
JSStringRef nullString = JSStringCreateWithUTF8CString(0);
const JSChar* characters = JSStringGetCharactersPtr(nullString);
if (characters) {
@@ -1139,6 +1245,14 @@ int main(int argc, char* argv[])
} else
printf("PASS: returned null when accessing character pointer of a null String.\n");
+ JSStringRef emptyString = JSStringCreateWithCFString(CFSTR(""));
+ characters = JSStringGetCharactersPtr(emptyString);
+ if (!characters) {
+ printf("FAIL: Returned null when accessing character pointer of an empty String.\n");
+ failed = 1;
+ } else
+ printf("PASS: returned empty when accessing character pointer of an empty String.\n");
+
size_t length = JSStringGetLength(nullString);
if (length) {
printf("FAIL: Didn't return 0 length for null String.\n");
@@ -1147,6 +1261,14 @@ int main(int argc, char* argv[])
printf("PASS: returned 0 length for null String.\n");
JSStringRelease(nullString);
+ length = JSStringGetLength(emptyString);
+ if (length) {
+ printf("FAIL: Didn't return 0 length for empty String.\n");
+ failed = 1;
+ } else
+ printf("PASS: returned 0 length for empty String.\n");
+ JSStringRelease(emptyString);
+
JSObjectRef propertyCatchalls = JSObjectMake(context, PropertyCatchalls_class(context), NULL);
JSStringRef propertyCatchallsString = JSStringCreateWithUTF8CString("PropertyCatchalls");
JSObjectSetProperty(context, globalObject, propertyCatchallsString, propertyCatchalls, kJSPropertyAttributeNone, NULL);
@@ -1399,9 +1521,12 @@ int main(int argc, char* argv[])
JSValueUnprotect(context, jsNumberValue);
JSStringRef goodSyntax = JSStringCreateWithUTF8CString("x = 1;");
- JSStringRef badSyntax = JSStringCreateWithUTF8CString("x := 1;");
+ const char* badSyntaxConstant = "x := 1;";
+ JSStringRef badSyntax = JSStringCreateWithUTF8CString(badSyntaxConstant);
ASSERT(JSCheckScriptSyntax(context, goodSyntax, NULL, 0, NULL));
ASSERT(!JSCheckScriptSyntax(context, badSyntax, NULL, 0, NULL));
+ ASSERT(!JSScriptCreateFromString(contextGroup, 0, 0, badSyntax, 0, 0));
+ ASSERT(!JSScriptCreateReferencingImmortalASCIIText(contextGroup, 0, 0, badSyntaxConstant, strlen(badSyntaxConstant), 0, 0));
JSValueRef result;
JSValueRef v;
@@ -1590,13 +1715,21 @@ int main(int argc, char* argv[])
v = JSObjectCallAsFunction(context, function, o, 0, NULL, NULL);
ASSERT(JSValueIsEqual(context, v, o, NULL));
- JSStringRef script = JSStringCreateWithUTF8CString("this;");
+ const char* thisScript = "this;";
+ JSStringRef script = JSStringCreateWithUTF8CString(thisScript);
v = JSEvaluateScript(context, script, NULL, NULL, 1, NULL);
ASSERT(JSValueIsEqual(context, v, globalObject, NULL));
v = JSEvaluateScript(context, script, o, NULL, 1, NULL);
ASSERT(JSValueIsEqual(context, v, o, NULL));
JSStringRelease(script);
+ JSScriptRef scriptObject = JSScriptCreateReferencingImmortalASCIIText(contextGroup, 0, 0, thisScript, strlen(thisScript), 0, 0);
+ v = JSScriptEvaluate(context, scriptObject, NULL, NULL);
+ ASSERT(JSValueIsEqual(context, v, globalObject, NULL));
+ v = JSScriptEvaluate(context, scriptObject, o, NULL);
+ ASSERT(JSValueIsEqual(context, v, o, NULL));
+ JSScriptRelease(scriptObject);
+
script = JSStringCreateWithUTF8CString("eval(this);");
v = JSEvaluateScript(context, script, NULL, NULL, 1, NULL);
ASSERT(JSValueIsEqual(context, v, globalObject, NULL));
@@ -1616,8 +1749,23 @@ int main(int argc, char* argv[])
printf("FAIL: Test script could not be loaded.\n");
failed = 1;
} else {
- script = JSStringCreateWithUTF8CString(scriptUTF8);
- result = JSEvaluateScript(context, script, NULL, NULL, 1, &exception);
+ JSStringRef url = JSStringCreateWithUTF8CString(scriptPath);
+ JSStringRef script = JSStringCreateWithUTF8CString(scriptUTF8);
+ JSStringRef errorMessage = 0;
+ int errorLine = 0;
+ JSScriptRef scriptObject = JSScriptCreateFromString(contextGroup, url, 1, script, &errorMessage, &errorLine);
+ ASSERT((!scriptObject) != (!errorMessage));
+ if (!scriptObject) {
+ printf("FAIL: Test script did not parse\n\t%s:%d\n\t", scriptPath, errorLine);
+ CFStringRef errorCF = JSStringCopyCFString(kCFAllocatorDefault, errorMessage);
+ CFShow(errorCF);
+ CFRelease(errorCF);
+ JSStringRelease(errorMessage);
+ failed = 1;
+ }
+
+ JSStringRelease(script);
+ result = scriptObject ? JSScriptEvaluate(context, scriptObject, 0, &exception) : 0;
if (result && JSValueIsUndefined(context, result))
printf("PASS: Test script executed successfully.\n");
else {
@@ -1629,10 +1777,163 @@ int main(int argc, char* argv[])
JSStringRelease(exceptionIString);
failed = 1;
}
- JSStringRelease(script);
+ JSScriptRelease(scriptObject);
free(scriptUTF8);
}
+#if PLATFORM(MAC) || PLATFORM(IOS)
+ JSStringRef currentCPUTimeStr = JSStringCreateWithUTF8CString("currentCPUTime");
+ JSObjectRef currentCPUTimeFunction = JSObjectMakeFunctionWithCallback(context, currentCPUTimeStr, currentCPUTime_callAsFunction);
+ JSObjectSetProperty(context, globalObject, currentCPUTimeStr, currentCPUTimeFunction, kJSPropertyAttributeNone, NULL);
+ JSStringRelease(currentCPUTimeStr);
+
+ /* Test script timeout: */
+ JSContextGroupSetExecutionTimeLimit(contextGroup, .10f, shouldTerminateCallback, 0);
+ {
+ const char* loopForeverScript = "var startTime = currentCPUTime(); while (true) { if (currentCPUTime() - startTime > .150) break; } ";
+ JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript);
+ double startTime;
+ double endTime;
+ exception = NULL;
+ shouldTerminateCallbackWasCalled = false;
+ startTime = currentCPUTime();
+ v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception);
+ endTime = currentCPUTime();
+
+ if (((endTime - startTime) < .150f) && shouldTerminateCallbackWasCalled)
+ printf("PASS: script timed out as expected.\n");
+ else {
+ if (!((endTime - startTime) < .150f))
+ printf("FAIL: script did not timed out as expected.\n");
+ if (!shouldTerminateCallbackWasCalled)
+ printf("FAIL: script timeout callback was not called.\n");
+ failed = true;
+ }
+
+ if (!exception) {
+ printf("FAIL: TerminatedExecutionException was not thrown.\n");
+ failed = true;
+ }
+ }
+
+ /* Test the script timeout's TerminatedExecutionException should NOT be catchable: */
+ JSContextGroupSetExecutionTimeLimit(contextGroup, 0.10f, shouldTerminateCallback, 0);
+ {
+ const char* loopForeverScript = "var startTime = currentCPUTime(); try { while (true) { if (currentCPUTime() - startTime > .150) break; } } catch(e) { }";
+ JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript);
+ double startTime;
+ double endTime;
+ exception = NULL;
+ shouldTerminateCallbackWasCalled = false;
+ startTime = currentCPUTime();
+ v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception);
+ endTime = currentCPUTime();
+
+ if (((endTime - startTime) >= .150f) || !shouldTerminateCallbackWasCalled) {
+ if (!((endTime - startTime) < .150f))
+ printf("FAIL: script did not timed out as expected.\n");
+ if (!shouldTerminateCallbackWasCalled)
+ printf("FAIL: script timeout callback was not called.\n");
+ failed = true;
+ }
+
+ if (exception)
+ printf("PASS: TerminatedExecutionException was not catchable as expected.\n");
+ else {
+ printf("FAIL: TerminatedExecutionException was caught.\n");
+ failed = true;
+ }
+ }
+
+ /* Test script timeout with no callback: */
+ JSContextGroupSetExecutionTimeLimit(contextGroup, .10f, 0, 0);
+ {
+ const char* loopForeverScript = "var startTime = currentCPUTime(); while (true) { if (currentCPUTime() - startTime > .150) break; } ";
+ JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript);
+ double startTime;
+ double endTime;
+ exception = NULL;
+ startTime = currentCPUTime();
+ v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception);
+ endTime = currentCPUTime();
+
+ if (((endTime - startTime) < .150f) && shouldTerminateCallbackWasCalled)
+ printf("PASS: script timed out as expected when no callback is specified.\n");
+ else {
+ if (!((endTime - startTime) < .150f))
+ printf("FAIL: script did not timed out as expected when no callback is specified.\n");
+ failed = true;
+ }
+
+ if (!exception) {
+ printf("FAIL: TerminatedExecutionException was not thrown.\n");
+ failed = true;
+ }
+ }
+
+ /* Test script timeout cancellation: */
+ JSContextGroupSetExecutionTimeLimit(contextGroup, 0.10f, cancelTerminateCallback, 0);
+ {
+ const char* loopForeverScript = "var startTime = currentCPUTime(); while (true) { if (currentCPUTime() - startTime > .150) break; } ";
+ JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript);
+ double startTime;
+ double endTime;
+ exception = NULL;
+ startTime = currentCPUTime();
+ v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception);
+ endTime = currentCPUTime();
+
+ if (((endTime - startTime) >= .150f) && cancelTerminateCallbackWasCalled && !exception)
+ printf("PASS: script timeout was cancelled as expected.\n");
+ else {
+ if (((endTime - startTime) < .150) || exception)
+ printf("FAIL: script timeout was not cancelled.\n");
+ if (!cancelTerminateCallbackWasCalled)
+ printf("FAIL: script timeout callback was not called.\n");
+ failed = true;
+ }
+
+ if (exception) {
+ printf("FAIL: Unexpected TerminatedExecutionException thrown.\n");
+ failed = true;
+ }
+ }
+
+ /* Test script timeout extension: */
+ JSContextGroupSetExecutionTimeLimit(contextGroup, 0.100f, extendTerminateCallback, 0);
+ {
+ const char* loopForeverScript = "var startTime = currentCPUTime(); while (true) { if (currentCPUTime() - startTime > .500) break; } ";
+ JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript);
+ double startTime;
+ double endTime;
+ double deltaTime;
+ exception = NULL;
+ startTime = currentCPUTime();
+ v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception);
+ endTime = currentCPUTime();
+ deltaTime = endTime - startTime;
+
+ if ((deltaTime >= .300f) && (deltaTime < .500f) && (extendTerminateCallbackCalled == 2) && exception)
+ printf("PASS: script timeout was extended as expected.\n");
+ else {
+ if (deltaTime < .200f)
+ printf("FAIL: script timeout was not extended as expected.\n");
+ else if (deltaTime >= .500f)
+ printf("FAIL: script did not timeout.\n");
+
+ if (extendTerminateCallbackCalled < 1)
+ printf("FAIL: script timeout callback was not called.\n");
+ if (extendTerminateCallbackCalled < 2)
+ printf("FAIL: script timeout callback was not called after timeout extension.\n");
+
+ if (!exception)
+ printf("FAIL: TerminatedExecutionException was not thrown during timeout extension test.\n");
+
+ failed = true;
+ }
+ }
+#endif /* PLATFORM(MAC) || PLATFORM(IOS) */
+
// Clear out local variables pointing at JSObjectRefs to allow their values to be collected
function = NULL;
v = NULL;
diff --git a/Source/JavaScriptCore/API/tests/testapi.js b/Source/JavaScriptCore/API/tests/testapi.js
index 28fa54433..47c20a830 100644
--- a/Source/JavaScriptCore/API/tests/testapi.js
+++ b/Source/JavaScriptCore/API/tests/testapi.js
@@ -262,6 +262,10 @@ shouldBe("PropertyCatchalls.x", 4);
for (var i = 0; i < 6; ++i)
var x = PropertyCatchalls.x;
shouldBe("x", null);
+var make_throw = 'make_throw';
+shouldThrow("PropertyCatchalls[make_throw]=1");
+make_throw = 0;
+shouldThrow("PropertyCatchalls[make_throw]=1");
for (var i = 0; i < 10; ++i) {
for (var p in PropertyCatchalls) {
diff --git a/Source/JavaScriptCore/API/tests/testapi.mm b/Source/JavaScriptCore/API/tests/testapi.mm
new file mode 100644
index 000000000..defb46e70
--- /dev/null
+++ b/Source/JavaScriptCore/API/tests/testapi.mm
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <JavaScriptCore/JavaScriptCore.h>
+
+extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef);
+
+extern "C" bool _Block_has_signature(id);
+extern "C" const char * _Block_signature(id);
+
+extern int failed;
+extern "C" void testObjectiveCAPI(void);
+
+#if JSC_OBJC_API_ENABLED
+
+@protocol ParentObject <JSExport>
+@end
+
+@interface ParentObject : NSObject<ParentObject>
++ (NSString *)parentTest;
+@end
+
+@implementation ParentObject
++ (NSString *)parentTest
+{
+ return [self description];
+}
+@end
+
+@protocol TestObject <JSExport>
+@property int variable;
+@property (readonly) int six;
+@property CGPoint point;
++ (NSString *)classTest;
++ (NSString *)parentTest;
+- (NSString *)getString;
+JSExportAs(testArgumentTypes,
+- (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
+);
+- (void)callback:(JSValue *)function;
+- (void)bogusCallback:(void(^)(int))function;
+@end
+
+@interface TestObject : ParentObject <TestObject>
+@property int six;
++ (id)testObject;
+@end
+
+@implementation TestObject
+@synthesize variable;
+@synthesize six;
+@synthesize point;
++ (id)testObject
+{
+ return [[TestObject alloc] init];
+}
++ (NSString *)classTest
+{
+ return @"classTest - okay";
+}
+- (NSString *)getString
+{
+ return @"42";
+}
+- (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
+{
+ return [NSString stringWithFormat:@"%d,%g,%d,%@,%d,%@,%@", i, d, b==YES?true:false,s,[n intValue],a[1],o[@"x"]];
+}
+- (void)callback:(JSValue *)function
+{
+ [function callWithArguments:[NSArray arrayWithObject:[NSNumber numberWithInt:42]]];
+}
+- (void)bogusCallback:(void(^)(int))function
+{
+ function(42);
+}
+@end
+
+bool testXYZTested = false;
+
+@protocol TextXYZ <JSExport>
+@property int x;
+@property (readonly) int y;
+@property (assign) JSValue *onclick;
+@property (assign) JSValue *weakOnclick;
+- (void)test:(NSString *)message;
+@end
+
+@interface TextXYZ : NSObject <TextXYZ>
+@property int x;
+@property int y;
+@property int z;
+- (void)click;
+@end
+
+@implementation TextXYZ {
+ JSManagedValue *m_weakOnclickHandler;
+ JSManagedValue *m_onclickHandler;
+}
+@synthesize x;
+@synthesize y;
+@synthesize z;
+- (void)test:(NSString *)message
+{
+ testXYZTested = [message isEqual:@"test"] && x == 13 & y == 4 && z == 5;
+}
+- (void)setWeakOnclick:(JSValue *)value
+{
+ m_weakOnclickHandler = [JSManagedValue managedValueWithValue:value];
+}
+
+- (void)setOnclick:(JSValue *)value
+{
+ m_onclickHandler = [JSManagedValue managedValueWithValue:value];
+ [value.context.virtualMachine addManagedReference:m_onclickHandler withOwner:self];
+}
+- (JSValue *)weakOnclick
+{
+ return [m_weakOnclickHandler value];
+}
+- (JSValue *)onclick
+{
+ return [m_onclickHandler value];
+}
+- (void)click
+{
+ if (!m_onclickHandler)
+ return;
+
+ JSValue *function = [m_onclickHandler value];
+ [function callWithArguments:[NSArray array]];
+}
+- (void)dealloc
+{
+ [[m_onclickHandler value].context.virtualMachine removeManagedReference:m_onclickHandler withOwner:self];
+}
+@end
+
+@class TinyDOMNode;
+
+@protocol TinyDOMNode <JSExport>
+- (void)appendChild:(TinyDOMNode *)child;
+- (NSUInteger)numberOfChildren;
+- (TinyDOMNode *)childAtIndex:(NSUInteger)index;
+- (void)removeChildAtIndex:(NSUInteger)index;
+@end
+
+@interface TinyDOMNode : NSObject<TinyDOMNode>
++ (JSVirtualMachine *)sharedVirtualMachine;
++ (void)clearSharedVirtualMachine;
+@end
+
+@implementation TinyDOMNode {
+ NSMutableArray *m_children;
+}
+
+static JSVirtualMachine *sharedInstance = nil;
+
++ (JSVirtualMachine *)sharedVirtualMachine
+{
+ if (!sharedInstance)
+ sharedInstance = [[JSVirtualMachine alloc] init];
+ return sharedInstance;
+}
+
++ (void)clearSharedVirtualMachine
+{
+ sharedInstance = nil;
+}
+
+- (id)init
+{
+ self = [super init];
+ if (!self)
+ return nil;
+
+ m_children = [[NSMutableArray alloc] initWithCapacity:0];
+
+ return self;
+}
+
+- (void)dealloc
+{
+ NSEnumerator *enumerator = [m_children objectEnumerator];
+ id nextChild;
+ while ((nextChild = [enumerator nextObject]))
+ [[TinyDOMNode sharedVirtualMachine] removeManagedReference:nextChild withOwner:self];
+
+#if !__has_feature(objc_arc)
+ [super dealloc];
+#endif
+}
+
+- (void)appendChild:(TinyDOMNode *)child
+{
+ [[TinyDOMNode sharedVirtualMachine] addManagedReference:child withOwner:self];
+ [m_children addObject:child];
+}
+
+- (NSUInteger)numberOfChildren
+{
+ return [m_children count];
+}
+
+- (TinyDOMNode *)childAtIndex:(NSUInteger)index
+{
+ if (index >= [m_children count])
+ return nil;
+ return [m_children objectAtIndex:index];
+}
+
+- (void)removeChildAtIndex:(NSUInteger)index
+{
+ if (index >= [m_children count])
+ return;
+ [[TinyDOMNode sharedVirtualMachine] removeManagedReference:[m_children objectAtIndex:index] withOwner:self];
+ [m_children removeObjectAtIndex:index];
+}
+
+@end
+
+static void checkResult(NSString *description, bool passed)
+{
+ NSLog(@"TEST: \"%@\": %@", description, passed ? @"PASSED" : @"FAILED");
+ if (!passed)
+ failed = 1;
+}
+
+static bool blockSignatureContainsClass()
+{
+ static bool containsClass = ^{
+ id block = ^(NSString *string){ return string; };
+ return _Block_has_signature(block) && strstr(_Block_signature(block), "NSString");
+ }();
+ return containsClass;
+}
+
+void testObjectiveCAPI()
+{
+ NSLog(@"Testing Objective-C API");
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ JSValue *result = [context evaluateScript:@"2 + 2"];
+ checkResult(@"2 + 2", [result isNumber] && [result toInt32] == 4);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ NSString *result = [NSString stringWithFormat:@"Two plus two is %@", [context evaluateScript:@"2 + 2"]];
+ checkResult(@"stringWithFormat", [result isEqual:@"Two plus two is 4"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"message"] = @"Hello";
+ JSValue *result = [context evaluateScript:@"message + ', World!'"];
+ checkResult(@"Hello, World!", [result isString] && [result isEqualToObject:@"Hello, World!"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ JSValue *result = [context evaluateScript:@"({ x:42 })"];
+ checkResult(@"({ x:42 })", [result isObject] && [result[@"x"] isEqualToObject:@42]);
+ id obj = [result toObject];
+ checkResult(@"Check dictionary literal", [obj isKindOfClass:[NSDictionary class]]);
+ id num = (NSDictionary *)obj[@"x"];
+ checkResult(@"Check numeric literal", [num isKindOfClass:[NSNumber class]]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ __block int result;
+ context[@"blockCallback"] = ^(int value){
+ result = value;
+ };
+ [context evaluateScript:@"blockCallback(42)"];
+ checkResult(@"blockCallback", result == 42);
+ }
+
+ if (blockSignatureContainsClass()) {
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ __block bool result = false;
+ context[@"blockCallback"] = ^(NSString *value){
+ result = [@"42" isEqualToString:value] == YES;
+ };
+ [context evaluateScript:@"blockCallback(42)"];
+ checkResult(@"blockCallback(NSString *)", result);
+ }
+ } else
+ NSLog(@"Skipping 'blockCallback(NSString *)' test case");
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ checkResult(@"!context.exception", !context.exception);
+ [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
+ checkResult(@"context.exception", context.exception);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ __block bool caught = false;
+ context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
+ (void)context;
+ (void)exception;
+ caught = true;
+ };
+ [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
+ checkResult(@"JSContext.exceptionHandler", caught);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"callback"] = ^{
+ JSContext *context = [JSContext currentContext];
+ context.exception = [JSValue valueWithNewErrorFromMessage:@"Something went wrong." inContext:context];
+ };
+ JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
+ checkResult(@"Explicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
+ checkResult(@"Explicit throw in callback - not thrown to Objective-C", !context.exception);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"callback"] = ^{
+ JSContext *context = [JSContext currentContext];
+ [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
+ };
+ JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
+ checkResult(@"Implicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
+ checkResult(@"Implicit throw in callback - not thrown to Objective-C", !context.exception);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ [context evaluateScript:
+ @"function sum(array) { \
+ var result = 0; \
+ for (var i in array) \
+ result += array[i]; \
+ return result; \
+ }"];
+ JSValue *array = [JSValue valueWithObject:@[@13, @2, @7] inContext:context];
+ JSValue *sumFunction = context[@"sum"];
+ JSValue *result = [sumFunction callWithArguments:@[ array ]];
+ checkResult(@"sum([13, 2, 7])", [result toInt32] == 22);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ JSValue *mulAddFunction = [context evaluateScript:
+ @"(function(array, object) { \
+ var result = []; \
+ for (var i in array) \
+ result.push(array[i] * object.x + object.y); \
+ return result; \
+ })"];
+ JSValue *result = [mulAddFunction callWithArguments:@[ @[ @2, @4, @8 ], @{ @"x":@0.5, @"y":@42 } ]];
+ checkResult(@"mulAddFunction", [result isObject] && [[result toString] isEqual:@"43,44,46"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ JSValue *array = [JSValue valueWithNewArrayInContext:context];
+ checkResult(@"arrayLengthEmpty", [[array[@"length"] toNumber] unsignedIntegerValue] == 0);
+ JSValue *value1 = [JSValue valueWithInt32:42 inContext:context];
+ JSValue *value2 = [JSValue valueWithInt32:24 inContext:context];
+ NSUInteger lowIndex = 5;
+ NSUInteger maxLength = UINT_MAX;
+
+ [array setValue:value1 atIndex:lowIndex];
+ checkResult(@"array.length after put to low index", [[array[@"length"] toNumber] unsignedIntegerValue] == (lowIndex + 1));
+
+ [array setValue:value1 atIndex:(maxLength - 1)];
+ checkResult(@"array.length after put to maxLength - 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
+
+ [array setValue:value2 atIndex:maxLength];
+ checkResult(@"array.length after put to maxLength", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
+
+ [array setValue:value2 atIndex:(maxLength + 1)];
+ checkResult(@"array.length after put to maxLength + 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
+
+ checkResult(@"valueAtIndex:0 is undefined", [[array valueAtIndex:0] isUndefined]);
+ checkResult(@"valueAtIndex:lowIndex", [[array valueAtIndex:lowIndex] toInt32] == 42);
+ checkResult(@"valueAtIndex:maxLength - 1", [[array valueAtIndex:(maxLength - 1)] toInt32] == 42);
+ checkResult(@"valueAtIndex:maxLength", [[array valueAtIndex:maxLength] toInt32] == 24);
+ checkResult(@"valueAtIndex:maxLength + 1", [[array valueAtIndex:(maxLength + 1)] toInt32] == 24);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ JSValue *object = [JSValue valueWithNewObjectInContext:context];
+
+ object[@"point"] = @{ @"x":@1, @"y":@2 };
+ object[@"point"][@"x"] = @3;
+ CGPoint point = [object[@"point"] toPoint];
+ checkResult(@"toPoint", point.x == 3 && point.y == 2);
+
+ object[@{ @"toString":^{ return @"foo"; } }] = @"bar";
+ checkResult(@"toString in object literal used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
+
+ object[[@"foobar" substringToIndex:3]] = @"bar";
+ checkResult(@"substring used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TextXYZ *testXYZ = [[TextXYZ alloc] init];
+ context[@"testXYZ"] = testXYZ;
+ testXYZ.x = 3;
+ testXYZ.y = 4;
+ testXYZ.z = 5;
+ [context evaluateScript:@"testXYZ.x = 13; testXYZ.y = 14;"];
+ [context evaluateScript:@"testXYZ.test('test')"];
+ checkResult(@"TextXYZ - testXYZTested", testXYZTested);
+ JSValue *result = [context evaluateScript:@"testXYZ.x + ',' + testXYZ.y + ',' + testXYZ.z"];
+ checkResult(@"TextXYZ - result", [result isEqualToObject:@"13,4,undefined"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ [context[@"Object"][@"prototype"] defineProperty:@"getterProperty" descriptor:@{
+ JSPropertyDescriptorGetKey:^{
+ return [JSContext currentThis][@"x"];
+ }
+ }];
+ JSValue *object = [JSValue valueWithObject:@{ @"x":@101 } inContext:context];
+ int result = [object [@"getterProperty"] toInt32];
+ checkResult(@"getterProperty", result == 101);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"concatenate"] = ^{
+ NSArray *arguments = [JSContext currentArguments];
+ if (![arguments count])
+ return @"";
+ NSString *message = [arguments[0] description];
+ for (NSUInteger index = 1; index < [arguments count]; ++index)
+ message = [NSString stringWithFormat:@"%@ %@", message, arguments[index]];
+ return message;
+ };
+ JSValue *result = [context evaluateScript:@"concatenate('Hello,', 'World!')"];
+ checkResult(@"concatenate", [result isEqualToObject:@"Hello, World!"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"foo"] = @YES;
+ checkResult(@"@YES is boolean", [context[@"foo"] isBoolean]);
+ JSValue *result = [context evaluateScript:@"typeof foo"];
+ checkResult(@"@YES is boolean", [result isEqualToObject:@"boolean"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject* testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ JSValue *result = [context evaluateScript:@"String(testObject)"];
+ checkResult(@"String(testObject)", [result isEqualToObject:@"[object TestObject]"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject* testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ JSValue *result = [context evaluateScript:@"String(testObject.__proto__)"];
+ checkResult(@"String(testObject.__proto__)", [result isEqualToObject:@"[object TestObjectPrototype]"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"TestObject"] = [TestObject class];
+ JSValue *result = [context evaluateScript:@"String(TestObject)"];
+ checkResult(@"String(TestObject)", [result isEqualToObject:@"[object TestObjectConstructor]"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ JSValue* value = [JSValue valueWithObject:[TestObject class] inContext:context];
+ checkResult(@"[value toObject] == [TestObject class]", [value toObject] == [TestObject class]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"TestObject"] = [TestObject class];
+ JSValue *result = [context evaluateScript:@"TestObject.parentTest()"];
+ checkResult(@"TestObject.parentTest()", [result isEqualToObject:@"TestObject"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject* testObject = [TestObject testObject];
+ context[@"testObjectA"] = testObject;
+ context[@"testObjectB"] = testObject;
+ JSValue *result = [context evaluateScript:@"testObjectA == testObjectB"];
+ checkResult(@"testObjectA == testObjectB", [result isBoolean] && [result toBool]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject* testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ testObject.point = (CGPoint){3,4};
+ JSValue *result = [context evaluateScript:@"var result = JSON.stringify(testObject.point); testObject.point = {x:12,y:14}; result"];
+ checkResult(@"testObject.point - result", [result isEqualToObject:@"{\"x\":3,\"y\":4}"]);
+ checkResult(@"testObject.point - {x:12,y:14}", testObject.point.x == 12 && testObject.point.y == 14);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject* testObject = [TestObject testObject];
+ testObject.six = 6;
+ context[@"testObject"] = testObject;
+ context[@"mul"] = ^(int x, int y){ return x * y; };
+ JSValue *result = [context evaluateScript:@"mul(testObject.six, 7)"];
+ checkResult(@"mul(testObject.six, 7)", [result isNumber] && [result toInt32] == 42);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject* testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ context[@"testObject"][@"variable"] = @4;
+ [context evaluateScript:@"++testObject.variable"];
+ checkResult(@"++testObject.variable", testObject.variable == 5);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"point"] = @{ @"x":@6, @"y":@7 };
+ JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
+ checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"point"] = @{ @"x":@6, @"y":@7 };
+ JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
+ checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject* testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ JSValue *result = [context evaluateScript:@"testObject.getString()"];
+ checkResult(@"testObject.getString()", [result isString] && [result toInt32] == 42);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject* testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ JSValue *result = [context evaluateScript:@"testObject.testArgumentTypes(101,0.5,true,'foo',666,[false,'bar',false],{x:'baz'})"];
+ checkResult(@"testObject.testArgumentTypes", [result isEqualToObject:@"101,0.5,1,foo,666,bar,baz"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject* testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ JSValue *result = [context evaluateScript:@"testObject.getString.call(testObject)"];
+ checkResult(@"testObject.getString.call(testObject)", [result isString] && [result toInt32] == 42);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject* testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ checkResult(@"testObject.getString.call({}) pre", !context.exception);
+ [context evaluateScript:@"testObject.getString.call({})"];
+ checkResult(@"testObject.getString.call({}) post", context.exception);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject* testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ JSValue *result = [context evaluateScript:@"var result = 0; testObject.callback(function(x){ result = x; }); result"];
+ checkResult(@"testObject.callback", [result isNumber] && [result toInt32] == 42);
+ result = [context evaluateScript:@"testObject.bogusCallback"];
+ checkResult(@"testObject.bogusCallback == undefined", [result isUndefined]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject *testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ JSValue *result = [context evaluateScript:@"Function.prototype.toString.call(testObject.callback)"];
+ checkResult(@"Function.prototype.toString", !context.exception && ![result isUndefined]);
+ }
+
+ @autoreleasepool {
+ JSContext *context1 = [[JSContext alloc] init];
+ JSContext *context2 = [[JSContext alloc] initWithVirtualMachine:context1.virtualMachine];
+ JSValue *value = [JSValue valueWithDouble:42 inContext:context2];
+ context1[@"passValueBetweenContexts"] = value;
+ JSValue *result = [context1 evaluateScript:@"passValueBetweenContexts"];
+ checkResult(@"[value isEqualToObject:result]", [value isEqualToObject:result]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"handleTheDictionary"] = ^(NSDictionary *dict) {
+ NSDictionary *expectedDict = @{
+ @"foo" : [NSNumber numberWithInt:1],
+ @"bar" : @{
+ @"baz": [NSNumber numberWithInt:2]
+ }
+ };
+ checkResult(@"recursively convert nested dictionaries", [dict isEqualToDictionary:expectedDict]);
+ };
+ [context evaluateScript:@"var myDict = { \
+ 'foo': 1, \
+ 'bar': {'baz': 2} \
+ }; \
+ handleTheDictionary(myDict);"];
+
+ context[@"handleTheArray"] = ^(NSArray *array) {
+ NSArray *expectedArray = @[@"foo", @"bar", @[@"baz"]];
+ checkResult(@"recursively convert nested arrays", [array isEqualToArray:expectedArray]);
+ };
+ [context evaluateScript:@"var myArray = ['foo', 'bar', ['baz']]; handleTheArray(myArray);"];
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject *testObject = [TestObject testObject];
+ @autoreleasepool {
+ context[@"testObject"] = testObject;
+ [context evaluateScript:@"var constructor = Object.getPrototypeOf(testObject).constructor; constructor.prototype = undefined;"];
+ [context evaluateScript:@"testObject = undefined"];
+ }
+
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+
+ @autoreleasepool {
+ context[@"testObject"] = testObject;
+ }
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TextXYZ *testXYZ = [[TextXYZ alloc] init];
+
+ @autoreleasepool {
+ context[@"testXYZ"] = testXYZ;
+
+ [context evaluateScript:@" \
+ didClick = false; \
+ testXYZ.onclick = function() { \
+ didClick = true; \
+ }; \
+ \
+ testXYZ.weakOnclick = function() { \
+ return 'foo'; \
+ }; \
+ "];
+ }
+
+ @autoreleasepool {
+ [testXYZ click];
+ JSValue *result = [context evaluateScript:@"didClick"];
+ checkResult(@"Event handler onclick", [result toBool]);
+ }
+
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+
+ @autoreleasepool {
+ JSValue *result = [context evaluateScript:@"testXYZ.onclick"];
+ checkResult(@"onclick still around after GC", !([result isNull] || [result isUndefined]));
+ }
+
+
+ @autoreleasepool {
+ JSValue *result = [context evaluateScript:@"testXYZ.weakOnclick"];
+ checkResult(@"weakOnclick not around after GC", [result isNull] || [result isUndefined]);
+ }
+
+ @autoreleasepool {
+ [context evaluateScript:@" \
+ didClick = false; \
+ testXYZ = null; \
+ "];
+ }
+
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+
+ @autoreleasepool {
+ [testXYZ click];
+ JSValue *result = [context evaluateScript:@"didClick"];
+ checkResult(@"Event handler onclick doesn't fire", ![result toBool]);
+ }
+ }
+
+ @autoreleasepool {
+ JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
+ TestObject *testObject = [TestObject testObject];
+ JSManagedValue *weakValue;
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
+ context[@"testObject"] = testObject;
+ weakValue = [[JSManagedValue alloc] initWithValue:context[@"testObject"]];
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
+ context[@"testObject"] = testObject;
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+ checkResult(@"weak value == nil", ![weakValue value]);
+ checkResult(@"root is still alive", ![context[@"testObject"] isUndefined]);
+ }
+ }
+
+ @autoreleasepool {
+ JSVirtualMachine *vm = [TinyDOMNode sharedVirtualMachine];
+ JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
+ TinyDOMNode *root = [[TinyDOMNode alloc] init];
+ TinyDOMNode *lastNode = root;
+ for (NSUInteger i = 0; i < 3; i++) {
+ TinyDOMNode *newNode = [[TinyDOMNode alloc] init];
+ [lastNode appendChild:newNode];
+ lastNode = newNode;
+ }
+
+ @autoreleasepool {
+ context[@"root"] = root;
+ context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
+ TinyDOMNode *lastNode = nil;
+ while (head) {
+ lastNode = head;
+ head = [lastNode childAtIndex:0];
+ }
+ return lastNode;
+ };
+ [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
+ }
+
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+
+ JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
+ checkResult(@"My custom property == 42", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42);
+
+ [TinyDOMNode clearSharedVirtualMachine];
+ }
+
+ @autoreleasepool {
+ JSVirtualMachine *vm = [TinyDOMNode sharedVirtualMachine];
+ JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
+ TinyDOMNode *root = [[TinyDOMNode alloc] init];
+ TinyDOMNode *lastNode = root;
+ for (NSUInteger i = 0; i < 3; i++) {
+ TinyDOMNode *newNode = [[TinyDOMNode alloc] init];
+ [lastNode appendChild:newNode];
+ lastNode = newNode;
+ }
+
+ @autoreleasepool {
+ context[@"root"] = root;
+ context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
+ TinyDOMNode *lastNode = nil;
+ while (head) {
+ lastNode = head;
+ head = [lastNode childAtIndex:0];
+ }
+ return lastNode;
+ };
+ [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
+
+ [root appendChild:[root childAtIndex:0]];
+ [root removeChildAtIndex:0];
+ }
+
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+
+ JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
+ checkResult(@"duplicate calls to addManagedReference don't cause things to die", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42);
+
+ [TinyDOMNode clearSharedVirtualMachine];
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ JSValue *o = [JSValue valueWithNewObjectInContext:context];
+ o[@"foo"] = @"foo";
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+
+ checkResult(@"JSValue correctly protected its internal value", [[o[@"foo"] toString] isEqualToString:@"foo"]);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ TestObject *testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ [context evaluateScript:@"testObject.__lookupGetter__('variable').call({})"];
+ checkResult(@"Make sure we throw an exception when calling getter on incorrect |this|", context.exception);
+ }
+
+ @autoreleasepool {
+ TestObject *testObject = [TestObject testObject];
+ JSManagedValue *managedTestObject;
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"testObject"] = testObject;
+ managedTestObject = [JSManagedValue managedValueWithValue:context[@"testObject"]];
+ [context.virtualMachine addManagedReference:managedTestObject withOwner:testObject];
+ }
+ }
+}
+
+#else
+
+void testObjectiveCAPI()
+{
+}
+
+#endif