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"
#include "js_request.h"
/* SpiderMonkey: */
#include <jsdbgapi.h>
enum {
PROP_VERSION
,PROP_TERMINATED
,PROP_AUTO_TERMINATE
,PROP_COUNTER
,PROP_TIME_LIMIT
,PROP_YIELD_INTERVAL
,PROP_GC_INTERVAL
,PROP_GC_ATTEMPTS
#ifdef jscntxt_h___
,PROP_GC_COUNTER
,PROP_GC_LASTBYTES
,PROP_BYTES
,PROP_MAXBYTES
#endif
,PROP_GLOBAL
,PROP_OPTIONS
};
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)
{
jsval idval;
jsint tiny;
js_callback_t* cb;
js_callback_t* top_cb;
if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
return(JS_FALSE);
JS_IdToValue(cx, id, &idval);
tiny = JSVAL_TO_INT(idval);
switch(tiny) {
case PROP_VERSION:
*vp=STRING_TO_JSVAL(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=BOOLEAN_TO_JSVAL(TRUE);
return JS_TRUE;
}
}
if(top_cb->terminated==NULL)
*vp=JSVAL_FALSE;
else
*vp=BOOLEAN_TO_JSVAL(*top_cb->terminated);
break;
case PROP_AUTO_TERMINATE:
*vp=BOOLEAN_TO_JSVAL(cb->auto_terminate);
break;
case PROP_COUNTER:
*vp=DOUBLE_TO_JSVAL((double)cb->counter);
break;
case PROP_TIME_LIMIT:
*vp=DOUBLE_TO_JSVAL(cb->limit);
break;
case PROP_YIELD_INTERVAL:
*vp=DOUBLE_TO_JSVAL((double)cb->yield_interval);
break;
case PROP_GC_INTERVAL:
*vp=DOUBLE_TO_JSVAL((double)cb->gc_interval);
break;
case PROP_GC_ATTEMPTS:
*vp=DOUBLE_TO_JSVAL((double)cb->gc_attempts);
break;
#ifdef jscntxt_h___
case PROP_GC_COUNTER:
*vp=UINT_TO_JSVAL(cx->runtime->gcNumber);
break;
case PROP_GC_LASTBYTES:
*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcLastBytes);
break;
case PROP_BYTES:
*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcBytes);
break;
case PROP_MAXBYTES:
*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcMaxBytes);
break;
#endif
case PROP_GLOBAL:
*vp = OBJECT_TO_JSVAL(JS_GetGlobalObject(cx));
break;
case PROP_OPTIONS:
*vp = UINT_TO_JSVAL(JS_GetOptions(cx));
break;
case PROP_KEEPGOING:
if (cb->events_supported)
*vp = BOOLEAN_TO_JSVAL(cb->keepGoing);
else
*vp = JSVAL_FALSE;
break;
}
return(JS_TRUE);
}
static JSBool js_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
{
jsval idval;
jsint tiny;
js_callback_t* cb;
if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
return(JS_FALSE);
JS_IdToValue(cx, id, &idval);
tiny = JSVAL_TO_INT(idval);
switch(tiny) {
case PROP_TERMINATED:
if(cb->terminated!=NULL)
JS_ValueToBoolean(cx, *vp, (int *)cb->terminated);
break;
case PROP_AUTO_TERMINATE:
JS_ValueToBoolean(cx,*vp,&cb->auto_terminate);
break;
case PROP_COUNTER:
if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->counter))
return JS_FALSE;
break;
case PROP_TIME_LIMIT:
if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->limit))
return JS_FALSE;
break;
case PROP_GC_INTERVAL:
if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->gc_interval))
return JS_FALSE;
break;
case PROP_YIELD_INTERVAL:
if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->yield_interval))
return JS_FALSE;
break;
#ifdef jscntxt_h___
case PROP_MAXBYTES:
if(!JS_ValueToInt32(cx, *vp, (int32*)&cx->runtime->gcMaxBytes))
return JS_FALSE;
break;
case PROP_KEEPGOING:
if (cb->events_supported)
JS_ValueToBoolean(cx, *vp, &cb->keepGoing);
break;
}
return(JS_TRUE);
}
#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>")
},
#ifdef jscntxt_h___
{ "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")
},
#endif
{ "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")
},
{0}
};
#ifdef BUILD_JSDOCS
static const char* prop_desc[] = {
"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>"
,NULL
};
#endif
js_CommonOperationCallback(JSContext *cx, js_callback_t* cb)
{
js_callback_t *top_cb;
cb->counter++;
/* Terminated? */
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);
}
}
}
/* Infinite loop? */
if(cb->limit && cb->counter > cb->limit) {
JS_ReportError(cx,"Infinite loop (%lu operation callbacks) detected",cb->counter);
cb->counter=0;
return(JS_FALSE);
}
/* Give up timeslices every once in a while */
if(cb->yield_interval && (cb->counter%cb->yield_interval)==0) {
rc=JS_SUSPENDREQUEST(cx);
YIELD();
JS_RESUMEREQUEST(cx, rc);
/* Permit other contexts to run GC */
JS_YieldRequest(cx);
/* Periodic Garbage Collection */
if(cb->gc_interval && (cb->counter%cb->gc_interval)==0)
JS_MaybeGC(cx), cb->gc_attempts++;
return(JS_TRUE);
}
// 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;
uintN arg=0;
JSObject* js_scope = NULL;
JSObject* pscope;
jsval rval = JSVAL_VOID;
uintN i;
js_callback_t * js_callback;
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
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])) {
if (js_scope == scope) {
free(cmd);
free(startup_dir);
JS_ReportError(cx, "Invalid Scope");
return(JS_FALSE);
}
}
if(js_scope == NULL) {
free(cmd);
free(startup_dir);
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);
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);
if (*path == '\0')
SAFECOPY(path, cmd);
JS_ReportError(cx, "Script file (%s) does not exist", path);
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++;
}
JS_DefineProperty(cx, js_scope, "argc", INT_TO_JSVAL(nargc)
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;
}
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);
JS_ClearPendingException(cx);
return(JS_TRUE);
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)
rval = exit_code;
JS_SET_RVAL(cx, arglist, rval);
JS_ClearPendingException(cx);
js_EvalOnExit(cx, js_scope, js_callback);
JS_ReportPendingException(cx);
JS_DestroyScript(cx, js_script);
JS_GC(cx);
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);
size_t buflen;
JSString* str;
JSObject* script;
JSContext* cx;
JSObject* obj;
JSErrorReporter reporter;
JS_SET_RVAL(cx, arglist, JSVAL_VOID);
if(argc<1)
return(JS_TRUE);
if((str=JS_ValueToString(parent_cx, argv[0]))==NULL)
return(JS_FALSE);
JSSTRING_TO_MSTRING(parent_cx, str, buf, &buflen);
HANDLE_PENDING(parent_cx, buf);
if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL) {
free(buf);
return(JS_FALSE);
/* 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);
return(JS_FALSE);
}
if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
jsval rval;
JS_ExecuteScript(cx, obj, script, &rval);
JS_SET_RVAL(cx, arglist, rval);
}
JS_DestroyContext(cx);
return(JS_TRUE);
}
static JSBool
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;
js_callback_t* cb;
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);
cb->gc_attempts++;
return(JS_TRUE);
}
static JSBool
js_report_error(JSContext *cx, uintN argc, jsval *arglist)
{
jsval *argv=JS_ARGV(cx, arglist);
JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
HANDLE_PENDING(cx, p);
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);
}
static JSBool
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);
global_private_t* pd;
str_list_t oldlist;
if((pd=(global_private_t*)JS_GetPrivate(cx,glob))==NULL)
return(JS_FALSE);
if(glob==scope) {
if(pd->exit_func==NULL)
pd->exit_func=strListInit();
list=pd->exit_func;
}
else {
/* First, look for an existing onexit scope for this scope */
for (oes = pd->onexit; oes; oes = oes->next) {
if (oes->scope == scope)
break;
/* 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");
}
oes->next = pd->onexit;
pd->onexit = oes;
oes->scope = scope;
oes->onexit = strListInit();
JS_AddObjectRoot(cx, &oes->scope);
JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
HANDLE_PENDING(cx, p);
strListPush(&list,p);
if(oldlist != list) {
if(glob==scope)
pd->exit_func=list;
else
return(JS_TRUE);
}
static JSBool
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));
return(JS_TRUE);
}
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);
}
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);
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
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);
ev->id = cb->next_eid++;
cb->events = ev;
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(ev->id));
return JS_TRUE;
}
JSBool
js_clear_event(JSContext *cx, jsval *arglist, js_callback_t *cb, enum js_event_type et, int ididx)
struct js_event_list *ev;
struct js_event_list *nev;
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;
}
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);
}
static JSBool
js_clearTimeout(JSContext *cx, uintN argc, jsval *arglist)
{
return js_internal_clear_event(cx, argc, arglist, JS_EVENT_TIMEOUT);
}
static JSBool
js_clearInterval(JSContext *cx, uintN argc, jsval *arglist)
{
return js_internal_clear_event(cx, argc, arglist, JS_EVENT_INTERVAL);
}
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);
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
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;
ev->id = cb->next_eid++;
cb->events = ev;
JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(ev->id));
return JS_TRUE;
}
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
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;
}
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
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;