Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

js_internal.c 42.8 KB
Newer Older
1
/* Synchronet "js" object, for internal JavaScript callback and GC control */
2 3 4 5 6

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *																			*
 * 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
23
#include "sockwrap.h"
24
#include "js_request.h"
Deucе's avatar
Deucе committed
25
#include "js_socket.h"
26

27 28 29
/* SpiderMonkey: */
#include <jsdbgapi.h>

30
enum {
31 32
	 PROP_VERSION
	,PROP_TERMINATED
33
	,PROP_AUTO_TERMINATE
34 35
	,PROP_COUNTER
	,PROP_TIME_LIMIT
36 37
	,PROP_YIELD_INTERVAL
	,PROP_GC_INTERVAL
38
	,PROP_GC_ATTEMPTS
39 40
#ifdef jscntxt_h___
	,PROP_GC_COUNTER
41 42 43
	,PROP_GC_LASTBYTES
	,PROP_BYTES
	,PROP_MAXBYTES
44
#endif
45
	,PROP_GLOBAL
46
	,PROP_OPTIONS
Deucе's avatar
Deucе committed
47
	,PROP_KEEPGOING
48 49
};

50
static JSBool js_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
51
{
52
	jsval idval;
53
    jsint			tiny;
54
	js_callback_t*	cb;
55
	js_callback_t*	top_cb;
56

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

60 61
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
62 63

	switch(tiny) {
64 65 66
		case PROP_VERSION:
			*vp=STRING_TO_JSVAL(JS_NewStringCopyZ(cx,(char *)JS_GetImplementationVersion()));
			break;
67
		case PROP_TERMINATED:
68 69 70 71 72 73 74
			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)
75 76
				*vp=JSVAL_FALSE;
			else
77
				*vp=BOOLEAN_TO_JSVAL(*top_cb->terminated);
78
			break;
79
		case PROP_AUTO_TERMINATE:
80
			*vp=BOOLEAN_TO_JSVAL(cb->auto_terminate);
81
			break;
82 83
		case PROP_COUNTER:
			*vp=DOUBLE_TO_JSVAL((double)cb->counter);
84
			break;
85 86
		case PROP_TIME_LIMIT:
			*vp=DOUBLE_TO_JSVAL(cb->limit);
87 88
			break;
		case PROP_YIELD_INTERVAL:
89
			*vp=DOUBLE_TO_JSVAL((double)cb->yield_interval);
90 91
			break;
		case PROP_GC_INTERVAL:
92
			*vp=DOUBLE_TO_JSVAL((double)cb->gc_interval);
93
			break;
94
		case PROP_GC_ATTEMPTS:
95
			*vp=DOUBLE_TO_JSVAL((double)cb->gc_attempts);
96
			break;
97
#ifdef jscntxt_h___
98
		case PROP_GC_COUNTER:
99
			*vp=UINT_TO_JSVAL(cx->runtime->gcNumber);
100 101
			break;
		case PROP_GC_LASTBYTES:
102
			*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcLastBytes);
103
			break;
104
		case PROP_BYTES:
105
			*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcBytes);
106
			break;
107
		case PROP_MAXBYTES:
108
			*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcMaxBytes);
109
			break;
110
#endif
111
		case PROP_GLOBAL:
112
			*vp = OBJECT_TO_JSVAL(JS_GetGlobalObject(cx));	
113
			break;
114 115 116
		case PROP_OPTIONS:
			*vp = UINT_TO_JSVAL(JS_GetOptions(cx));
			break;
Deucе's avatar
Deucе committed
117 118 119 120 121 122
		case PROP_KEEPGOING:
			if (cb->events_supported)
				*vp = BOOLEAN_TO_JSVAL(cb->keepGoing);
			else
				*vp = JSVAL_FALSE;
			break;
123 124 125 126 127
	}

	return(JS_TRUE);
}

128
static JSBool js_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
129
{
130
	jsval idval;
131
    jsint			tiny;
132
	js_callback_t*	cb;
133

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

137 138
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
139 140

	switch(tiny) {
141
		case PROP_TERMINATED:
142 143
			if(cb->terminated!=NULL)
				JS_ValueToBoolean(cx, *vp, (int *)cb->terminated);
144
			break;
145
		case PROP_AUTO_TERMINATE:
146
			JS_ValueToBoolean(cx,*vp,&cb->auto_terminate);
147
			break;
148 149
		case PROP_COUNTER:
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->counter))
150
				return JS_FALSE;
151
			break;
