js_internal.c 16 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
/* js_internal.c */

/* Synchronet "js" object, for internal JavaScript branch and GC control */

/* $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 2011 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
#ifdef _DEBUG
	#include <jscntxt.h>	/* Needed for Context-private data structure */
#endif
44
45

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

63
static JSBool js_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
64
{
65
	jsval idval;
66
67
68
69
70
71
    jsint			tiny;
	js_branch_t*	branch;

	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		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
			if(branch->terminated==NULL)
				*vp=JSVAL_FALSE;
			else
83
84
				*vp=BOOLEAN_TO_JSVAL(*branch->terminated);
			break;
85
86
87
		case PROP_AUTO_TERMINATE:
			*vp=BOOLEAN_TO_JSVAL(branch->auto_terminate);
			break;
88
		case PROP_BRANCH_COUNTER:
89
			*vp=DOUBLE_TO_JSVAL((double)branch->counter);
90
91
			break;
		case PROP_BRANCH_LIMIT:
92
			*cp=DOUBLE_TO_JSVAL(branch->limit);
93
94
			break;
		case PROP_YIELD_INTERVAL:
95
			*vp=DOUBLE_TO_JSVAL((double)branch->yield_interval);
96
97
			break;
		case PROP_GC_INTERVAL:
98
			*vp=DOUBLE_TO_JSVAL((double)branch->gc_interval);
99
			break;
100
		case PROP_GC_ATTEMPTS:
101
			*vp=DOUBLE_TO_JSVAL((double)branch->gc_attempts);
102
			break;
103
#ifdef jscntxt_h___
104
		case PROP_GC_COUNTER:
105
			*vp=UINT_TO_JSVAL(cx->runtime->gcNumber);
106
107
			break;
		case PROP_GC_LASTBYTES:
108
			*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcLastBytes);
109
			break;
110
		case PROP_BYTES:
111
			*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcBytes);
112
			break;
113
		case PROP_MAXBYTES:
114
			*vp=DOUBLE_TO_JSVAL((double)cx->runtime->gcMaxBytes);
115
			break;
116
#endif
117
		case PROP_GLOBAL:
118
			*vp = OBJECT_TO_JSVAL(JS_GetGlobalObject(cx));	
119
			break;
120
121
122
123
124
	}

	return(JS_TRUE);
}

125
static JSBool js_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
126
{
127
	jsval idval;
128
129
130
131
132
133
    jsint			tiny;
	js_branch_t*	branch;

	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);

134
135
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
136
137

	switch(tiny) {
138
139
		case PROP_TERMINATED:
			if(branch->terminated!=NULL)
140
				JS_ValueToBoolean(cx, *vp, (int *)branch->terminated);
141
			break;
142
143
144
		case PROP_AUTO_TERMINATE:
			JS_ValueToBoolean(cx,*vp,&branch->auto_terminate);
			break;
145
146
147
148
149
150
151
		case PROP_BRANCH_COUNTER:
			JS_ValueToInt32(cx, *vp, (int32*)&branch->counter);
			break;
		case PROP_BRANCH_LIMIT:
			JS_ValueToInt32(cx, *vp, (int32*)&branch->limit);
			break;
		case PROP_GC_INTERVAL:
152
			JS_ValueToInt32(cx, *vp, (int32*)&branch->gc_interval);
153
154
			break;
		case PROP_YIELD_INTERVAL:
155
			JS_ValueToInt32(cx, *vp, (int32*)&branch->yield_interval);
156
			break;
157
#ifdef jscntxt_h___
158
159
160
		case PROP_MAXBYTES:
			JS_ValueToInt32(cx, *vp, (int32*)&cx->runtime->gcMaxBytes);
			break;
161
#endif
162
163
	}

164
	return(JS_TRUE);
165
166
}

167
#define PROP_FLAGS	JSPROP_ENUMERATE|JSPROP_READONLY
168

