js_internal.c 24.7 KB
Newer Older
1
2
/* js_internal.c */

3
/* Synchronet "js" object, for internal JavaScript callback and GC control */
4
5
6
7
8
9
10

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
11
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 *																			*
 * 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										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
39
#include "js_request.h"
40

41
42
43
/* SpiderMonkey: */
#include <jsdbgapi.h>

44
enum {
45
46
	 PROP_VERSION
	,PROP_TERMINATED
47
	,PROP_AUTO_TERMINATE
48
49
	,PROP_COUNTER
	,PROP_TIME_LIMIT
50
51
	,PROP_YIELD_INTERVAL
	,PROP_GC_INTERVAL
52
	,PROP_GC_ATTEMPTS
53
54
#ifdef jscntxt_h___
	,PROP_GC_COUNTER
55
56
57
	,PROP_GC_LASTBYTES
	,PROP_BYTES
	,PROP_MAXBYTES
58
#endif
59
	,PROP_GLOBAL
60
61
};

62
static JSBool js_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
63
{
64
	jsval idval;
65
    jsint			tiny;
66
	js_callback_t*	cb;
67
	js_callback_t*	top_cb;
68

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

72
73
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
74
75

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

	return(JS_TRUE);
}

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

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

140
141
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
142
143

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

175
	return(JS_TRUE);
176
177
}

178
#define PROP_FLAGS	JSPROP_ENUMERATE|JSPROP_READONLY
179

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

183
	{	"version",			PROP_VERSION,		PROP_FLAGS,			311 },
184
	{	"auto_terminate",	PROP_AUTO_TERMINATE,JSPROP_ENUMERATE,	311 },
185
	{	"terminated",		PROP_TERMINATED,	JSPROP_ENUMERATE,	311 },
186
187
188
189
	{	"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 },
190
191
	{	"yield_interval",	PROP_YIELD_INTERVAL,JSPROP_ENUMERATE,	311 },
	{	"gc_interval",		PROP_GC_INTERVAL,	JSPROP_ENUMERATE,	311 },
192
	{	"gc_attempts",		PROP_GC_ATTEMPTS,	PROP_FLAGS,			311 },
193
#ifdef jscntxt_h___
194
195
196
	{	"gc_counter",		PROP_GC_COUNTER,	PROP_FLAGS,			311 },
	{	"gc_last_bytes",	PROP_GC_LASTBYTES,	PROP_FLAGS,			311 },
	{	"bytes",			PROP_BYTES,			PROP_FLAGS,			311 },
197
	{	"max_bytes",		PROP_MAXBYTES,		JSPROP_ENUMERATE,	311 },
198
#endif
rswindell's avatar
rswindell committed
199
	{	"global",			PROP_GLOBAL,		PROP_FLAGS,			314 },
200
201
202
	{0}
};

203
#ifdef BUILD_JSDOCS
204
static char* prop_desc[] = {
205
206
	 "JavaScript engine version information (AKA system.js_version)"
	,"set to <i>false</i> to disable the automatic termination of the script upon external request"
207
	,"termination has been requested (stop execution as soon as possible)"
208
209
	,"number of operation callbacks performed in this runtime"
	,"maximum number of operation callbacks, used for infinite-loop detection (0=disabled)"
210
	,"interval of periodic time-slice yields (lower number=higher frequency, 0=disabled)"
211
	,"interval of periodic garbage collection attempts (lower number=higher frequency, 0=disabled)"
212
	,"number of garbage collections attempted in this runtime - <small>READ ONLY</small>"
213
#ifdef jscntxt_h___
214
215
216
	,"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>"
217
	,"maximum number of bytes available for heap"
218
#endif
219
	,"global (top level) object - <small>READ ONLY</small>"
deuce's avatar
deuce committed
220
	/* New properties go here... */
221
	,"load() search path array.<br>For relative load paths (e.g. not beginning with '/' or '\\'), "
deuce's avatar
deuce committed
222
223
224
225
226
227
228
229
230
		"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>"
	,"full path and filename of JS file executed"
	,"JS filename executed (with no path)"
deuce's avatar
deuce committed
231
	,"directory of executed JS file"
232
	,"Either the configured startup directory in SCFG (for externals) or the cwd when jsexec is started"
deuce's avatar
deuce committed
233
	,"global scope for this script"
234
235
236
237
	,NULL
};
#endif

238
JSBool DLLCALL
239
js_CommonOperationCallback(JSContext *cx, js_callback_t* cb)
240
{
241
242
	js_callback_t *top_cb;

243
	cb->counter++;
244

245
	/* Terminated? */
246
247
248
249
250
251
252
253
	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);
			}
		}
