Skip to content
Snippets Groups Projects
js_internal.c 45.4 KiB
Newer Older
/* Synchronet "js" object, for internal JavaScript callback and GC control */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
Deucе's avatar
Deucе committed
#include "sockwrap.h"
Deucе's avatar
Deucе committed
#include "js_socket.h"
	 PROP_VERSION
	,PROP_TERMINATED
	,PROP_YIELD_INTERVAL
	,PROP_GC_INTERVAL
	,PROP_GC_LASTBYTES
	,PROP_BYTES
	,PROP_MAXBYTES
Deucе's avatar
Deucе committed
	,PROP_KEEPGOING
JSBool js_IsTerminated(JSContext* cx, JSObject* obj)
{
	js_callback_t*	cb;
	js_callback_t*	top_cb;

	if ((cb = (js_callback_t*)JS_GetPrivate(cx,obj)) == NULL)
		return JS_FALSE;
	for (top_cb=cb; top_cb->bg && top_cb->parent_cb; top_cb=top_cb->parent_cb) {
		if(top_cb->terminated && *top_cb->terminated)
			return JS_TRUE;
	}
	return top_cb->terminated && *top_cb->terminated;
}

static JSBool js_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
		case PROP_VERSION:
			*vp=STRING_TO_JSVAL(JS_NewStringCopyZ(cx,(char *)JS_GetImplementationVersion()));
			break;
			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=BOOLEAN_TO_JSVAL(TRUE);
					return JS_TRUE;
				}
			}
			if(top_cb->terminated==NULL)
				*vp=BOOLEAN_TO_JSVAL(*top_cb->terminated);
			*vp=BOOLEAN_TO_JSVAL(cb->auto_terminate);
		case PROP_COUNTER:
			*vp=DOUBLE_TO_JSVAL((double)cb->counter);
		case PROP_TIME_LIMIT:
			*vp=DOUBLE_TO_JSVAL(cb->limit);
			*vp=DOUBLE_TO_JSVAL((double)cb->yield_interval);
			*vp=DOUBLE_TO_JSVAL((double)cb->gc_interval);
			*vp=DOUBLE_TO_JSVAL((double)cb->gc_attempts);
			*vp=UINT_TO_JSVAL(cx->runtime->gcNumber);
			*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcLastBytes);
			*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcBytes);
			*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcMaxBytes);
			*vp = OBJECT_TO_JSVAL(JS_GetGlobalObject(cx));	
		case PROP_OPTIONS:
			*vp = UINT_TO_JSVAL(JS_GetOptions(cx));
			break;
Deucе's avatar
Deucе committed
		case PROP_KEEPGOING:
			if (cb->events_supported)
				*vp = BOOLEAN_TO_JSVAL(cb->keepGoing);
			else
				*vp = JSVAL_FALSE;
			break;
static JSBool js_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
			if(cb->terminated!=NULL)
				JS_ValueToBoolean(cx, *vp, (int *)cb->terminated);
			JS_ValueToBoolean(cx,*vp,&cb->auto_terminate);
		case PROP_COUNTER:
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->counter))
		case PROP_TIME_LIMIT:
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->limit))
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->gc_interval))
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->yield_interval))
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cx->runtime->gcMaxBytes))
				return JS_FALSE;
Deucе's avatar
Deucе committed
		case PROP_KEEPGOING:
			if (cb->events_supported)
				JS_ValueToBoolean(cx, *vp, &cb->keepGoing);
			break;
