diff --git a/src/sbbs3/js_global.cpp b/src/sbbs3/js_global.cpp
index ef287cba5c04ea27c31c37f53c97173db9ba7348..665a3cb26ce14c02462a8aaed72204c504c9fece 100644
--- a/src/sbbs3/js_global.cpp
+++ b/src/sbbs3/js_global.cpp
@@ -232,7 +232,7 @@ js_log(JSContext *cx, unsigned argc, JS::Value *arglist)
 }
 
 /* Create a new value in the new context with a value from the original context */
-static JS::HandleValue js_CopyValue(JSContext* cx, JS::HandleValue val, JSContext* new_cx, JS::MutableHandleValue rval)
+JS::HandleValue DLLCALL js_CopyValue(JSContext* cx, JS::HandleValue val, JSContext* new_cx, JS::MutableHandleValue rval)
 {
 	JS::StructuredCloneScope scope(JS::StructuredCloneScope::SameProcessSameThread);
 	JSStructuredCloneData nval(scope);
diff --git a/src/sbbs3/js_internal.cpp b/src/sbbs3/js_internal.cpp
index c318f7003ba1552728e40ff55f7a3a7f51a99052..4472320d8428f7d7ea8a4a5c799455057e39584a 100644
--- a/src/sbbs3/js_internal.cpp
+++ b/src/sbbs3/js_internal.cpp
@@ -37,9 +37,7 @@
 
 #include "sbbs.h"
 #include "js_request.h"
-
-/* SpiderMonkey: */
-#include <jsdbgapi.h>
+#include "js_rtpool.h"
 
 enum {
 	 PROP_VERSION
@@ -61,7 +59,7 @@ enum {
 
 static JSObject *getCurrentCompartmentGlobalObject(JSContext *cx)
 {
-	JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL);
+	JS::RootedObject obj(cx, JS_NewPlainObject(cx));
 	JSCompartment *c;
 
 	c = JS_EnterCompartment(cx, obj);
@@ -71,92 +69,92 @@ static JSObject *getCurrentCompartmentGlobalObject(JSContext *cx)
 
 static bool js_get(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp)
 {
-	JS::Value idval;
-    int			tiny;
+	JS::RootedValue idval(cx);
+	int32_t tiny;
 	js_callback_t*	cb;
 	js_callback_t*	top_cb;
 
 	if((cb=(js_callback_t*)JS_GetPrivate(obj))==NULL)
 		return(false);
 
-    JS_IdToValue(cx, id, &idval);
-    tiny = JSVAL_TO_INT(idval);
+	JS_IdToValue(cx, id, &idval);
+	tiny = idval.toInt32();
 
 	switch(tiny) {
 		case PROP_VERSION:
-			vp.set(STRING_TO_JSVAL(JS_NewStringCopyZ(cx,(char *)JS_GetImplementationVersion())));
+			vp.setString(JS_NewStringCopyZ(cx,(char *)JS_GetImplementationVersion()));
 			break;
 		case PROP_TERMINATED:
 			for(top_cb=cb; top_cb->bg && top_cb->parent_cb; top_cb=top_cb->parent_cb) {
 				if(top_cb->terminated && *top_cb->terminated) {
-					vp.set(BOOLEAN_TO_JSVAL(TRUE));
+					vp.setBoolean(true);
 					return true;
 				}
 			}
 			if(top_cb->terminated==NULL)
-				vp.set(JSVAL_FALSE);
+				vp.setBoolean(false);
 			else
-				vp.set(BOOLEAN_TO_JSVAL(*top_cb->terminated));
+				vp.setBoolean(*top_cb->terminated);
 			break;
 		case PROP_AUTO_TERMINATE:
-			vp.set(BOOLEAN_TO_JSVAL(cb->auto_terminate));
+			vp.setBoolean(cb->auto_terminate);
 			break;
 		case PROP_COUNTER:
-			vp.set(DOUBLE_TO_JSVAL((double)cb->counter));
+			vp.setNumber((double)cb->counter);
 			break;
 		case PROP_TIME_LIMIT:
-			vp.set(DOUBLE_TO_JSVAL(cb->limit));
+			vp.setNumber(cb->limit);
 			break;
 		case PROP_YIELD_INTERVAL:
-			vp.set(DOUBLE_TO_JSVAL((double)cb->yield_interval));
+			vp.setNumber((double)cb->yield_interval);
 			break;
 		case PROP_GC_INTERVAL:
-			vp.set(DOUBLE_TO_JSVAL((double)cb->gc_interval));
+			vp.setNumber((double)cb->gc_interval);
 			break;
 		case PROP_GC_ATTEMPTS:
-			vp.set(DOUBLE_TO_JSVAL((double)cb->gc_attempts));
+			vp.setNumber((double)cb->gc_attempts);
 			break;
 #ifdef jscntxt_h___
 		case PROP_GC_COUNTER:
-			vp.set(UINT_TO_JSVAL(cx->runtime->gcNumber));
+			vp.setNumber(cx->runtime->gcNumber);
 			break;
 		case PROP_GC_LASTBYTES:
-			vp.set(DOUBLE_TO_JSVAL((double)cx->runtime->gcLastBytes));
+			vp.setNumber((double)cx->runtime->gcLastBytes);
 			break;
 		case PROP_BYTES:
-			vp.set(DOUBLE_TO_JSVAL((double)cx->runtime->gcBytes));
+			vp.setNumber((double)cx->runtime->gcBytes);
 			break;
 		case PROP_MAXBYTES:
-			vp.set(DOUBLE_TO_JSVAL((double)cx->runtime->gcMaxBytes));
+			vp.setNumber((double)cx->runtime->gcMaxBytes);
 			break;
 #endif
 		case PROP_GLOBAL:
-			vp.set(OBJECT_TO_JSVAL(getCurrentCompartmentGlobalObject(cx)));
+			vp.setObject(*getCurrentCompartmentGlobalObject(cx));
 			break;
 	}
 
 	return(true);
 }
 
-static bool js_set(JSContext *cx, JS::HandleObject obj, JS::HandleId id, bool strict, JS::MutableHandleValue vp)
+static bool js_set(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult &result)
 {
-	JS::Value idval;
-    int			tiny;
+	JS::RootedValue idval(cx);
+	int32_t		tiny;
 	js_callback_t*	cb;
 
 	if((cb=(js_callback_t*)JS_GetPrivate(obj))==NULL)
 		return(false);
 
-    JS_IdToValue(cx, id, &idval);
-    tiny = JSVAL_TO_INT(idval);
+	JS_IdToValue(cx, id, &idval);
+	tiny = idval.toInt32();
 
 	switch(tiny) {
 		case PROP_TERMINATED:
 			if(cb->terminated!=NULL)
-				*cb->terminated = JS::ToBoolean(vp);
+				*cb->terminated = vp.toBoolean();
 			break;
 		case PROP_AUTO_TERMINATE:
-			cb->auto_terminate = JS::ToBoolean(vp);
+			cb->auto_terminate = vp.toBoolean();
 			break;
 		case PROP_COUNTER:
 			if(!JS::ToInt32(cx, vp, (int32_t*)&cb->counter))
@@ -182,6 +180,7 @@ static bool js_set(JSContext *cx, JS::HandleObject obj, JS::HandleId id, bool st
 #endif
 	}
 
+	result.succeed();
 	return(true);
 }
 
@@ -256,7 +255,7 @@ js_CommonInterruptCallback(JSContext *cx, js_callback_t* cb)
 	if(cb->auto_terminate) {
 		for(top_cb=cb; top_cb; top_cb=top_cb->parent_cb) {
 			if (top_cb->terminated!=NULL && *top_cb->terminated) {
-				JS_ReportWarning(cx,"Terminated");
+				JS_ReportWarningASCII(cx,"Terminated");
 				cb->counter=0;
 				return(false);
 			}
@@ -265,7 +264,7 @@ js_CommonInterruptCallback(JSContext *cx, js_callback_t* cb)
 
 	/* Infinite loop? */
 	if(cb->limit && cb->counter > cb->limit) {
-		JS_ReportError(cx,"Infinite loop (%lu operation callbacks) detected",cb->counter);
+		JS_ReportErrorASCII(cx,"Infinite loop (%" PRIu32 " operation callbacks) detected",cb->counter);
 		cb->counter=0;
 		return(false);
 	}
@@ -284,7 +283,7 @@ js_CommonInterruptCallback(JSContext *cx, js_callback_t* cb)
 }
 
 // This is kind of halfway between js_execfile() in exec.cpp and js_load
-static int
+static bool
 js_execfile(JSContext *cx, unsigned argc, JS::Value *arglist)
 {
 	char*		cmd = NULL;
@@ -293,76 +292,72 @@ js_execfile(JSContext *cx, unsigned argc, JS::Value *arglist)
 	char*		startup_dir = NULL;
 	unsigned		arg=0;
 	char		path[MAX_PATH+1] = "";
-	JSObject*	js_scope = NULL;
-	JSObject*	pscope;
-	JSScript*	js_script=NULL;
-	JSObject*	nargv;
-	JS::Value		rval;
+	JS::RootedObject js_scope(cx, NULL);
+	JS::RootedObject pscope(cx);
+	JS::RootedScript	js_script(cx, NULL);
+	JS::RootedObject	nargv(cx);
+	JS::RootedValue		rval(cx);
 	unsigned		i;
-	JS::Value		val;
-	JSObject *js_obj;
-	JSObject *pjs_obj;
+	JS::RootedValue		val(cx);
+	JS::RootedObject js_obj(cx);
+	JS::RootedObject pjs_obj(cx);
 	js_callback_t *	js_callback;
 	JS::CallArgs args = JS::CallArgsFromVp(argc, arglist);
-	JSObject*       scope = JS_GetGlobalForObject(cx, &args.callee());
+	JS::RootedObject       scope(cx, JS_GetGlobalForObject(cx, &args.callee()));
 
 	if(argc<1) {
-		JS_ReportError(cx, "No filename passed");
+		JS_ReportErrorASCII(cx, "No filename passed");
 		return(false);
 	}
 
-	JS::Value *argv=JS_ARGV(cx, arglist);
+	JS::CallArgs argv = JS::CallArgsFromVp(argc, arglist);
 
-	if (!JSVAL_IS_STRING(argv[arg])) {
-		JS_ReportError(cx, "Invalid script name");
+	if (!argv[arg].isString()) {
+		JS_ReportErrorASCII(cx, "Invalid script name");
 		return(false);
 	}
 	JSVALUE_TO_MSTRING(cx, argv[arg++], cmd, NULL);
 	HANDLE_PENDING(cx, cmd);
 	if (cmd == NULL) {
-		JS_ReportError(cx, "Invalid NULL string");
+		JS_ReportErrorASCII(cx, "Invalid NULL string");
 		return(false);
 	}
 
 	if (argc > arg) {
-		if (JSVAL_IS_STRING(argv[arg])) {
+		if (argv[arg].isString()) {
 			JSVALUE_TO_MSTRING(cx, argv[arg++], startup_dir, &cmdlen);
 			HANDLE_PENDING(cx, cmd);
 			if(startup_dir == NULL) {
 				free(cmd);
-				JS_ReportError(cx, "Invalid NULL string");
+				JS_ReportErrorASCII(cx, "Invalid NULL string");
 				return(false);
 			}
 		}
 	}
 
-	if (argc > arg && JSVAL_IS_OBJECT(argv[arg])) {
-		js_scope = JSVAL_TO_OBJECT(argv[arg++]);
+	if (argc > arg && argv[arg].isObject()) {
+		js_scope = &argv[arg++].toObject();
 		if (js_scope == scope) {
 			free(cmd);
 			free(startup_dir);
-			JS_ReportError(cx, "Invalid Scope");
+			JS_ReportErrorASCII(cx, "Invalid Scope");
 			return(false);
 		}
 	}
 	else {
 		free(cmd);
 		free(startup_dir);
-		JS_ReportError(cx, "Invalid Scope");
+		JS_ReportErrorASCII(cx, "Invalid Scope");
 		return(false);
 	}
 
-	pscope = scope;
-	while ((!JS_GetProperty(cx, pscope, "js", &val) || val==JS::UndefinedValue() || !JSVAL_IS_OBJECT(val)) && pscope != NULL) {
-		pscope = JS_GetParent(pscope);
-		if (pscope == NULL) {
-			free(cmd);
-			free(startup_dir);
-			JS_ReportError(cx, "Walked to global, no js object!");
-			return false;
-		}
+	if (!JS_GetProperty(cx, scope, "js", &val) || val.isUndefined() || !val.isObject()) {
+		free(cmd);
+		free(startup_dir);
+		JS_ReportErrorASCII(cx, "Unable to locate js object!");
+		return false;
 	}
-	pjs_obj = JSVAL_TO_OBJECT(val);
+	pjs_obj = &val.toObject();
 	js_callback = (js_callback_t *)JS_GetPrivate(pjs_obj);
 
 	if(isfullpath(cmd))
@@ -379,7 +374,7 @@ js_execfile(JSContext *cx, unsigned argc, JS::Value *arglist)
 		// Then check js.exec_dir
 		/* if js.exec_dir is defined (location of executed script), search there first */
 		if (*path == 0) {
-			if(JS_GetProperty(cx, pjs_obj, "exec_dir", &val) && val!=JS::UndefinedValue() && JSVAL_IS_STRING(val)) {
+			if(JS_GetProperty(cx, pjs_obj, "exec_dir", &val) && val!=JS::UndefinedValue() && val.isString()) {
 				JSVALUE_TO_STRBUF(cx, val, path, sizeof(path), &pathlen);
 				strncat(path, cmd, sizeof(path)-pathlen-1);
 				if(!fexistcase(path))
@@ -390,93 +385,103 @@ js_execfile(JSContext *cx, unsigned argc, JS::Value *arglist)
 	free(cmd);
 
 	if(!fexistcase(path)) {
-		JS_ReportError(cx, "Script file (%s) does not exist", path);
+		JS_ReportErrorASCII(cx, "Script file (%s) does not exist", path);
 		free(startup_dir);
 		return false;
 	}
 
-	nargv=JS_NewArrayObject(cx, 0, NULL);
+	nargv=JS_NewArrayObject(cx, 0);
 
-	JS_DefineProperty(cx, js_scope, "argv", OBJECT_TO_JSVAL(nargv)
-		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
+	JS_DefineProperty(cx, js_scope, "argv", nargv
+		,JSPROP_READONLY|JSPROP_ENUMERATE);
 
 	unsigned nargc = 0;
 	for(i=arg; i<argc; i++) {
-		JS_SetElement(cx, nargv, nargc, &argv[i]);
+		JS_SetElement(cx, nargv, nargc, argv[i]);
 		nargc++;
 	}
 
-	JS_DefineProperty(cx, js_scope, "argc", INT_TO_JSVAL(nargc)
-		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
+	JS_DefineProperty(cx, js_scope, "argc", nargc
+		,JSPROP_ENUMERATE|JSPROP_READONLY);
 
 	js_obj = js_CreateInternalJsObject(cx, js_scope, js_callback, NULL);
 	js_PrepareToExecute(cx, js_scope, path, startup_dir, js_scope);
 	free(startup_dir);
 	// Copy in the load_path_list...
 	if(pjs_obj != NULL) {
-		JSObject*	pload_path_list;
-		JSObject*	load_path_list;
+		JS::RootedObject	pload_path_list(cx);
+		JS::RootedObject	load_path_list(cx);
 		uint32_t	plen;
 		uint32_t	pcnt;
+		bool isarray;
 
-		if (JS_GetProperty(cx, pjs_obj, JAVASCRIPT_LOAD_PATH_LIST, &val) && val!=JS::UndefinedValue() && JSVAL_IS_OBJECT(val)) {
-			pload_path_list = JSVAL_TO_OBJECT(val);
-			if (!JS_IsArrayObject(cx, pload_path_list)) {
-				JS_ReportError(cx, "Weird js." JAVASCRIPT_LOAD_PATH_LIST " value");
+		if (JS_GetProperty(cx, pjs_obj, JAVASCRIPT_LOAD_PATH_LIST, &val) && val!=JS::UndefinedValue() && val.isObject()) {
+			pload_path_list = &val.toObject();
+			if (!JS_IsArrayObject(cx, pload_path_list, &isarray) || !isarray) {
+				JS_ReportErrorASCII(cx, "Weird js." JAVASCRIPT_LOAD_PATH_LIST " value");
 				return false;
 			}
-			if((load_path_list=JS_NewArrayObject(cx, 0, NULL))==NULL) {
-				JS_ReportError(cx, "Unable to create js." JAVASCRIPT_LOAD_PATH_LIST);
+			if((load_path_list=JS_NewArrayObject(cx, 0))==NULL) {
+				JS_ReportErrorASCII(cx, "Unable to create js." JAVASCRIPT_LOAD_PATH_LIST);
 				return false;
 			}
-			val = OBJECT_TO_JSVAL(load_path_list);
-			JS_SetProperty(cx, js_obj, JAVASCRIPT_LOAD_PATH_LIST, &val);
+			val.setObject(*load_path_list);
+			JS_SetProperty(cx, js_obj, JAVASCRIPT_LOAD_PATH_LIST, val);
 			JS_GetArrayLength(cx, pload_path_list, &plen);
 			for (pcnt = 0; pcnt < plen; pcnt++) {
 				JS_GetElement(cx, pload_path_list, pcnt, &val);
-				JS_SetElement(cx, load_path_list, pcnt, &val);
+				JS_SetElement(cx, load_path_list, pcnt, val);
 			}
 		}
 		else {
-			JS_ReportError(cx, "Unable to get parent js." JAVASCRIPT_LOAD_PATH_LIST " array.");
+			JS_ReportErrorASCII(cx, "Unable to get parent js." JAVASCRIPT_LOAD_PATH_LIST " array.");
 			return false;
 		}
 	}
 	else {
-		JS_ReportError(cx, "Unable to get parent js object");
+		JS_ReportErrorASCII(cx, "Unable to get parent js object");
 		return false;
 	}
 
 	JS::CompileOptions options(cx);
 	options.setFileAndLine(path, 1);
-	js_script = JS::Compile(cx, JS::RootedObject(cx, js_scope), options, path);
+	bool compiled = JS::Compile(cx, options, path, &js_script);
 
-	if(js_script == NULL) {
+	if(!compiled) {
 		/* If the script fails to compile, it's not a fatal error
 		 * for the caller. */
 		if (JS_IsExceptionPending(cx)) {
 			JS_GetPendingException(cx, &rval);
-			JS_SET_RVAL(cx, arglist, rval);
+			argv.rval().set(rval);
 		}
 		JS_ClearPendingException(cx);
 		return(true);
 	}
 
-	JS_ExecuteScript(cx, js_scope, js_script, &rval);
+	JS::AutoObjectVector js_vscope(cx);
+	js_vscope.append(js_scope);
+	JS_ExecuteScript(cx, js_vscope, js_script, &rval);
 	if (JS_IsExceptionPending(cx)) {
 		JS_GetPendingException(cx, &rval);
 	}
 	else {
 		JS_GetProperty(cx, js_scope, "exit_code", &rval);
 	}
-	JS_SET_RVAL(cx, arglist, rval);
+	argv.rval().set(rval);
 	JS_ClearPendingException(cx);
 
 	js_EvalOnExit(cx, js_scope, js_callback);
-	JS_ReportPendingException(cx);
+	if (JS_IsExceptionPending(cx)) {
+		JS::RootedValue vp(cx);
+
+		if (JS_GetPendingException(cx, &vp)) {
+			JS::RootedObject err(cx, &vp.toObject());
+			JS::GetWarningReporter(cx)(cx, JS_ErrorFromException(cx, err));
+		}
+		JS_ClearPendingException(cx);
+	}
 	JS_DestroyScript(cx, js_script);
-	// TODO: Can't garbage collect a context in 24... comes back in 52 though.
-	JS_MaybeGC(cx);
+	JS_GC(cx);
 
 	return true;
 }
@@ -484,69 +489,71 @@ js_execfile(JSContext *cx, unsigned argc, JS::Value *arglist)
 static JSClass eval_class = {
     "Global",  /* name */
     JSCLASS_GLOBAL_FLAGS,  /* flags */
-    JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
-    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL,
-    JSCLASS_NO_OPTIONAL_MEMBERS
 };
 
 /* Execute a string in its own context (away from Synchronet objects) */
 static bool
 js_eval(JSContext *parent_cx, unsigned argc, JS::Value *arglist)
 {
-	JS::Value *argv=JS_ARGV(parent_cx, arglist);
+	JS::CallArgs argv = JS::CallArgsFromVp(argc, arglist);
 	char*			buf = NULL;
 	size_t			buflen;
-	JSString*		str;
-	JSScript*		script;
+	JS::RootedString	str(parent_cx);
 	JSContext*		cx;
-	JSObject*		obj;
-	JSErrorReporter	reporter;
 
-	JS_SET_RVAL(cx, arglist, JS::UndefinedValue());
+	argv.rval().setUndefined();
 
 	if(argc<1)
 		return(true);
 
-	if((str=JS_ValueToString(parent_cx, argv[0]))==NULL)
+	if((str=JS::ToString(parent_cx, argv[0]))==NULL)
 		return(false);
 	JSSTRING_TO_MSTRING(parent_cx, str, buf, &buflen);
 	HANDLE_PENDING(parent_cx, buf);
 	if(buf==NULL)
 		return(true);
 
-	if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL) {
+	if((cx=JS_NewContext(JAVASCRIPT_CONTEXT_STACK, JS::DefaultNurseryBytes, parent_cx))==NULL) {
 		free(buf);
 		return(false);
 	}
 
 	/* Use the error reporter from the parent context */
-	reporter=JS_SetErrorReporter(parent_cx,NULL);
-	JS_SetErrorReporter(parent_cx,reporter);
-	JS_SetErrorReporter(cx,reporter);
+	JS::SetWarningReporter(parent_cx,JS::GetWarningReporter(parent_cx));
 
 	/* Use the operation callback from the parent context */
 	JS_SetContextPrivate(cx, JS_GetContextPrivate(parent_cx));
-	JS_AddInterruptCallback(cx, JS_GetInterruptCallback(parent_cx));
+	// TODO: No way to get the parents InterruptCallback...
+	//JS_AddInterruptCallback(cx, JS_GetInterruptCallback(parent_cx));
+
+	JS::CompartmentOptions opts;
+	opts.behaviors().setVersion(JSVERSION_DEFAULT);
+	opts.creationOptions().setSharedMemoryAndAtomicsEnabled(true);
 
-	if((obj=JS_NewGlobalObject(cx, &eval_class, NULL))==NULL) {
+	JS::RootedObject		obj(cx);
+	if((obj=JS_NewGlobalObject(cx, &eval_class, NULL, JS::FireOnNewGlobalHook, opts))==NULL) {
+		JS_DestroyContext(cx);
+		free(buf);
+		return(false);
+	}
+	if(!JS_InitStandardClasses(cx,obj)) {
 		JS_DestroyContext(cx);
 		free(buf);
 		return(false);
 	}
-	{ // Scope for auto compartment...
-		JSAutoCompartment ac(cx, obj);
-		if(!JS_InitStandardClasses(cx,obj)) {
-			JS_DestroyContext(cx);
-			free(buf);
-			return(false);
-		}
-
-		if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
-			JS::Value	rval;
 
-			JS_ExecuteScript(cx, obj, script, &rval);
-			JS_SET_RVAL(cx, arglist, rval);
-		}
+	JS::CompileOptions options(cx);
+	options.setFileAndLine("js.eval", 1);
+	JS::RootedScript		script(cx);
+	if(JS::Compile(cx, options, buf, buflen, &script)) {
+		JS::RootedValue	rval(cx);
+		JS::RootedValue	prval(parent_cx);
+		JS::AutoObjectVector js_vscope(cx);
+
+		js_vscope.append(obj);
+		JS_ExecuteScript(cx, js_vscope, script, &rval);
+		js_CopyValue(cx, rval, parent_cx, &prval);
+		argv.rval().set(prval);
 	}
 	free(buf);
 
@@ -558,23 +565,22 @@ js_eval(JSContext *parent_cx, unsigned argc, JS::Value *arglist)
 static bool
 js_gc(JSContext *cx, unsigned argc, JS::Value *arglist)
 {
-	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
-	JS::Value *argv=JS_ARGV(cx, arglist);
+	JS::RootedObject obj(cx, JS_THIS_OBJECT(cx, arglist));
+	JS::CallArgs argv = JS::CallArgsFromVp(argc, arglist);
 	bool			forced=true;
 	js_callback_t*	cb;
 
-	JS_SET_RVAL(cx, arglist, JS::UndefinedValue());
-
 	if((cb=(js_callback_t*)JS_GetPrivate(obj))==NULL)
 		return(false);
 
+	argv.rval().setUndefined();
+
 	if(argc)
-		JS_ValueToBoolean(cx,argv[0],&forced);
+		forced = argv[0].toBoolean();
 
-	// TODO: Can't garbage collect a context in 24... comes back in 52 though.
-	//if(forced)
-	//	JS_GC(cx);
-	//else
+	if(forced)
+		JS_GC(cx);
+	else
 		JS_MaybeGC(cx);
 
 	cb->gc_attempts++;
@@ -585,40 +591,40 @@ js_gc(JSContext *cx, unsigned argc, JS::Value *arglist)
 static bool
 js_report_error(JSContext *cx, unsigned argc, JS::Value *arglist)
 {
-	JS::Value *argv=JS_ARGV(cx, arglist);
+	JS::CallArgs argv = JS::CallArgsFromVp(argc, arglist);
 	char	*p = NULL;
 
 	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
 	HANDLE_PENDING(cx, p);
 	if(p==NULL)
-		JS_ReportError(cx,"NULL");
+		JS_ReportErrorASCII(cx,"NULL");
 	else {
-		JS_ReportError(cx,"%s",p);
+		JS_ReportErrorASCII(cx,"%s",p);
 		free(p);
 	}
 
-	JS_SET_RVAL(cx, arglist, JS::UndefinedValue());
-
-	if(argc>1 && argv[1]==JSVAL_TRUE)
+	if(argc>1 && argv[1].toBoolean())
 		return(false);	/* fatal */
 
+	argv.rval().setUndefined();
+
 	return(true);
 }
 
 static bool
 js_on_exit(JSContext *cx, unsigned argc, JS::Value *arglist)
 {
-	JSObject *glob=getCurrentCompartmentGlobalObject(cx);
-	JS::Value *argv=JS_ARGV(cx, arglist);
+	JS::RootedObject glob(cx, getCurrentCompartmentGlobalObject(cx));
+	JS::CallArgs argv = JS::CallArgsFromVp(argc, arglist);
 	global_private_t*	pd;
 	struct js_onexit_scope *oes = NULL;
 	str_list_t	oldlist;
 	str_list_t	list;
 	char		*p = NULL;
 	JS::CallArgs args = JS::CallArgsFromVp(argc, arglist);
-	JSObject*       scope = JS_GetGlobalForObject(cx, &args.callee());
+	JS::RootedObject       scope(cx, JS_GetGlobalForObject(cx, &args.callee()));
 
-	JS_SET_RVAL(cx, arglist, JS::UndefinedValue());
+	argv.rval().setUndefined();
 
 	if((pd=(global_private_t*)JS_GetPrivate(glob))==NULL)
 		return(false);
@@ -638,14 +644,13 @@ js_on_exit(JSContext *cx, unsigned argc, JS::Value *arglist)
 		if (oes == NULL) {
 			oes = (struct js_onexit_scope *)malloc(sizeof(*oes));
 			if (oes == NULL) {
-				JS_ReportError(cx, "Unable to allocate memory for onexit scope");
+				JS_ReportErrorASCII(cx, "Unable to allocate memory for onexit scope");
 				return false;
 			}
 			oes->next = pd->onexit;
 			pd->onexit = oes;
-			oes->scope = scope;
+			oes->scope = scope.get();
 			oes->onexit = strListInit();
-			JS_AddObjectRoot(cx, &oes->scope);
 		}
 		list = oes->onexit;
 	}
@@ -666,34 +671,36 @@ js_on_exit(JSContext *cx, unsigned argc, JS::Value *arglist)
 	return(true);
 }
 
+#ifdef TODO_BATMAN
 static bool
 js_get_parent(JSContext *cx, unsigned argc, JS::Value *arglist)
 {
-	JS::Value *argv=JS_ARGV(cx, arglist);
-	JSObject* child=NULL;
-	JSObject* parent;
+	JS::CallArgs argv = JS::CallArgsFromVp(argc, arglist);
+	JS::RootedObject child(cx, NULL);
+	JS::RootedObject parent(cx);
 
-	if(JS_ValueToObject(cx, argv[0], &child)
+	if((child = JS::ToObject(cx, argv[0])) != NULL
 		&& child!=NULL
 		&& (parent=JS_GetParent(child))!=NULL)
 		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(parent));
 
 	return(true);
 }
+#endif
 
 static bool js_getsize(JSContext *cx, unsigned argc, JS::Value *arglist)
 {
-	JS::Value	*argv=JS_ARGV(cx, arglist);
-	JSObject* tmp_obj;
+	JS::CallArgs argv = JS::CallArgsFromVp(argc, arglist);
+	JS::RootedObject tmp_obj(cx);
 
-	JS_ReportError(cx, "get_size() no longer functional.");
+	JS_ReportErrorASCII(cx, "get_size() no longer functional.");
 	return false;
 #ifdef TODO_GETOBJECTTOTALSIZE
-	if(!JSVAL_IS_OBJECT(argv[0])) {
-		JS_ReportError(cx, "get_size() error!  Parameter is not an object.");
+	if(!argv[0].isObject()) {
+		JS_ReportErrorASCII(cx, "get_size() error!  Parameter is not an object.");
 		return(false);
 	}
-	tmp_obj=JSVAL_TO_OBJECT(argv[0]);
+	tmp_obj=argv[0].toObject();
 	if(!tmp_obj)
 		return(false);
 	JS_SET_RVAL(cx, arglist, DOUBLE_TO_JSVAL(JS_GetObjectTotalSize(cx, tmp_obj)));
@@ -703,14 +710,14 @@ static bool js_getsize(JSContext *cx, unsigned argc, JS::Value *arglist)
 
 static bool js_flatten(JSContext *cx, unsigned argc, JS::Value *arglist)
 {
-	JS::Value	*argv=JS_ARGV(cx, arglist);
+	JS::CallArgs argv = JS::CallArgsFromVp(argc, arglist);
 
-	if(!JSVAL_IS_STRING(argv[0])) {
-		JS_ReportError(cx, "get_size() error!  Parameter is not a string.");
+	if(!argv[0].isString()) {
+		JS_ReportErrorASCII(cx, "get_size() error!  Parameter is not a string.");
 		return(false);
 	}
-	JS_FlattenString(cx, JSVAL_TO_STRING(argv[0]));
-	JS_SET_RVAL(cx, arglist, JS::UndefinedValue());
+	JS_FlattenString(cx, argv[0].toString());
+	argv.rval().setUndefined();
 	return(true);
 }
 
@@ -735,10 +742,12 @@ static jsSyncMethodSpec js_functions[] = {
 	"if <i>fatal</i> is <i>true</i>, immediately terminates script")
 	,313
 	},
+#ifdef TODO_BATMAN
 	{"get_parent",		js_get_parent,		1,	JSTYPE_OBJECT,	JSDOCSTR("object")
 	,JSDOCSTR("return the parent of the specified object")
 	,314
 	},
+#endif
 	{"get_size",		js_getsize,			1,	JSTYPE_NUMBER,	JSDOCSTR("[object]")
 	,JSDOCSTR("return the size in bytes the object uses in memory (forces GC) ")
 	,316
@@ -759,52 +768,54 @@ static jsSyncMethodSpec js_functions[] = {
 	{0}
 };
 
-static bool js_internal_resolve(JSContext *cx, JS::HandleObject obj, JS::HandleId id)
+static bool js_internal_resolve(JSContext *cx, JS::HandleObject obj, JS::HandleId id, bool *resolved)
 {
 	char*			name=NULL;
-	bool			ret;
 
 	if(id != JSID_VOID && id != JSID_EMPTY) {
-		JS::Value idval;
+		JS::RootedValue idval(cx);
 		
 		JS_IdToValue(cx, id, &idval);
-		if(JSVAL_IS_STRING(idval)) {
-			JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
+		if(idval.isString()) {
+			JSSTRING_TO_MSTRING(cx, idval.toString(), name, NULL);
 			HANDLE_PENDING(cx, name);
 		}
 	}
 
-	ret=js_SyncResolve(cx, obj, name, js_properties, js_functions, NULL, 0);
+	*resolved = js_SyncResolve(cx, obj, name, js_properties, js_functions, NULL, 0);
 	if(name)
 		free(name);
-	return(ret);
+	return(true);
 }
 
 static bool js_internal_enumerate(JSContext *cx, JS::HandleObject obj)
 {
-	return(js_internal_resolve(cx, obj, JS::JSID_VOIDHANDLE));
+	bool resolved;
+	return(js_internal_resolve(cx, obj, JSID_VOIDHANDLE, &resolved));
 }
 
-static JSClass js_internal_class = {
-     "JsInternal"				/* name			*/
-    ,JSCLASS_HAS_PRIVATE	/* flags		*/
-	,JS_PropertyStub		/* addProperty	*/
-	,JS_DeletePropertyStub		/* delProperty	*/
+static JSClassOps js_internal_class_ops = {
+	 NULL		/* addProperty	*/
+	,NULL		/* delProperty	*/
 	,js_get					/* getProperty	*/
 	,js_set					/* setProperty	*/
 	,js_internal_enumerate	/* enumerate	*/
 	,js_internal_resolve	/* resolve		*/
-	,JS_ConvertStub			/* convert		*/
-	,NULL		/* finalize		*/
 };
 
-void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_callback_t* cb)
+static JSClass js_internal_class = {
+     "JsInternal"				/* name			*/
+    ,JSCLASS_HAS_PRIVATE	/* flags		*/
+    ,&js_internal_class_ops
+};
+
+void DLLCALL js_EvalOnExit(JSContext *cx, JS::HandleObject obj, js_callback_t* cb)
 {
 	char*	p;
-	JS::Value	rval;
+	JS::RootedValue	rval(cx);
 	JSScript* script;
 	BOOL	auto_terminate=cb->auto_terminate;
-	JSObject	*glob=getCurrentCompartmentGlobalObject(cx);
+	JS::RootedObject	glob(cx, getCurrentCompartmentGlobalObject(cx));
 	global_private_t *pt;
 	str_list_t	list = NULL;
 	struct js_onexit_scope **prev_oes_next = NULL;
@@ -816,7 +827,7 @@ void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_callback_t* cb)
 		while (pt->onexit) {
 			if (pt->onexit->scope == glob) {
 				// This shouldn't happen, but let's not go inifinite eh?
-				JS_ReportError(cx, "js_EvalOnExit() extra scope is global");
+				JS_ReportErrorASCII(cx, "js_EvalOnExit() extra scope is global");
 				return;
 			}
 			else {
@@ -824,7 +835,7 @@ void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_callback_t* cb)
 				js_EvalOnExit(cx, pt->onexit->scope, cb);
 				if (oes == pt->onexit) {
 					// This *really* shouldn't happen...
-					JS_ReportError(cx, "js_EvalOnExit() did not pop on_exit stack");
+					JS_ReportErrorASCII(cx, "js_EvalOnExit() did not pop on_exit stack");
 					return;
 				}
 			}
@@ -837,7 +848,7 @@ void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_callback_t* cb)
 			if (oes->scope == obj) {
 				(*prev_oes_next) = oes->next;
 				list = oes->onexit;
-				JS_RemoveObjectRoot(cx, &oes->scope);
+				oes->scope = NULL;
 				free(oes);
 				break;
 			}
@@ -849,8 +860,13 @@ void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_callback_t* cb)
 	cb->auto_terminate=FALSE;
 
 	while((p=strListPop(&list))!=NULL) {
-		if((script=JS_CompileScript(cx, obj, p, strlen(p), NULL, 0))!=NULL) {
-			JS_ExecuteScript(cx, obj, script, &rval);
+		JS::CompileOptions options(cx);
+		options.setFileAndLine("js.on_exit", 1);
+		JS::RootedScript		script(cx);
+		if(JS::Compile(cx, options, p, strlen(p), &script)) {
+			JS::AutoObjectVector js_vscope(cx);
+			js_vscope.append(obj);
+			JS_ExecuteScript(cx, js_vscope, script, &rval);
 		}
 		free(p);
 	}
@@ -863,36 +879,36 @@ void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_callback_t* cb)
 		cb->auto_terminate = TRUE;
 }
 
-JSObject* DLLCALL js_CreateInternalJsObject(JSContext* cx, JSObject* parent, js_callback_t* cb, js_startup_t* startup)
+JS::HandleObject DLLCALL js_CreateInternalJsObject(JSContext* cx, JS::HandleObject parent, js_callback_t* cb, js_startup_t* startup)
 {
-	JSObject*	obj;
+	JS::RootedObject	obj(cx);
 
-	if((obj = JS_DefineObject(cx, parent, "js", &js_internal_class, NULL
+	if((obj = JS_DefineObject(cx, parent, "js", &js_internal_class
 		,JSPROP_ENUMERATE|JSPROP_READONLY))==NULL)
 		return(NULL);
 
 	JS_SetPrivate(obj, cb);	/* Store a pointer to js_callback_t */
 
 	if(startup!=NULL) {
-		JSObject*	load_path_list;
-		JS::Value		val;
+		JS::RootedObject	load_path_list(cx);
+		JS::RootedValue		val(cx);
 		str_list_t	load_path;
 
-		if((load_path_list=JS_NewArrayObject(cx, 0, NULL))==NULL) 
+		if((load_path_list=JS_NewArrayObject(cx, 0))==NULL) 
 			return(NULL);
-		val=OBJECT_TO_JSVAL(load_path_list);
-		if(!JS_SetProperty(cx, obj, JAVASCRIPT_LOAD_PATH_LIST, &val)) 
+		val.setObject(*load_path_list);
+		if(!JS_SetProperty(cx, obj, JAVASCRIPT_LOAD_PATH_LIST, val)) 
 			return(NULL);
 
 		if((load_path=strListSplitCopy(NULL, startup->load_path, ",")) != NULL) {
-			JSString*	js_str;
+			JS::RootedString	js_str(cx);
 			unsigned	i;
 
 			for(i=0; load_path[i]!=NULL; i++) {
 				if((js_str=JS_NewStringCopyZ(cx, load_path[i]))==NULL)
 					break;
-				val=STRING_TO_JSVAL(js_str);
-				if(!JS_SetElement(cx, load_path_list, i, &val))
+				val.setString(js_str);
+				if(!JS_SetElement(cx, load_path_list, i, val))
 					break;
 			}
 			strListFree(&load_path);
@@ -917,41 +933,41 @@ void msvc_invalid_parameter_handler(const wchar_t* expression,
 }
 #endif
 
-void DLLCALL js_PrepareToExecute(JSContext *cx, JSObject *obj, const char *filename, const char* startup_dir, JSObject *scope)
+void DLLCALL js_PrepareToExecute(JSContext *cx, JS::HandleObject obj, const char *filename, const char* startup_dir, JS::HandleObject scope)
 {
-	JSString*	str;
-	JS::Value		val;
+	JS::RootedString	str(cx);
+	JS::RootedValue		val(cx);
 
-	if(JS_GetProperty(cx, obj, "js", &val) && JSVAL_IS_OBJECT(val)) {
-		JSObject* js = JSVAL_TO_OBJECT(val);
+	if(JS_GetProperty(cx, obj, "js", &val) && val.isObject()) {
+		JS::RootedObject js(cx, &val.toObject());
 		char	dir[MAX_PATH+1];
 
 		if(filename!=NULL) {
 			char* p;
 
 			if((str=JS_NewStringCopyZ(cx, filename)) != NULL)
-				JS_DefineProperty(cx, js, "exec_path", STRING_TO_JSVAL(str)
-					,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
+				JS_DefineProperty(cx, js, "exec_path", str
+					,JSPROP_ENUMERATE|JSPROP_READONLY);
 			if((str=JS_NewStringCopyZ(cx, getfname(filename))) != NULL)
-				JS_DefineProperty(cx, js, "exec_file", STRING_TO_JSVAL(str)
-					,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
+				JS_DefineProperty(cx, js, "exec_file", str
+					,JSPROP_ENUMERATE|JSPROP_READONLY);
 			SAFECOPY(dir,filename);
 			p=getfname(dir);
 			*p=0;
 			if((str=JS_NewStringCopyZ(cx, dir)) != NULL)
-				JS_DefineProperty(cx, js, "exec_dir", STRING_TO_JSVAL(str)
-					,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
+				JS_DefineProperty(cx, js, "exec_dir", str
+					,JSPROP_ENUMERATE|JSPROP_READONLY);
 		}
 		if(startup_dir==NULL)
 			startup_dir="";
 		if((str=JS_NewStringCopyZ(cx, startup_dir)) != NULL)
-			JS_DefineProperty(cx, js, "startup_dir", STRING_TO_JSVAL(str)
-				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
-		JS_DefineProperty(cx, js, "scope", OBJECT_TO_JSVAL(scope)
-			,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
+			JS_DefineProperty(cx, js, "startup_dir", str
+				,JSPROP_ENUMERATE|JSPROP_READONLY);
+		JS_DefineProperty(cx, js, "scope", scope
+			,JSPROP_ENUMERATE|JSPROP_READONLY);
 	}
-	JS_DefineProperty(cx, scope, "exit_code", JSVAL_NULL
-		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_PERMANENT);
+	JS_DefineProperty(cx, scope, "exit_code", JS::RootedValue(cx, JS::NullValue())
+		,JSPROP_ENUMERATE|JSPROP_PERMANENT);
 #if defined(_MSC_VER)
 	_set_invalid_parameter_handler(msvc_invalid_parameter_handler);
 #endif
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index c7ce1baafa6084eb9de4a0de0390282614148aad..89dfec7b43ad70379448bdebf2cf6d0475e15ba8 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -1344,7 +1344,7 @@ extern char lastuseron[LEN_ALIAS+1];  /* Name of user last online */
 
 	/* js_global.c */
 	struct js_onexit_scope {
-		JS::HandleObject scope;
+		JS::PersistentRootedObject scope;
 		str_list_t onexit;
 		struct js_onexit_scope *next;
 	};
@@ -1361,6 +1361,7 @@ extern char lastuseron[LEN_ALIAS+1];  /* Name of user last online */
 	} global_private_t;
 	DLLEXPORT bool DLLCALL js_argc(JSContext *cx, unsigned argc, unsigned min);
 	DLLEXPORT bool DLLCALL js_CreateGlobalObject(JSContext* cx, scfg_t* cfg, jsSyncMethodSpec* methods, js_startup_t*, JSObject**);
+	DLLEXPORT JS::HandleValue DLLCALL js_CopyValue(JSContext* cx, JS::HandleValue val, JSContext* new_cx, JS::MutableHandleValue rval);
 
 	/* js_internal.c */
 	DLLEXPORT JS::HandleObject DLLCALL js_CreateInternalJsObject(JSContext*, JS::HandleObject parent, js_callback_t*, js_startup_t*);