254
255
	}

256
	/* Infinite loop? */
257
	if(cb->limit && cb->counter > cb->limit) {
258
		JS_ReportError(cx,"Infinite loop (%lu operation callbacks) detected",cb->counter);
259
		cb->counter=0;
260
261
		return(JS_FALSE);
	}
262

263
	/* Give up timeslices every once in a while */
264
	if(cb->yield_interval && (cb->counter%cb->yield_interval)==0) {
deuce's avatar
deuce committed
265
266
		jsrefcount	rc;

267
		rc=JS_SUSPENDREQUEST(cx);
268
		YIELD();
269
		JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
270
	}
271

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

275
	/* Periodic Garbage Collection */
276
277
	if(cb->gc_interval && (cb->counter%cb->gc_interval)==0)
		JS_MaybeGC(cx), cb->gc_attempts++;
278
279
280
281

    return(JS_TRUE);
}

deuce's avatar
deuce committed
282
283
284
285
286
287
288
289
// 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;
290
	uintN		arg=0;
deuce's avatar
deuce committed
291
292
	char		path[MAX_PATH+1];
	JSObject*	scope = JS_GetScopeChain(cx);
293
294
	JSObject*	js_scope = NULL;
	JSObject*	pscope;
deuce's avatar
deuce committed
295
296
297
298
	JSObject*	js_script=NULL;
	JSObject*	nargv;
	jsval		rval;
	jsrefcount	rc;
299
	uintN		i;
deuce's avatar
deuce committed
300
	jsval		val;
deuce's avatar
deuce committed
301
302
	JSObject *js_obj;
	JSObject *pjs_obj;
303
	js_callback_t *	js_callback;
deuce's avatar
deuce committed
304
305
306
307
308
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

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

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

351
352
353
354
355
356
357
358
359
360
361
362
363
	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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
	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
378
379
380
381
382
383
384
385
386
		/* 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
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
			}
		}
	}
	free(cmd);

	if(!fexistcase(path)) {
		JS_ReportError(cx, "Can't find script");
		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);

	for(i=arg; i<argc; i++)
		JS_SetElement(cx, nargv, i-arg, &argv[i]);

	JS_DefineProperty(cx, js_scope, "argc", INT_TO_JSVAL(argc-arg)
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
	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);
			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);
			}
		}
		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
447
448
449
	js_script=JS_CompileFile(cx, js_scope, path);

	if(js_script == NULL) {
450
451
452
453
454
455
		/* If the script fails to compile, it's not a fatal error
		 * for the caller. */
		free(startup_dir);
		if (JS_IsExceptionPending(cx)) {
			JS_GetPendingException(cx, &rval);
			JS_SET_RVAL(cx, arglist, rval);
deuce's avatar
deuce committed
456
		}
457
458
		JS_ClearPendingException(cx);
		return(JS_TRUE);
deuce's avatar
deuce committed
459
460
461
	}

	JS_ExecuteScript(cx, js_scope, js_script, &rval);
462
463
464
465
	if (JS_IsExceptionPending(cx)) {
		JS_GetPendingException(cx, &rval);
	}
	else {
deuce's avatar
deuce committed
466
467
		JS_GetProperty(cx, js_scope, "exit_code", &rval);
	}
468
469
470
471
	JS_SET_RVAL(cx, arglist, rval);
	JS_ClearPendingException(cx);

	js_EvalOnExit(cx, js_scope, js_callback);
deuce's avatar
deuce committed
472
473
	JS_ReportPendingException(cx);
	JS_DestroyScript(cx, js_script);
474
	JS_GC(cx);
deuce's avatar
deuce committed
475
476
477
478

	return JS_TRUE;
}

479
480
481
482
483
484
485
486
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
};

487
488
/* Execute a string in its own context (away from Synchronet objects) */
static JSBool
489
js_eval(JSContext *parent_cx, uintN argc, jsval *arglist)
490
{
491
	jsval *argv=JS_ARGV(parent_cx, arglist);
492
	char*			buf = NULL;
493
494
	size_t			buflen;
	JSString*		str;
495
    JSObject*		script;
496
497
	JSContext*		cx;
	JSObject*		obj;
498
	JSErrorReporter	reporter;
499

500
501
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

502
503
504
	if(argc<1)
		return(JS_TRUE);

505
	if((str=JS_ValueToString(parent_cx, argv[0]))==NULL)
506
		return(JS_FALSE);
507
	JSSTRING_TO_MSTRING(parent_cx, str, buf, &buflen);
508
	HANDLE_PENDING(parent_cx, buf);
deuce's avatar
deuce committed
509
	if(buf==NULL)
510
		return(JS_TRUE);
511

512
513
	if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL) {
		free(buf);
514
		return(JS_FALSE);
515
	}