152 153
		case PROP_TIME_LIMIT:
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->limit))
154
				return JS_FALSE;
155 156
			break;
		case PROP_GC_INTERVAL:
157
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->gc_interval))
158
				return JS_FALSE;
159 160
			break;
		case PROP_YIELD_INTERVAL:
161
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->yield_interval))
162
				return JS_FALSE;
163
			break;
164
#ifdef jscntxt_h___
165
		case PROP_MAXBYTES:
166 167
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cx->runtime->gcMaxBytes))
				return JS_FALSE;
168
			break;
169
#endif
Deucе's avatar
Deucе committed
170 171 172 173
		case PROP_KEEPGOING:
			if (cb->events_supported)
				JS_ValueToBoolean(cx, *vp, &cb->keepGoing);
			break;
174 175
	}

176
	return(JS_TRUE);
177 178
}

179
#define PROP_FLAGS	JSPROP_ENUMERATE|JSPROP_READONLY
180

181 182 183
static jsSyncPropertySpec js_properties[] = {
/*		 name,				tinyid,						flags,		ver	*/

184
	{	"version",			PROP_VERSION,		PROP_FLAGS,			311 },
185
	{	"auto_terminate",	PROP_AUTO_TERMINATE,JSPROP_ENUMERATE,	311 },
186
	{	"terminated",		PROP_TERMINATED,	JSPROP_ENUMERATE,	311 },
187 188 189 190
	{	"branch_counter",	PROP_COUNTER,		0,					311 },
	{	"counter",			PROP_COUNTER,		JSPROP_ENUMERATE,	316 },
	{	"branch_limit",		PROP_TIME_LIMIT,	0,					311 },
	{	"time_limit",		PROP_TIME_LIMIT,	JSPROP_ENUMERATE,	316 },
191 192
	{	"yield_interval",	PROP_YIELD_INTERVAL,JSPROP_ENUMERATE,	311 },
	{	"gc_interval",		PROP_GC_INTERVAL,	JSPROP_ENUMERATE,	311 },
193
	{	"gc_attempts",		PROP_GC_ATTEMPTS,	PROP_FLAGS,			311 },
194
#ifdef jscntxt_h___
195 196 197
	{	"gc_counter",		PROP_GC_COUNTER,	PROP_FLAGS,			311 },
	{	"gc_last_bytes",	PROP_GC_LASTBYTES,	PROP_FLAGS,			311 },
	{	"bytes",			PROP_BYTES,			PROP_FLAGS,			311 },
198
	{	"max_bytes",		PROP_MAXBYTES,		JSPROP_ENUMERATE,	311 },
199
#endif
rswindell's avatar
rswindell committed
200
	{	"global",			PROP_GLOBAL,		PROP_FLAGS,			314 },
201
	{	"options",			PROP_OPTIONS,		PROP_FLAGS,			31802 },
Deucе's avatar
Deucе committed
202
	{	"do_callbacks",		PROP_KEEPGOING,		JSPROP_ENUMERATE,			31802 },
203 204 205
	{0}
};

206
#ifdef BUILD_JSDOCS
207
static char* prop_desc[] = {
208 209
	 "JavaScript engine version information (AKA system.js_version)"
	,"set to <i>false</i> to disable the automatic termination of the script upon external request"
210
	,"termination has been requested (stop execution as soon as possible)"
211 212
	,"number of operation callbacks performed in this runtime"
	,"maximum number of operation callbacks, used for infinite-loop detection (0=disabled)"
213
	,"interval of periodic time-slice yields (lower number=higher frequency, 0=disabled)"
214
	,"interval of periodic garbage collection attempts (lower number=higher frequency, 0=disabled)"
215
	,"number of garbage collections attempted in this runtime - <small>READ ONLY</small>"
216
#ifdef jscntxt_h___
217 218 219
	,"number of garbage collections performed in this runtime - <small>READ ONLY</small>"
	,"number of heap bytes in use after last garbage collection - <small>READ ONLY</small>"
	,"number of heap bytes currently in use - <small>READ ONLY</small>"
220
	,"maximum number of bytes available for heap"
221
#endif
222
	,"global (top level) object - <small>READ ONLY</small>"
223
	,"option flags - <small>READ ONLY</small>"
Deucе's avatar
Deucе committed
224
	,"do callbacks after script finishes running"
deuce's avatar
deuce committed
225
	/* New properties go here... */
226 227
	,"full path and filename of JS file executed"
	,"directory of executed JS file"
228
	,"JS filename executed (with no path)"
229 230
	,"Either the configured startup directory in SCFG (for externals) or the cwd when jsexec is started"
	,"global scope for this script"
231
	,"load() search path array.<br>For relative load paths (e.g. not beginning with '/' or '\\'), "
deuce's avatar
deuce committed
232 233 234 235 236 237 238
		"the path is assumed to be a sub-directory of the (configurable) mods or exec directories "
		"and is searched accordingly. "
		"So, by default, load(\"somefile.js\") will search in this order:<br>"
		"mods/load/somefile.js<br>"
		"exec/load/somefile.js<br>"
		"mods/somefile.js<br>"
		"exec/somefile.js<br>"
239 240 241 242
	,NULL
};
#endif