169
170
171
static jsSyncPropertySpec js_properties[] = {
/*		 name,				tinyid,						flags,		ver	*/

172
	{	"version",			PROP_VERSION,		PROP_FLAGS,			311 },
173
	{	"auto_terminate",	PROP_AUTO_TERMINATE,JSPROP_ENUMERATE,	311 },
174
175
176
177
178
	{	"terminated",		PROP_TERMINATED,	JSPROP_ENUMERATE,	311 },
	{	"branch_counter",	PROP_BRANCH_COUNTER,JSPROP_ENUMERATE,	311 },
	{	"branch_limit",		PROP_BRANCH_LIMIT,	JSPROP_ENUMERATE,	311 },
	{	"yield_interval",	PROP_YIELD_INTERVAL,JSPROP_ENUMERATE,	311 },
	{	"gc_interval",		PROP_GC_INTERVAL,	JSPROP_ENUMERATE,	311 },
179
	{	"gc_attempts",		PROP_GC_ATTEMPTS,	PROP_FLAGS,			311 },
180
#ifdef jscntxt_h___
181
182
183
	{	"gc_counter",		PROP_GC_COUNTER,	PROP_FLAGS,			311 },
	{	"gc_last_bytes",	PROP_GC_LASTBYTES,	PROP_FLAGS,			311 },
	{	"bytes",			PROP_BYTES,			PROP_FLAGS,			311 },
184
	{	"max_bytes",		PROP_MAXBYTES,		JSPROP_ENUMERATE,	311 },
185
#endif
rswindell's avatar
rswindell committed
186
	{	"global",			PROP_GLOBAL,		PROP_FLAGS,			314 },
187
188
189
	{0}
};

190
#ifdef BUILD_JSDOCS
191
static char* prop_desc[] = {
192
193
	 "JavaScript engine version information (AKA system.js_version)"
	,"set to <i>false</i> to disable the automatic termination of the script upon external request"
194
	,"termination has been requested (stop execution as soon as possible)"
195
	,"number of branch operations performed in this runtime"
196
197
	,"maximum number of branches, used for infinite-loop detection (0=disabled)"
	,"interval of periodic time-slice yields (lower number=higher frequency, 0=disabled)"
198
	,"interval of periodic garbage collection attempts (lower number=higher frequency, 0=disabled)"
199
	,"number of garbage collections attempted in this runtime - <small>READ ONLY</small>"
200
#ifdef jscntxt_h___
201
202
203
	,"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>"
204
	,"maximum number of bytes available for heap"
205
#endif
206
	,"global (top level) object - <small>READ ONLY</small>"
207
208
209
210
	,NULL
};
#endif

211
JSBool DLLCALL
212
js_CommonBranchCallback(JSContext *cx, js_branch_t* branch)
213
214
215
{
	branch->counter++;

216
217
218
	/* Terminated? */
	if(branch->auto_terminate &&
		(branch->terminated!=NULL && *branch->terminated)) {
219
		JS_ReportWarning(cx,"Terminated");
220
221
222
223
		branch->counter=0;
		return(JS_FALSE);
	}

224
225
226
227
228
229
	/* Infinite loop? */
	if(branch->limit && branch->counter > branch->limit) {
		JS_ReportError(cx,"Infinite loop (%lu branches) detected",branch->counter);
		branch->counter=0;
		return(JS_FALSE);
	}
230

231
#ifndef USE_JS_OPERATION_CALLBACK
232
	/* Give up timeslices every once in a while */
deuce's avatar
deuce committed
233
234
235
	if(branch->yield_interval && (branch->counter%branch->yield_interval)==0) {
		jsrefcount	rc;

236
		rc=JS_SUSPENDREQUEST(cx);
237
		YIELD();
238
		JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
239
	}
240

241
	/* Periodic Garbage Collection */
242
243
	if(branch->gc_interval && (branch->counter%branch->gc_interval)==0)
		JS_MaybeGC(cx), branch->gc_attempts++;
244
#endif
245
246
247
248

    return(JS_TRUE);
}

249
250
/* Execute a string in its own context (away from Synchronet objects) */
static JSBool
251
js_eval(JSContext *parent_cx, uintN argc, jsval *arglist)
252
{
253
254
	JSObject *parent_obj=JS_THIS_OBJECT(parent_cx, arglist);
	jsval *argv=JS_ARGV(parent_cx, arglist);
255
	char*			buf;
256
257
	size_t			buflen;
	JSString*		str;
258
    JSObject*		script;
259
260
	JSContext*		cx;
	JSObject*		obj;
261
	JSErrorReporter	reporter;
262
#ifndef EVAL_BRANCH_CALLBACK
263
#ifndef USE_JS_OPERATION_CALLBACK
264
	JSBranchCallback callback;
265
#endif
266
#endif
267

268
269
270
	if(argc<1)
		return(JS_TRUE);

271
	if((str=JS_ValueToString(parent_cx, argv[0]))==NULL)
272
		return(JS_FALSE);
273
274
275
	if((buf=JS_GetStringBytes(str))==NULL)
		return(JS_FALSE);
	buflen=JS_GetStringLength(str);
276

277
	if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL)