516

517
	/* Use the error reporter from the parent context */
518
519
	reporter=JS_SetErrorReporter(parent_cx,NULL);
	JS_SetErrorReporter(parent_cx,reporter);
520
	JS_SetErrorReporter(cx,reporter);
521

522
	/* Use the operation callback from the parent context */
523
	JS_SetContextPrivate(cx, JS_GetContextPrivate(parent_cx));
deuce's avatar
deuce committed
524
	JS_SetOperationCallback(cx, JS_GetOperationCallback(parent_cx));
525

526
	if((obj=JS_NewCompartmentAndGlobalObject(cx, &eval_class, NULL))==NULL
527
528
		|| !JS_InitStandardClasses(cx,obj)) {
		JS_DestroyContext(cx);
529
		free(buf);
530
531
		return(JS_FALSE);
	}
532

533
	if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
deuce's avatar
deuce committed
534
535
536
537
		jsval	rval;

		JS_ExecuteScript(cx, obj, script, &rval);
		JS_SET_RVAL(cx, arglist, rval);
538
	}
539
	free(buf);
540
541
542
543
544
545

	JS_DestroyContext(cx);

    return(JS_TRUE);
}

546
static JSBool
547
js_gc(JSContext *cx, uintN argc, jsval *arglist)
548
{
549
550
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
551
	JSBool			forced=JS_TRUE;
552
	js_callback_t*	cb;
553

deuce's avatar
deuce committed
554
555
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

556
	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
557
		return(JS_FALSE);
558
559
560
561
562
563
564
565
566

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

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

567
	cb->gc_attempts++;
568

569
570
571
	return(JS_TRUE);
}

572
static JSBool
573
js_report_error(JSContext *cx, uintN argc, jsval *arglist)
574
{
575
	jsval *argv=JS_ARGV(cx, arglist);
576
	char	*p = NULL;
deuce's avatar
deuce committed
577

578
	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
579
	HANDLE_PENDING(cx, p);
580
581
582
583
584
585
	if(p==NULL)
		JS_ReportError(cx,"NULL");
	else {
		JS_ReportError(cx,"%s",p);
		free(p);
	}
586

587
588
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

589
590
591
592
593
	if(argc>1 && argv[1]==JSVAL_TRUE)
		return(JS_FALSE);	/* fatal */

	return(JS_TRUE);
}
594

595
static JSBool
596
js_on_exit(JSContext *cx, uintN argc, jsval *arglist)
597
{
598
	JSObject *scope=JS_GetScopeChain(cx);
599
	JSObject *glob=JS_GetGlobalObject(cx);
600
	jsval *argv=JS_ARGV(cx, arglist);
601
602
	global_private_t*	pd;
	str_list_t	list;
603
	str_list_t	oldlist;
604
	char		*p = NULL;
605

deuce's avatar
deuce committed
606
607
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

608
	if(glob==scope) {
609
610
611
612
613
614
615
		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 {
616
		list=(str_list_t)JS_GetPrivate(cx,scope);
617
618
		if(list==NULL) {
			list=strListInit();
619
			JS_SetPrivate(cx,scope,list);
620
621
		}
	}
622

623
	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
624
	HANDLE_PENDING(cx, p);
625
626
	if(!p)
		return JS_TRUE;
deuce's avatar
deuce committed
627
	oldlist=list;
628
	strListPush(&list,p);
629
	free(p);
deuce's avatar
deuce committed
630
631
632
633
634
635
	if(oldlist != list) {
		if(glob==scope)
			pd->exit_func=list;
		else
			JS_SetPrivate(cx,scope,list);
	}
636
637
638
	return(JS_TRUE);
}

639
static JSBool
640
js_get_parent(JSContext *cx, uintN argc, jsval *arglist)
641
{
642
	jsval *argv=JS_ARGV(cx, arglist);
643
644
645
646
647
648
	JSObject* child=NULL;
	JSObject* parent;

	if(JS_ValueToObject(cx, argv[0], &child)
		&& child!=NULL
		&& (parent=JS_GetParent(cx,child))!=NULL)
649
		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(parent));
650
651
652
653

	return(JS_TRUE);
}

654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
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);
}