243
JSBool DLLCALL
244
js_CommonOperationCallback(JSContext *cx, js_callback_t* cb)
245
{
246 247
	js_callback_t *top_cb;

248
	cb->counter++;
249

250
	/* Terminated? */
251 252 253 254 255 256 257 258
	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);
			}
		}
259 260
	}

261
	/* Infinite loop? */
262
	if(cb->limit && cb->counter > cb->limit) {
263
		JS_ReportError(cx,"Infinite loop (%lu operation callbacks) detected",cb->counter);
264
		cb->counter=0;
265 266
		return(JS_FALSE);
	}
267

268
	/* Give up timeslices every once in a while */
269
	if(cb->yield_interval && (cb->counter%cb->yield_interval)==0) {
deuce's avatar
deuce committed
270 271
		jsrefcount	rc;

272
		rc=JS_SUSPENDREQUEST(cx);
273
		YIELD();
274
		JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
275
	}
276

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

280
	/* Periodic Garbage Collection */
281 282
	if(cb->gc_interval && (cb->counter%cb->gc_interval)==0)
		JS_MaybeGC(cx), cb->gc_attempts++;
283 284 285 286

    return(JS_TRUE);
}

deuce's avatar
deuce committed
287 288 289 290 291 292 293 294
// 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;
295
	uintN		arg=0;
deuce's avatar
deuce committed
296
	char		path[MAX_PATH+1] = "";
deuce's avatar
deuce committed
297
	JSObject*	scope = JS_GetScopeChain(cx);
298 299
	JSObject*	js_scope = NULL;
	JSObject*	pscope;
deuce's avatar
deuce committed
300 301
	JSObject*	js_script=NULL;
	JSObject*	nargv;
302
	jsval		rval = JSVAL_VOID;
deuce's avatar
deuce committed
303
	jsrefcount	rc;
304
	uintN		i;
deuce's avatar
deuce committed
305
	jsval		val;
deuce's avatar
deuce committed
306 307
	JSObject *js_obj;
	JSObject *pjs_obj;
308
	js_callback_t *	js_callback;
deuce's avatar
deuce committed
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339

	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);
			}
		}
	}

340
	if (argc > arg && JSVAL_IS_OBJECT(argv[arg])) {
deuce's avatar
deuce committed
341
		js_scope = JSVAL_TO_OBJECT(argv[arg++]);
342 343 344 345 346 347 348
		if (js_scope == scope) {
			free(cmd);
			free(startup_dir);
			JS_ReportError(cx, "Invalid Scope");
			return(JS_FALSE);
		}
	}
deuce's avatar
deuce committed
349
	else {
350 351
		free(cmd);
		free(startup_dir);
deuce's avatar
deuce committed
352 353 354 355
		JS_ReportError(cx, "Invalid Scope");
		return(JS_FALSE);
	}

356 357 358 359 360 361 362 363 364 365 366 367 368
	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
369 370 371 372 373 374 375 376 377 378 379 380 381 382
	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