278
279
		return(JS_FALSE);

280
	/* Use the error reporter from the parent context */
281
282
	reporter=JS_SetErrorReporter(parent_cx,NULL);
	JS_SetErrorReporter(parent_cx,reporter);
283
	JS_SetErrorReporter(cx,reporter);
284

285
#ifdef EVAL_BRANCH_CALLBACK
286
	JS_SetContextPrivate(cx, JS_GetPrivate(parent_cx, parent_obj));
287
#if JS_VERSION>180
288
289
	JS_SetOperationCallback(cx, js_OperationCallback);
#else
290
	JS_SetBranchCallback(cx, js_BranchCallback);
291
#endif
292
293
#else	/* Use the branch callback from the parent context */
	JS_SetContextPrivate(cx, JS_GetContextPrivate(parent_cx));
294
#if JS_VERSION>180
deuce's avatar
deuce committed
295
	JS_SetOperationCallback(cx, JS_GetOperationCallback(parent_cx));
296
#else
297
298
299
	callback=JS_SetBranchCallback(parent_cx,NULL);
	JS_SetBranchCallback(parent_cx, callback);
	JS_SetBranchCallback(cx, callback);
300
#endif
301
#endif
302

303
304
305
306
307
	if((obj=JS_NewObject(cx, NULL, NULL, NULL))==NULL
		|| !JS_InitStandardClasses(cx,obj)) {
		JS_DestroyContext(cx);
		return(JS_FALSE);
	}
308

309
	if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
310
311
312
313
314
315
316
317
318
		JS_ExecuteScript(cx, obj, script, rval);
		JS_DestroyScript(cx, script);
	}

	JS_DestroyContext(cx);

    return(JS_TRUE);
}

319
static JSBool
320
js_gc(JSContext *cx, uintN argc, jsval *arglist)
321
{
322
323
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
324
325
326
327
328
	JSBool			forced=JS_TRUE;
	js_branch_t*	branch;

	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);
329
330
331
332
333
334
335
336
337

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

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

338
339
	branch->gc_attempts++;

340
341
342
	return(JS_TRUE);
}

343
static JSBool
344
js_report_error(JSContext *cx, uintN argc, jsval *arglist)
345
{
346
347
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
348
349
350
351
352
353
354
	JS_ReportError(cx,"%s",JS_GetStringBytes(JS_ValueToString(cx, argv[0])));

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

	return(JS_TRUE);
}
355

356
static JSBool
357
js_on_exit(JSContext *cx, uintN argc, jsval *arglist)
358
{
359
360
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
361
362
363
364
365
366
367
368
369
370
371
372
373
	js_branch_t*	branch;

	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);

	if(branch->exit_func==NULL)
		branch->exit_func=strListInit();

	strListPush(&branch->exit_func,JS_GetStringBytes(JS_ValueToString(cx, argv[0])));

	return(JS_TRUE);
}

374
static JSBool
375
js_get_parent(JSContext *cx, uintN argc, jsval *arglist)
376
{
377
378
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
379
380
381
382
383
384
	JSObject* child=NULL;
	JSObject* parent;

	if(JS_ValueToObject(cx, argv[0], &child)
		&& child!=NULL
		&& (parent=JS_GetParent(cx,child))!=NULL)
385
		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(parent));
386
387
388
389

	return(JS_TRUE);
}

390
static jsSyncMethodSpec js_functions[] = {
rswindell's avatar
rswindell committed
391
	{"eval",            js_eval,            0,	JSTYPE_UNDEF,	JSDOCSTR("script")
392
	,JSDOCSTR("evaluate a JavaScript string in its own (secure) context, returning the result")
393
	,311
394
	},		
rswindell's avatar
rswindell committed
395
	{"gc",				js_gc,				0,	JSTYPE_VOID,	JSDOCSTR("forced=<tt>true</tt>")
396
397
398
	,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")
399
	,311
400
	},
rswindell's avatar
rswindell committed
401
402
	{"on_exit",			js_on_exit,			1,	JSTYPE_VOID,	JSDOCSTR("to_eval")
	,JSDOCSTR("add a string to evaluate/execute (LIFO stack) upon script's termination")
403
	,313
404
	},
rswindell's avatar
rswindell committed
405
	{"report_error",	js_report_error,	1,	JSTYPE_VOID,	JSDOCSTR("error [,fatal=<tt>false</tt>]")
406
407
	,JSDOCSTR("report an error using the standard JavaScript error reporting mechanism "
	"(including script filename and line number), "
408
409
	"if <i>fatal</i> is <i>true</i>, immediately terminates script")
	,313
410
	},
rswindell's avatar
rswindell committed
411
	{"get_parent",		js_get_parent,		1,	JSTYPE_OBJECT,	JSDOCSTR("object")
412
	,JSDOCSTR("return the parent of the specified object")
rswindell's avatar
rswindell committed
413
	,314
414
	},
415
416
417
	{0}
};