683
static jsSyncMethodSpec js_functions[] = {
rswindell's avatar
rswindell committed
684
	{"eval",            js_eval,            0,	JSTYPE_UNDEF,	JSDOCSTR("script")
685
	,JSDOCSTR("evaluate a JavaScript string in its own (secure) context, returning the result")
686
	,311
687
	},		
rswindell's avatar
rswindell committed
688
	{"gc",				js_gc,				0,	JSTYPE_VOID,	JSDOCSTR("forced=<tt>true</tt>")
689
690
691
	,JSDOCSTR("perform a garbage collection operation (freeing memory for unused allocated objects), "
		"if <i>forced</i> is <i>true</i> (the default) a garbage collection is always performed, "
		"otherwise it is only performed if deemed appropriate by the JavaScript engine")
692
	,311
693
	},
rswindell's avatar
rswindell committed
694
695
	{"on_exit",			js_on_exit,			1,	JSTYPE_VOID,	JSDOCSTR("to_eval")
	,JSDOCSTR("add a string to evaluate/execute (LIFO stack) upon script's termination")
696
	,313
697
	},
rswindell's avatar
rswindell committed
698
	{"report_error",	js_report_error,	1,	JSTYPE_VOID,	JSDOCSTR("error [,fatal=<tt>false</tt>]")
699
700
	,JSDOCSTR("report an error using the standard JavaScript error reporting mechanism "
	"(including script filename and line number), "
701
702
	"if <i>fatal</i> is <i>true</i>, immediately terminates script")
	,313
703
	},
rswindell's avatar
rswindell committed
704
	{"get_parent",		js_get_parent,		1,	JSTYPE_OBJECT,	JSDOCSTR("object")
705
	,JSDOCSTR("return the parent of the specified object")
rswindell's avatar
rswindell committed
706
	,314
707
	},
708
709
710
711
712
713
714
715
	{"get_size",		js_getsize,			1,	JSTYPE_NUMBER,	JSDOCSTR("[object]")
	,JSDOCSTR("return the size in bytes the object uses in memory (forces GC) ")
	,316
	},
	{"flatten_string",	js_flatten,			1,	JSTYPE_VOID,	JSDOCSTR("[string]")
	,JSDOCSTR("flattens a string, optimizing allocated memory used for concatenated strings")
	,316
	},
deuce's avatar
deuce committed
716
	{"exec",	js_execfile,			1,	JSTYPE_NUMBER,	JSDOCSTR("filename [, startup_dir], obj [,...]")
717
	,JSDOCSTR("Executes a script optionally with a custom scope.  The main difference between this "
deuce's avatar
deuce committed
718
	"and load() is that scripts called this way can call exit() without terminating the caller.  If it does, any "
719
720
721
	"on_exit() handlers will be evaluated in scripts scope when the script exists. "
	"NOTE: To get a child of the current scope, you need to create an object in the current scope "
	"an anonymous object can be created using 'new function(){}'.")
deuce's avatar
deuce committed
722
723
	,316
	},
724
725
726
	{0}
};

727
static JSBool js_internal_resolve(JSContext *cx, JSObject *obj, jsid id)
deuce's avatar
deuce committed
728
729
{
	char*			name=NULL;
730
	JSBool			ret;
deuce's avatar
deuce committed
731

deuce's avatar
deuce committed
732
733
734
735
	if(id != JSID_VOID && id != JSID_EMPTY) {
		jsval idval;
		
		JS_IdToValue(cx, id, &idval);
736
737
		if(JSVAL_IS_STRING(idval)) {
			JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
738
			HANDLE_PENDING(cx, name);
739
		}
deuce's avatar
deuce committed
740
	}
deuce's avatar
deuce committed
741

742
743
744
745
	ret=js_SyncResolve(cx, obj, name, js_properties, js_functions, NULL, 0);
	if(name)
		free(name);
	return(ret);
deuce's avatar
deuce committed
746
747
748
749
}

static JSBool js_internal_enumerate(JSContext *cx, JSObject *obj)
{
deuce's avatar
deuce committed
750
	return(js_internal_resolve(cx, obj, JSID_VOID));
deuce's avatar
deuce committed
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
}

static JSClass js_internal_class = {
     "JsInternal"				/* name			*/
    ,JSCLASS_HAS_PRIVATE	/* flags		*/
	,JS_PropertyStub		/* addProperty	*/
	,JS_PropertyStub		/* delProperty	*/
	,js_get					/* getProperty	*/
	,js_set					/* setProperty	*/
	,js_internal_enumerate	/* enumerate	*/
	,js_internal_resolve	/* resolve		*/
	,JS_ConvertStub			/* convert		*/
	,JS_FinalizeStub		/* finalize		*/
};