#define PROP_FLAGS	JSPROP_ENUMERATE|JSPROP_READONLY
static jsSyncPropertySpec js_properties[] = {
/*		 name,				tinyid,						flags,		ver	*/

	{	"version",			PROP_VERSION,		PROP_FLAGS,			311
		,JSDOCSTR("JavaScript engine version information (AKA system.js_version) - <small>READ ONLY</small>")
	},
	{	"auto_terminate",	PROP_AUTO_TERMINATE,JSPROP_ENUMERATE,	311
		,JSDOCSTR("Set to <i>false</i> to disable the automatic termination of the script upon external request or user disconnection")
	},
	{	"terminated",		PROP_TERMINATED,	JSPROP_ENUMERATE,	311
		,JSDOCSTR("Termination has been requested (stop execution as soon as possible)")
	},
	{	"branch_counter",	PROP_COUNTER,		0,					311
		,JSDOCSTR("alias")
	},
	{	"counter",			PROP_COUNTER,		JSPROP_ENUMERATE,	316
		,JSDOCSTR("Number of operation callbacks performed in this runtime")
	},
	{	"branch_limit",		PROP_TIME_LIMIT,	0,					311
		,JSDOCSTR("alias")
	},
	{	"time_limit",		PROP_TIME_LIMIT,	JSPROP_ENUMERATE,	316
		,JSDOCSTR("Maximum number of operation callbacks, used for infinite-loop detection (0=disabled)")
	},
	{	"yield_interval",	PROP_YIELD_INTERVAL,JSPROP_ENUMERATE,	311
		,JSDOCSTR("Interval of periodic time-slice yields (lower number=higher frequency, 0=disabled)")
	},
	{	"gc_interval",		PROP_GC_INTERVAL,	JSPROP_ENUMERATE,	311
		,JSDOCSTR("Interval of periodic garbage collection attempts (lower number=higher frequency, 0=disabled)")
	},
	{	"gc_attempts",		PROP_GC_ATTEMPTS,	PROP_FLAGS,			311
		,JSDOCSTR("Number of garbage collections attempted in this runtime - <small>READ ONLY </small>")
	},
	{	"gc_counter",		PROP_GC_COUNTER,	PROP_FLAGS,			311
		,JSDOCSTR("Number of garbage collections performed in this runtime - <small>READ ONLY</small>")
	},
	{	"gc_last_bytes",	PROP_GC_LASTBYTES,	PROP_FLAGS,			311
		,JSDOCSTR("Number of heap bytes in use after last garbage collection - <small>READ ONLY</small>")
	},
	{	"bytes",			PROP_BYTES,			PROP_FLAGS,			311
		,JSDOCSTR("Number of heap bytes currently in use - <small>READ ONLY</small>")
	},
	{	"max_bytes",		PROP_MAXBYTES,		JSPROP_ENUMERATE,	311
		,JSDOCSTR("Maximum number of bytes available for heap")
	},
	{	"global",			PROP_GLOBAL,		PROP_FLAGS,			314
		,JSDOCSTR("Global (top level) object - <small>READ ONLY</small>")
	},
	{	"options",			PROP_OPTIONS,		PROP_FLAGS,			31802
		,JSDOCSTR("Option flags - <small>READ ONLY</small>")
	},
	{	"do_callbacks",		PROP_KEEPGOING,		JSPROP_ENUMERATE,	31900
		,JSDOCSTR("Do callbacks after script finishes running")
	},
static const char* prop_desc[] = {
deuce's avatar
deuce committed
	/* New properties go here... */
	"Full path and filename of JS file executed"
	,"JS filename executed (with no path)"
	,"Directory of executed JS file"
	,"Either the configured startup directory in SCFG (for externals) or the cwd when jsexec is started"
	,"Global scope for this script"
	,"<tt>load()</tt> search path array.<br>For relative load paths (e.g. not beginning with '/' or '\\'), "
		"the path is assumed to be a sub-directory of the (configurable) mods or exec directories "
		"and is searched accordingly.<br>"
		"So, by default, <tt>load(\"somefile.js\")</tt> will search in this order:<tt><ol>"
		"<li>mods/load/somefile.js"
		"<li>exec/load/somefile.js"
		"<li>mods/somefile.js"
		"<li>exec/somefile.js"
		"</ol></tt>"
js_CommonOperationCallback(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");
				cb->counter=0;
				return(JS_FALSE);
			}
		}
	if(cb->limit && cb->counter > cb->limit) {
		JS_ReportError(cx,"Infinite loop (%lu operation callbacks) detected",cb->counter);
	/* Give up timeslices every once in a while */
	if(cb->yield_interval && (cb->counter%cb->yield_interval)==0) {
deuce's avatar
deuce committed
		jsrefcount	rc;

	/* Permit other contexts to run GC */
	JS_YieldRequest(cx);

	if(cb->gc_interval && (cb->counter%cb->gc_interval)==0)
		JS_MaybeGC(cx), cb->gc_attempts++;
deuce's avatar
deuce committed
// This is kind of halfway between js_execfile() in exec.cpp and js_load
static int
js_execfile(JSContext *cx, uintN argc, jsval *arglist)
{
	char*		cmd = NULL;
	size_t		cmdlen;
	size_t		pathlen;
	char*		startup_dir = NULL;
deuce's avatar
deuce committed
	char		path[MAX_PATH+1] = "";
deuce's avatar
deuce committed
	JSObject*	scope = JS_GetScopeChain(cx);
	JSObject*	js_scope = NULL;
	JSObject*	pscope;
deuce's avatar
deuce committed
	JSObject*	js_script=NULL;
	JSObject*	nargv;
deuce's avatar
deuce committed
	jsrefcount	rc;
deuce's avatar
deuce committed
	jsval		val;
deuce's avatar
deuce committed
	JSObject *js_obj;
	JSObject *pjs_obj;
deuce's avatar
deuce committed

	if(argc<1) {
		JS_ReportError(cx, "No filename passed");
		return(JS_FALSE);
	}

	jsval *argv=JS_ARGV(cx, arglist);

	if (!JSVAL_IS_STRING(argv[arg])) {
		JS_ReportError(cx, "Invalid script name");
		return(JS_FALSE);
	}
	JSVALUE_TO_MSTRING(cx, argv[arg++], cmd, NULL);
	HANDLE_PENDING(cx, cmd);
	if (cmd == NULL) {
		JS_ReportError(cx, "Invalid NULL string");
		return(JS_FALSE);
	}

	if (argc > arg) {
		if (JSVAL_IS_STRING(argv[arg])) {
			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");
				return(JS_FALSE);
			}
		}
	}

	if (argc > arg && JSVAL_IS_OBJECT(argv[arg])) {
deuce's avatar
deuce committed
		js_scope = JSVAL_TO_OBJECT(argv[arg++]);
		if (js_scope == scope) {
			free(cmd);
			free(startup_dir);
			JS_ReportError(cx, "Invalid Scope");
			return(JS_FALSE);
		}
	}
deuce's avatar
deuce committed
		JS_ReportError(cx, "Invalid Scope");
		return(JS_FALSE);
	}

	pscope = scope;
	while ((!JS_GetProperty(cx, pscope, "js", &val) || val==JSVAL_VOID || !JSVAL_IS_OBJECT(val)) && pscope != NULL) {
		pscope = JS_GetParent(cx, pscope);
		if (pscope == NULL) {
			free(cmd);
			free(startup_dir);
			JS_ReportError(cx, "Walked to global, no js object!");
			return JS_FALSE;
		}
	}
	pjs_obj = JSVAL_TO_OBJECT(val);
	js_callback = JS_GetPrivate(cx, pjs_obj);

deuce's avatar
deuce committed
	if(isfullpath(cmd))
		SAFECOPY(path,cmd);
	else {
		// If startup dir specified, check there first.
		if (startup_dir) {
			SAFECOPY(path, startup_dir);
			backslash(path);
			strncat(path, cmd, sizeof(path) - strlen(path) - 1);
			rc=JS_SUSPENDREQUEST(cx);
			if (!fexist(path))
				*path = 0;
			JS_RESUMEREQUEST(cx, rc);
		}
		// 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!=JSVAL_VOID && JSVAL_IS_STRING(val)) {
				JSVALUE_TO_STRBUF(cx, val, path, sizeof(path), &pathlen);
				strncat(path, cmd, sizeof(path)-pathlen-1);
				rc=JS_SUSPENDREQUEST(cx);
				if(!fexistcase(path))
					path[0]=0;
				JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
			}
			if (*path == '\0')
				SAFECOPY(path, cmd);
deuce's avatar
deuce committed
		}
	}
	free(cmd);

	if(!fexistcase(path)) {
		JS_ReportError(cx, "Script file (%s) does not exist", path);
deuce's avatar
deuce committed
		free(startup_dir);
		return JS_FALSE;
	}

	nargv=JS_NewArrayObject(cx, 0, NULL);

	JS_DefineProperty(cx, js_scope, "argv", OBJECT_TO_JSVAL(nargv)
		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

	uintN nargc = 0;
	for(i=arg; i<argc; i++) {
		JS_SetElement(cx, nargv, nargc, &argv[i]);
		nargc++;
	}
deuce's avatar
deuce committed

	JS_DefineProperty(cx, js_scope, "argc", INT_TO_JSVAL(nargc)
deuce's avatar
deuce committed
		,NULL,NULL,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;
		uint32_t	plen;
		uint32_t	pcnt;

		if (JS_GetProperty(cx, pjs_obj, JAVASCRIPT_LOAD_PATH_LIST, &val) && val!=JSVAL_VOID && 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");
				return JS_FALSE;
			}
			if((load_path_list=JS_NewArrayObject(cx, 0, NULL))==NULL) {
				JS_ReportError(cx, "Unable to create js."JAVASCRIPT_LOAD_PATH_LIST);
				return JS_FALSE;
			}
			val = OBJECT_TO_JSVAL(load_path_list);
			JS_SetProperty(cx, js_obj, JAVASCRIPT_LOAD_PATH_LIST, &val);
			if(JS_GetArrayLength(cx, pload_path_list, &plen))
				for (pcnt = 0; pcnt < plen; pcnt++) {
					if(JS_GetElement(cx, pload_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.");
			return JS_FALSE;
		}
	}
	else {
		JS_ReportError(cx, "Unable to get parent js object");
		return JS_FALSE;
	}

deuce's avatar
deuce committed
	js_script=JS_CompileFile(cx, js_scope, path);

	if(js_script == NULL) {
		/* 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);
deuce's avatar
deuce committed
		}
		JS_ClearPendingException(cx);
		return(JS_TRUE);
deuce's avatar
deuce committed
	}

	JS_ExecuteScript(cx, js_scope, js_script, &rval);
	if (JS_IsExceptionPending(cx)) {
		JS_GetPendingException(cx, &rval);
	}
	else {
		jsval exit_code = JSVAL_VOID;
		if(JS_GetProperty(cx, js_scope, "exit_code", &exit_code) && exit_code != JSVAL_VOID)
deuce's avatar
deuce committed
	}
	JS_SET_RVAL(cx, arglist, rval);
	JS_ClearPendingException(cx);

	js_EvalOnExit(cx, js_scope, js_callback);
deuce's avatar
deuce committed
	JS_ReportPendingException(cx);
	JS_DestroyScript(cx, js_script);
deuce's avatar
deuce committed

	return JS_TRUE;
}

static JSClass eval_class = {
    "Global",  /* name */
    JSCLASS_GLOBAL_FLAGS,  /* flags */
    JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
    JSCLASS_NO_OPTIONAL_MEMBERS
};

/* Execute a string in its own context (away from Synchronet objects) */
static JSBool
js_eval(JSContext *parent_cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(parent_cx, arglist);
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((str=JS_ValueToString(parent_cx, argv[0]))==NULL)
	JSSTRING_TO_MSTRING(parent_cx, str, buf, &buflen);
	HANDLE_PENDING(parent_cx, buf);
deuce's avatar
deuce committed
	if(buf==NULL)
		return(JS_TRUE);
	if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL) {
		free(buf);
	/* Use the error reporter from the parent context */
	reporter=JS_SetErrorReporter(parent_cx,NULL);
	JS_SetErrorReporter(parent_cx,reporter);
	JS_SetErrorReporter(cx,reporter);
	/* Use the operation callback from the parent context */
	JS_SetContextPrivate(cx, JS_GetContextPrivate(parent_cx));
	JS_SetOperationCallback(cx, JS_GetOperationCallback(parent_cx));
	if((obj=JS_NewCompartmentAndGlobalObject(cx, &eval_class, NULL))==NULL
		|| !JS_InitStandardClasses(cx,obj)) {
		JS_DestroyContext(cx);
		free(buf);
	if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
deuce's avatar
deuce committed
		jsval	rval;

		JS_ExecuteScript(cx, obj, script, &rval);
		JS_SET_RVAL(cx, arglist, rval);
	free(buf);
js_gc(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
	JSBool			forced=JS_TRUE;
deuce's avatar
deuce committed
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)

	if(argc)
		JS_ValueToBoolean(cx,argv[0],&forced);

	if(forced)
		JS_GC(cx);
	else
		JS_MaybeGC(cx);

js_report_error(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
	if(p==NULL)
		JS_ReportError(cx,"NULL");
	else {
		JS_ReportError(cx,"%s",p);
		free(p);
	}
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if(argc>1 && argv[1]==JSVAL_TRUE)
		return(JS_FALSE);	/* fatal */

	return(JS_TRUE);
}
js_on_exit(JSContext *cx, uintN argc, jsval *arglist)
	JSObject *scope=JS_GetScopeChain(cx);
	JSObject *glob=JS_GetGlobalObject(cx);
	jsval *argv=JS_ARGV(cx, arglist);
Deucе's avatar
Deucе committed
	struct js_onexit_scope *oes = NULL;
Deucе's avatar
Deucе committed
	str_list_t	list;
deuce's avatar
deuce committed
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

Deucе's avatar
Deucе committed
	if((pd=(global_private_t*)JS_GetPrivate(cx,glob))==NULL)
		return(JS_FALSE);
		if(pd->exit_func==NULL)
			pd->exit_func=strListInit();
		list=pd->exit_func;
	}
	else {
Deucе's avatar
Deucе committed
		/* First, look for an existing onexit scope for this scope */
		for (oes = pd->onexit; oes; oes = oes->next) {
			if (oes->scope == scope)
				break;
Deucе's avatar
Deucе committed

		/* If one isn't found, insert it */
		if (oes == NULL) {
			oes = malloc(sizeof(*oes));
			if (oes == NULL) {
				JS_ReportError(cx, "Unable to allocate memory for onexit scope");
Deucе's avatar
Deucе committed
				return JS_FALSE;
Deucе's avatar
Deucе committed
			}
			oes->next = pd->onexit;
			pd->onexit = oes;
			oes->scope = scope;
			oes->onexit = strListInit();
			JS_AddObjectRoot(cx, &oes->scope);
Deucе's avatar
Deucе committed
		list = oes->onexit;
	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
	if(!p)
		return JS_TRUE;
	oldlist=list;
	free(p);
	if(oldlist != list) {
		if(glob==scope)
			pd->exit_func=list;
		else
Deucе's avatar
Deucе committed
			oes->onexit = list;
js_get_parent(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
	JSObject* child=NULL;
	JSObject* parent;

	if(JS_ValueToObject(cx, argv[0], &child)
		&& child!=NULL
		&& (parent=JS_GetParent(cx,child))!=NULL)
		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(parent));
static JSBool js_getsize(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval	*argv=JS_ARGV(cx, arglist);
	JSObject* tmp_obj;

	if(!JSVAL_IS_OBJECT(argv[0])) {
		JS_ReportError(cx, "Parameter is not an object.");
		return(JS_FALSE);
	}
	tmp_obj=JSVAL_TO_OBJECT(argv[0]);
	if(!tmp_obj)
		return(JS_FALSE);
	JS_SET_RVAL(cx, arglist, DOUBLE_TO_JSVAL(JS_GetObjectTotalSize(cx, tmp_obj)));
	return(JS_TRUE);
}

static JSBool js_flatten(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval	*argv=JS_ARGV(cx, arglist);

	if(!JSVAL_IS_STRING(argv[0])) {
		JS_ReportError(cx, "Parameter is not a string.");
		return(JS_FALSE);
	}
	JS_FlattenString(cx, JSVAL_TO_STRING(argv[0]));
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
	return(JS_TRUE);
}

Deucе's avatar
Deucе committed
static JSBool
js_setTimeout(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval	*argv=JS_ARGV(cx, arglist);
	struct js_event_list *ev;
	js_callback_t*	cb;
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	JSFunction *ecb;
	uint64_t now = (uint64_t)(xp_timer() * 1000);
Deucе's avatar
Deucе committed
	jsdouble timeout;

	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);

	if (!cb->events_supported) {
		JS_ReportError(cx, "events not supported");
		return JS_FALSE;
	}

	if (argc < 2) {
		JS_ReportError(cx, "js.setTimeout() requires two parameters");
		return JS_FALSE;
	}
	ecb = JS_ValueToFunction(cx, argv[0]);
	if (ecb == NULL) {
		return JS_FALSE;
	}
	if (argc > 2) {
		if (!JS_ValueToObject(cx, argv[2], &obj))
			return JS_FALSE;
	}
	if (!JS_ValueToNumber(cx, argv[1], &timeout)) {
		return JS_FALSE;
	}
	ev = malloc(sizeof(*ev));
	if (ev == NULL) {
		JS_ReportError(cx, "error allocating %lu bytes", sizeof(*ev));
		return JS_FALSE;
	}
	ev->prev = NULL;
	ev->next = cb->events;
	if (ev->next)
		ev->next->prev = ev;
	ev->type = JS_EVENT_TIMEOUT;
	ev->cx = obj;
	JS_AddObjectRoot(cx, &ev->cx);
	ev->cb = ecb;
	ev->data.timeout.end = (uint64_t)(now + timeout);
Deucе's avatar
Deucе committed
	ev->id = cb->next_eid++;
	cb->events = ev;

	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(ev->id));
	return JS_TRUE;
}

JSBool
Deucе's avatar
Deucе committed
js_clear_event(JSContext *cx, jsval *arglist, js_callback_t *cb, enum js_event_type et, int ididx)
Deucе's avatar
Deucе committed
{
	int32 id;
Deucе's avatar
Deucе committed
	jsval	*argv=JS_ARGV(cx, arglist);
Deucе's avatar
Deucе committed
	struct js_event_list *ev;
	struct js_event_list *nev;

Deucе's avatar
Deucе committed
	if (!JS_ValueToInt32(cx, argv[ididx], &id)) {
Deucе's avatar
Deucе committed
		return JS_FALSE;
	}
	if (!cb->events_supported) {
		JS_ReportError(cx, "events not supported");
		return JS_FALSE;
	}

	for (ev = cb->events; ev; ev = nev) {
		nev = ev->next;
		if (ev->type == et && ev->id == id) {
			if (ev->next)
				ev->next->prev = ev->prev;
			if (ev->prev)
				ev->prev->next = ev->next;
			else
				cb->events = ev->next;
			JS_RemoveObjectRoot(cx, &ev->cx);
			free(ev);
		}
	}

	return JS_TRUE;
}

Deucе's avatar
Deucе committed
static JSBool
js_internal_clear_event(JSContext *cx, uintN argc, jsval *arglist, enum js_event_type et)
{
	js_callback_t*	cb;
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if (argc < 1) {
		JS_ReportError(cx, "js.clearTimeout() requires an id");
		return JS_FALSE;
	}
	if((cb=(js_callback_t*)JS_GetPrivate(cx, obj))==NULL)
		return(JS_FALSE);

	return js_clear_event(cx, arglist, cb, et, 0);
}

Deucе's avatar
Deucе committed
static JSBool
js_clearTimeout(JSContext *cx, uintN argc, jsval *arglist)
{
Deucе's avatar
Deucе committed
	return js_internal_clear_event(cx, argc, arglist, JS_EVENT_TIMEOUT);
Deucе's avatar
Deucе committed
}

static JSBool
js_clearInterval(JSContext *cx, uintN argc, jsval *arglist)
{
Deucе's avatar
Deucе committed
	return js_internal_clear_event(cx, argc, arglist, JS_EVENT_INTERVAL);
Deucе's avatar
Deucе committed
}

static JSBool
js_setInterval(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval	*argv=JS_ARGV(cx, arglist);
	struct js_event_list *ev;
	js_callback_t*	cb;
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	JSFunction *ecb;
	uint64_t now = (uint64_t)(xp_timer() * 1000);
Deucе's avatar
Deucе committed
	jsdouble period;

	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);

	if (!cb->events_supported) {
		JS_ReportError(cx, "events not supported");
		return JS_FALSE;
	}

	if (argc < 2) {
		JS_ReportError(cx, "js.setInterval() requires two parameters");
		return JS_FALSE;
	}
	ecb = JS_ValueToFunction(cx, argv[0]);
	if (ecb == NULL) {
		return JS_FALSE;
	}
	if (!JS_ValueToNumber(cx, argv[1], &period)) {
		return JS_FALSE;
	}
	if (argc > 2) {
		if (!JS_ValueToObject(cx, argv[2], &obj))
			return JS_FALSE;
	}
	ev = malloc(sizeof(*ev));
	if (ev == NULL) {
		JS_ReportError(cx, "error allocating %lu bytes", sizeof(*ev));
		return JS_FALSE;
	}
	ev->prev = NULL;
	ev->next = cb->events;
	if (ev->next)
		ev->next->prev = ev;
	ev->type = JS_EVENT_INTERVAL;
	ev->cx = obj;
	JS_AddObjectRoot(cx, &ev->cx);
	ev->cb = ecb;
	ev->data.interval.last = now;
	ev->data.interval.period =(uint64_t)period;
Deucе's avatar
Deucе committed
	ev->id = cb->next_eid++;
	cb->events = ev;

	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(ev->id));
	return JS_TRUE;
}

static JSBool
js_setImmediate(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval	*argv=JS_ARGV(cx, arglist);
	JSFunction *cbf;
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	js_callback_t *cb;
	struct js_runq_entry *rqe;

	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);

	if (argc < 1) {
		JS_ReportError(cx, "js.setImmediate() requires a callback");
		return JS_FALSE;
	}

	cbf = JS_ValueToFunction(cx, argv[0]);
	if (cbf == NULL) {
		return JS_FALSE;
	}

	if (argc > 1) {
		if (!JS_ValueToObject(cx, argv[1], &obj))
			return JS_FALSE;
	}

	rqe = malloc(sizeof(*rqe));
	if (rqe == NULL) {
		JS_ReportError(cx, "error allocating %ul bytes", sizeof(*rqe));
		return JS_FALSE;
	}
	rqe->func = cbf;
	rqe->cx = obj;
	JS_AddObjectRoot(cx, &rqe->cx);
	rqe->next = NULL;
	if (cb->rq_tail != NULL)
		cb->rq_tail->next = rqe;
	cb->rq_tail = rqe;
	if (cb->rq_head == NULL)
		cb->rq_head = rqe;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
	return JS_TRUE;
}

Deucе's avatar
Deucе committed
static JSBool
js_addEventListener(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval	*argv=JS_ARGV(cx, arglist);
	char *name;
	JSFunction *cbf;
	struct js_listener_entry *listener;
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	js_callback_t *cb;

	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);

	if (argc < 2) {
		JS_ReportError(cx, "js.addEventListener() requires exactly two parameters");
		return JS_FALSE;
	}

	cbf = JS_ValueToFunction(cx, argv[1]);
	if (cbf == NULL) {
		return JS_FALSE;
	}

	JSVALUE_TO_MSTRING(cx, argv[0], name, NULL);
	HANDLE_PENDING(cx, name);

	listener = malloc(sizeof(*listener));
	if (listener == NULL) {
		free(name);
		JS_ReportError(cx, "error allocating %ul bytes", sizeof(*listener));
		return JS_FALSE;
	}

	listener->func = cbf;
	listener->id = cb->next_eid++;
	listener->next = cb->listeners;
	listener->name = name;
	cb->listeners = listener;