383 384 385 386 387 388 389 390 391
		/* 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
392 393 394 395 396 397
			}
		}
	}
	free(cmd);

	if(!fexistcase(path)) {
398
		JS_ReportError(cx, "Script file (%s) does not exist", path);
deuce's avatar
deuce committed
399 400 401 402 403 404 405 406 407
		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);

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

414
	JS_DefineProperty(cx, js_scope, "argc", INT_TO_JSVAL(nargc)
deuce's avatar
deuce committed
415 416
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
	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);
Rob Swindell's avatar
Rob Swindell committed
439 440 441 442 443
			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);
				}
444 445 446 447 448 449 450 451 452 453 454
		}
		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
455 456 457
	js_script=JS_CompileFile(cx, js_scope, path);

	if(js_script == NULL) {
458 459 460 461 462
		/* 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
463
		}
464 465
		JS_ClearPendingException(cx);
		return(JS_TRUE);
deuce's avatar
deuce committed
466 467 468
	}

	JS_ExecuteScript(cx, js_scope, js_script, &rval);
469 470 471 472
	if (JS_IsExceptionPending(cx)) {
		JS_GetPendingException(cx, &rval);
	}
	else {
473
		jsval exit_code = JSVAL_VOID;
474
		if(JS_GetProperty(cx, js_scope, "exit_code", &exit_code) && exit_code != JSVAL_VOID)
475
			rval = exit_code;
deuce's avatar
deuce committed
476
	}
477 478 479 480
	JS_SET_RVAL(cx, arglist, rval);
	JS_ClearPendingException(cx);

	js_EvalOnExit(cx, js_scope, js_callback);
deuce's avatar
deuce committed
481 482
	JS_ReportPendingException(cx);
	JS_DestroyScript(cx, js_script);
483
	JS_GC(cx);
deuce's avatar
deuce committed
484 485 486 487

	return JS_TRUE;
}

488 489 490 491 492 493 494 495
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
};

496 497
/* Execute a string in its own context (away from Synchronet objects) */
static JSBool
498
js_eval(JSContext *parent_cx, uintN argc, jsval *arglist)
499
{
500
	jsval *argv=JS_ARGV(parent_cx, arglist);
501
	char*			buf = NULL;
502 503
	size_t			buflen;
	JSString*		str;
504
    JSObject*		script;
505 506
	JSContext*		cx;
	JSObject*		obj;
507
	JSErrorReporter	reporter;
508

509 510
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

511 512 513
	if(argc<1)
		return(JS_TRUE);

514
	if((str=JS_ValueToString(parent_cx, argv[0]))==NULL)
515
		return(JS_FALSE);
516
	JSSTRING_TO_MSTRING(parent_cx, str, buf, &buflen);
517
	HANDLE_PENDING(parent_cx, buf);
deuce's avatar
deuce committed
518
	if(buf==NULL)
519
		return(JS_TRUE);
520

521 522
	if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL) {
		free(buf);
523
		return(JS_FALSE);
524
	}
525

526
	/* Use the error reporter from the parent context */
527 528
	reporter=JS_SetErrorReporter(parent_cx,NULL);
	JS_SetErrorReporter(parent_cx,reporter);
529
	JS_SetErrorReporter(cx,reporter);
530

531
	/* Use the operation callback from the parent context */
532
	JS_SetContextPrivate(cx, JS_GetContextPrivate(parent_cx));
deuce's avatar
deuce committed
533
	JS_SetOperationCallback(cx, JS_GetOperationCallback(parent_cx));
534

535
	if((obj=JS_NewCompartmentAndGlobalObject(cx, &eval_class, NULL))==NULL
536 537
		|| !JS_InitStandardClasses(cx,obj)) {
		JS_DestroyContext(cx);
538
		free(buf);
539 540
		return(JS_FALSE);
	}
541

542
	if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
deuce's avatar
deuce committed
543 544 545 546
		jsval	rval;

		JS_ExecuteScript(cx, obj, script, &rval);
		JS_SET_RVAL(cx, arglist, rval);
547
	}
548
	free(buf);
549 550 551 552 553 554

	JS_DestroyContext(cx);

    return(JS_TRUE);
}

555
static JSBool
556
js_gc(JSContext *cx, uintN argc, jsval *arglist)
557
{
558 559
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
560
	JSBool			forced=JS_TRUE;
561
	js_callback_t*	cb;
562

deuce's avatar
deuce committed
563 564
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

565
	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
566
		return(JS_FALSE);
567 568 569 570 571 572 573 574 575

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

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

576
	cb->gc_attempts++;
577

578 579 580
	return(JS_TRUE);
}

581
static JSBool
582
js_report_error(JSContext *cx, uintN argc, jsval *arglist)
583
{
584
	jsval *argv=JS_ARGV(cx, arglist);
585
	char	*p = NULL;
deuce's avatar
deuce committed
586

587
	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
588
	HANDLE_PENDING(cx, p);
589 590 591 592 593 594
	if(p==NULL)
		JS_ReportError(cx,"NULL");
	else {
		JS_ReportError(cx,"%s",p);
		free(p);
	}
595

596 597
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

598 599 600 601 602
	if(argc>1 && argv[1]==JSVAL_TRUE)
		return(JS_FALSE);	/* fatal */

	return(JS_TRUE);
}
603