418
static JSBool js_internal_resolve(JSContext *cx, JSObject *obj, jsid id)
deuce's avatar
deuce committed
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
{
	char*			name=NULL;

	if(id != JSVAL_NULL)
		name=JS_GetStringBytes(JSVAL_TO_STRING(id));

	return(js_SyncResolve(cx, obj, name, js_properties, js_functions, NULL, 0));
}

static JSBool js_internal_enumerate(JSContext *cx, JSObject *obj)
{
	return(js_internal_resolve(cx, obj, JSVAL_NULL));
}

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		*/
};

446
#if JS_VERSION >= 185
447
char* DLLCALL JS_GetStringBytes_dumbass(JSContext *cx, JSString *str)
448
449
{
	size_t			len;
450
	size_t			pos;
451
452
453
454
455
	const jschar	*val;
	char			*ret;

	if(!(val=JS_GetStringCharsAndLength(cx, str, &len)))
		return NULL;
456
	if(!(ret=malloc(len+1)))
457
		return NULL;
458
459
	for(pos=0; pos<len; pos++)
		ret[pos]=val[pos];
460
461
462
463
464
	ret[len]=0;
	return ret;
}
#endif

465
466
467
468
void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_branch_t* branch)
{
	char*	p;
	jsval	rval;
469
	JSObject* script;
470
	BOOL	auto_terminate=branch->auto_terminate;
471

rswindell's avatar
rswindell committed
472
	branch->auto_terminate=FALSE;
473

474
475
476
477
478
	while((p=strListPop(&branch->exit_func))!=NULL) {
		if((script=JS_CompileScript(cx, obj, p, strlen(p), NULL, 0))!=NULL) {
			JS_ExecuteScript(cx, obj, script, &rval);
			JS_DestroyScript(cx, script);
		}
deuce's avatar
deuce committed
479
		free(p);
480
481
482
	}

	strListFree(&branch->exit_func);
483

484
485
	if(auto_terminate)
		branch->auto_terminate = TRUE;
486
487
}

488
JSObject* DLLCALL js_CreateInternalJsObject(JSContext* cx, JSObject* parent, js_branch_t* branch, js_startup_t* startup)
489
490
491
492
493
494
495
496
497
498
{
	JSObject*	obj;

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

	if(!JS_SetPrivate(cx, obj, branch))	/* Store a pointer to js_branch_t */
		return(NULL);

499
500
501
	if(startup!=NULL) {
		JSObject*	load_path_list;
		jsval		val;
502
		str_list_t	load_path;
503
504
505
506
507
508
509

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

510
		if((load_path=strListSplitCopy(NULL, startup->load_path, ",")) != NULL) {
511
512
513
			JSString*	js_str;
			unsigned	i;

514
515
516
			for(i=0; load_path[i]!=NULL; i++) {
				if((js_str=JS_NewStringCopyZ(cx, load_path[i]))==NULL)
					break;
517
518
				val=STRING_TO_JSVAL(js_str);
				if(!JS_SetElement(cx, load_path_list, i, &val))
519
					break;
520
			}
521
			strListFree(&load_path);
522
523
524
		}
	}

525
#ifdef BUILD_JSDOCS
526
	js_DescribeSyncObject(cx,obj,"JavaScript execution and garbage collection control object",311);
527
528
529
530
531
	js_CreateArrayOfStrings(cx, obj, "_property_desc_list", prop_desc, JSPROP_READONLY);
#endif

	return(obj);
}
532

533
void DLLCALL js_PrepareToExecute(JSContext *cx, JSObject *obj, const char *filename, const char* startup_dir)
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
{
	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);
		}
558
559
560
561
562
		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);
563
564
	}
}