766
void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_callback_t* cb)
767
768
769
{
	char*	p;
	jsval	rval;
770
	JSObject* script;
771
	BOOL	auto_terminate=cb->auto_terminate;
772
773
774
775
776
777
778
779
780
781
	JSObject	*glob=JS_GetGlobalObject(cx);
	global_private_t *pt;
	str_list_t	list;

	if(glob==obj) {
		pt=(global_private_t *)JS_GetPrivate(cx,JS_GetGlobalObject(cx));		
		list=pt->exit_func;
	}
	else
		list=JS_GetPrivate(cx,obj);
782

783
	cb->auto_terminate=FALSE;
784

785
	while((p=strListPop(&list))!=NULL) {
786
787
788
		if((script=JS_CompileScript(cx, obj, p, strlen(p), NULL, 0))!=NULL) {
			JS_ExecuteScript(cx, obj, script, &rval);
		}
deuce's avatar
deuce committed
789
		free(p);
790
791
	}

792
	strListFree(&list);
793
794
795
796
	if(glob != obj)
		JS_SetPrivate(cx,obj,NULL);
	else
		pt->exit_func=NULL;
797

798
	if(auto_terminate)
799
		cb->auto_terminate = TRUE;
800
801
}

802
JSObject* DLLCALL js_CreateInternalJsObject(JSContext* cx, JSObject* parent, js_callback_t* cb, js_startup_t* startup)
803
804
805
806
807
808
809
{
	JSObject*	obj;

	if((obj = JS_DefineObject(cx, parent, "js", &js_internal_class, NULL
		,JSPROP_ENUMERATE|JSPROP_READONLY))==NULL)
		return(NULL);

810
	if(!JS_SetPrivate(cx, obj, cb))	/* Store a pointer to js_callback_t */
811
812
		return(NULL);

813
814
815
	if(startup!=NULL) {
		JSObject*	load_path_list;
		jsval		val;
816
		str_list_t	load_path;
817
818
819
820
821
822
823

		if((load_path_list=JS_NewArrayObject(cx, 0, NULL))==NULL) 
			return(NULL);
		val=OBJECT_TO_JSVAL(load_path_list);
		if(!JS_SetProperty(cx, obj, JAVASCRIPT_LOAD_PATH_LIST, &val)) 
			return(NULL);

824
		if((load_path=strListSplitCopy(NULL, startup->load_path, ",")) != NULL) {
825
826
827
			JSString*	js_str;
			unsigned	i;

828
829
830
			for(i=0; load_path[i]!=NULL; i++) {
				if((js_str=JS_NewStringCopyZ(cx, load_path[i]))==NULL)
					break;
831
832
				val=STRING_TO_JSVAL(js_str);
				if(!JS_SetElement(cx, load_path_list, i, &val))
833
					break;
834
			}
835
			strListFree(&load_path);
836
837
838
		}
	}

839
#ifdef BUILD_JSDOCS
840
	js_DescribeSyncObject(cx,obj,"JavaScript engine internal control object",311);
841
842
843
844
845
	js_CreateArrayOfStrings(cx, obj, "_property_desc_list", prop_desc, JSPROP_READONLY);
#endif

	return(obj);
}
846

847
848
849
850
851
852
853
854
855
856
#if defined(_MSC_VER)
void msvc_invalid_parameter_handler(const wchar_t* expression,
   const wchar_t* function, 
   const wchar_t* file, 
   unsigned int line, 
   uintptr_t pReserved)
{
}
#endif

857
void DLLCALL js_PrepareToExecute(JSContext *cx, JSObject *obj, const char *filename, const char* startup_dir, JSObject *scope)
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
{
	JSString*	str;
	jsval		val;

	if(JS_GetProperty(cx, obj, "js", &val) && JSVAL_IS_OBJECT(val)) {
		JSObject* js = JSVAL_TO_OBJECT(val);
		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);
			if((str=JS_NewStringCopyZ(cx, getfname(filename))) != NULL)
				JS_DefineProperty(cx, js, "exec_file", STRING_TO_JSVAL(str)
					,NULL,NULL,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);
		}
882
883
884
885
886
		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);
887
888
		JS_DefineProperty(cx, js, "scope", OBJECT_TO_JSVAL(scope)
			,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
889
	}
890
891
	JS_DefineProperty(cx, scope, "exit_code", JSVAL_NULL
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_PERMANENT);
892
893
894
#if defined(_MSC_VER)
	_set_invalid_parameter_handler(msvc_invalid_parameter_handler);
#endif
895
}