604
static JSBool
605
js_on_exit(JSContext *cx, uintN argc, jsval *arglist)
606
{
607
	JSObject *scope=JS_GetScopeChain(cx);
608
	JSObject *glob=JS_GetGlobalObject(cx);
609
	jsval *argv=JS_ARGV(cx, arglist);
610
	global_private_t*	pd;
Deucе's avatar
Deucе committed
611
	struct js_onexit_scope *oes = NULL;
612
	str_list_t	oldlist;
Deucе's avatar
Deucе committed
613
	str_list_t	list;
614
	char		*p = NULL;
615

deuce's avatar
deuce committed
616 617
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

Deucе's avatar
Deucе committed
618 619
	if((pd=(global_private_t*)JS_GetPrivate(cx,glob))==NULL)
		return(JS_FALSE);
620
	if(glob==scope) {
621 622 623 624 625
		if(pd->exit_func==NULL)
			pd->exit_func=strListInit();
		list=pd->exit_func;
	}
	else {
Deucе's avatar
Deucе committed
626 627 628 629
		/* 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
630
		}
Deucе's avatar
Deucе committed
631 632 633 634 635 636

		/* 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
637
				return JS_FALSE;
Deucе's avatar
Deucе committed
638 639 640 641 642 643
			}
			oes->next = pd->onexit;
			pd->onexit = oes;
			oes->scope = scope;
			oes->onexit = strListInit();
			JS_AddObjectRoot(cx, &oes->scope);
644
		}
Deucе's avatar
Deucе committed
645
		list = oes->onexit;
646
	}
647

648
	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
649
	HANDLE_PENDING(cx, p);
650 651
	if(!p)
		return JS_TRUE;
deuce's avatar
deuce committed
652
	oldlist=list;
653
	strListPush(&list,p);
654
	free(p);
deuce's avatar
deuce committed
655 656 657 658
	if(oldlist != list) {
		if(glob==scope)
			pd->exit_func=list;
		else
Deucе's avatar
Deucе committed
659
			oes->onexit = list;
deuce's avatar
deuce committed
660
	}
661 662 663
	return(JS_TRUE);
}

664
static JSBool
665
js_get_parent(JSContext *cx, uintN argc, jsval *arglist)
666
{
667
	jsval *argv=JS_ARGV(cx, arglist);
668 669 670 671 672 673
	JSObject* child=NULL;
	JSObject* parent;

	if(JS_ValueToObject(cx, argv[0], &child)
		&& child!=NULL
		&& (parent=JS_GetParent(cx,child))!=NULL)
674
		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(parent));
675 676 677 678

	return(JS_TRUE);
}

679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
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, "get_size() error!  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, "get_size() error!  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
708 709 710 711 712 713 714 715
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;
716
	uint64_t now = (uint64_t)(xp_timer() * 1000);
Deucе's avatar
Deucе committed
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
	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;
755
	ev->data.timeout.end = (uint64_t)(now + timeout);
Deucе's avatar
Deucе committed
756 757 758 759 760 761 762 763
	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
764
js_clear_event(JSContext *cx, jsval *arglist, js_callback_t *cb, enum js_event_type et, int ididx)
Deucе's avatar
Deucе committed
765 766
{
	int32 id;
Deucе's avatar
Deucе committed
767
	jsval	*argv=JS_ARGV(cx, arglist);
Deucе's avatar
Deucе committed
768 769 770
	struct js_event_list *ev;
	struct js_event_list *nev;

Deucе's avatar
Deucе committed
771
	if (!JS_ValueToInt32(cx, argv[ididx], &id)) {
Deucе's avatar
Deucе committed
772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
		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
796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
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
814 815 816
static JSBool
js_clearTimeout(JSContext *cx, uintN argc, jsval *arglist)
{
Deucе's avatar
Deucе committed
817
	return js_internal_clear_event(cx, argc, arglist, JS_EVENT_TIMEOUT);
Deucе's avatar
Deucе committed
818 819 820 821 822
}

static JSBool
js_clearInterval(JSContext *cx, uintN argc, jsval *arglist)
{
Deucе's avatar
Deucе committed
823
	return js_internal_clear_event(cx, argc, arglist, JS_EVENT_INTERVAL);
Deucе's avatar
Deucе committed
824 825 826 827 828 829 830 831 832 833
}

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;
834
	uint64_t now = (uint64_t)(xp_timer() * 1000);
Deucе's avatar
Deucе committed
835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873
	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;
874
	ev->data.interval.period =(uint64_t)period;
Deucе's avatar
Deucе committed
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 908 909 910 911 912 913 914 915 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 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 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044