diff --git a/src/sbbs3/js_global.c b/src/sbbs3/js_global.c
index 50765d75cc5ea4f3d8dffafc5590de6182a7a66d..222b5e64ca5305e1953dea0b041b0cc4392e3a28 100644
--- a/src/sbbs3/js_global.c
+++ b/src/sbbs3/js_global.c
@@ -110,7 +110,7 @@ typedef struct {
 	msg_queue_t*	msg_queue;
 	js_callback_t	cb;
 	JSErrorReporter error_reporter;
-	JSNative		log;
+	JSObject*		logobj;
 } background_data_t;
 
 static void background_thread(void* arg)
@@ -128,6 +128,7 @@ static void background_thread(void* arg)
 		result=exit_code;
 	js_EvalOnExit(bg->cx, bg->obj, &bg->cb);
 	js_enqueue_value(bg->cx, bg->msg_queue, result, NULL);
+	JS_RemoveObjectRoot(bg->cx, &bg->logobj);
 	JS_RemoveObjectRoot(bg->cx, &bg->obj);
 	JS_ENDREQUEST(bg->cx);
 	JS_DestroyContext(bg->cx);
@@ -171,32 +172,42 @@ js_OperationCallback(JSContext *cx)
 	return ret;
 }
 
+static JSBool
+js_log(JSContext *cx, uintN argc, jsval *arglist)
+{
+	jsval *argv=JS_ARGV(cx, arglist);
+	JSBool retval;
+	background_data_t* bg;
+	jsval	rval;
+
+	JS_SET_RVAL(cx, arglist, rval);
+	rval=JSVAL_VOID;
+
+	if((bg=(background_data_t*)JS_GetContextPrivate(cx))==NULL)
+		return JS_FALSE;
+
+	/* Use parent's context private data */
+	JS_SetContextPrivate(cx, JS_GetContextPrivate(bg->parent_cx));
+
+	/* Call parent's log() function */
+	retval = JS_CallFunctionName(cx, bg->logobj, "log", argc, argv, &rval);
+
+	/* Restore our context private data */
+	JS_SetContextPrivate(cx, bg);
+
+	return retval;
+}
+
 /* Create a new value in the new context with a value from the original context */
-/* Note: objects (including arrays) not currently supported */
 static jsval* js_CopyValue(JSContext* cx, jsval val, JSContext* new_cx, jsval* rval)
 {
 	*rval = JSVAL_VOID;
+	size_t	size;
+	uint64	nval;
 
-	if(cx==new_cx
-		|| JSVAL_IS_BOOLEAN(val) 
-		|| JSVAL_IS_NULL(val) 
-		|| JSVAL_IS_VOID(val) 
-		|| JSVAL_IS_INT(val))
-		*rval = val;
-	else if(JSVAL_IS_NUMBER(val)) {
-		jsdouble	d;
-		if(JS_ValueToNumber(cx,val,&d))
-			*rval=DOUBLE_TO_JSVAL(d);
-	}
-	else {
-		JSString*	str;
-		size_t		len;
-		char*		p;
-
-		JSVALUE_TO_STRING(cx, val, p, &len);
-		if(p != NULL
-			&& (str=JS_NewStringCopyN(new_cx,p,len)) != NULL)
-			*rval=STRING_TO_JSVAL(str);
+	if(JS_WriteStructuredClone(cx, val, &nval, &size, NULL, NULL)) {
+		JS_ReadStructuredClone(new_cx, nval, size, JS_STRUCTURED_CLONE_VERSION, rval, NULL, NULL);
+		JS_free(cx, nval);
 	}
 
 	return rval;
@@ -245,10 +256,6 @@ js_load(JSContext *cx, uintN argc, jsval *arglist)
 		bg->cb.limit=p->startup->time_limit;
 		bg->cb.gc_interval=p->startup->gc_interval;
 		bg->cb.yield_interval=p->startup->yield_interval;
-#if 0
-		if(JS_GetProperty(cx, obj,"js",&val))	/* copy branch settings from parent */
-			memcpy(&bg->branch,JS_GetPrivate(cx,JSVAL_TO_OBJECT(val)),sizeof(bg->branch));
-#endif
 		bg->cb.terminated=NULL;	/* could be bad pointer at any time */
 		bg->cb.counter=0;
 		bg->cb.gc_attempts=0;
@@ -286,6 +293,15 @@ js_load(JSContext *cx, uintN argc, jsval *arglist)
 			return(JS_FALSE);
 		}
 
+		if((bg->logobj=JS_NewObjectWithGivenProto(bg->cx, NULL, NULL, bg->obj))==NULL) {
+			JS_ENDREQUEST(bg->cx);
+			JS_DestroyContext(bg->cx);
+			jsrt_Release(bg->runtime);
+			free(bg);
+			return(JS_FALSE);
+		}
+		JS_AddObjectRoot(cx, &bg->logobj);
+
 		bg->msg_queue = msgQueueInit(NULL,MSG_QUEUE_BIDIR);
 
 		js_CreateQueueObject(bg->cx, bg->obj, "parent_queue", bg->msg_queue);
@@ -305,8 +321,9 @@ js_load(JSContext *cx, uintN argc, jsval *arglist)
 			if((func=JS_ValueToFunction(cx, val))!=NULL) {
 				JSObject *obj;
 
-				obj=JS_CloneFunctionObject(bg->cx, JS_GetFunctionObject(func), bg->obj);
-				JS_DefineProperty(bg->cx, bg->obj, "log", OBJECT_TO_JSVAL(obj), NULL, NULL, JSPROP_ENUMERATE|JSPROP_PERMANENT);
+				obj=JS_CloneFunctionObject(bg->cx, JS_GetFunctionObject(func), bg->logobj);
+				JS_DefineProperty(bg->cx, bg->logobj, "log", OBJECT_TO_JSVAL(obj), NULL, NULL, JSPROP_ENUMERATE|JSPROP_PERMANENT);
+				JS_DefineFunction(bg->cx, bg->obj, "log", js_log, JS_GetFunctionArity(func), JS_GetFunctionFlags(func));
 			}
 		}