js_internal.c 15.5 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 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

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

59
static JSBool js_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
60
{
61
	jsval idval;
62
    jsint			tiny;
63
	js_callback_t*	cb;
64

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

68
69
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
70
71

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

	return(JS_TRUE);
}

121
static JSBool js_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
122
{
123
	jsval idval;
124
    jsint			tiny;
125
	js_callback_t*	cb;
126

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

130
131
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
132
133

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

165
	return(JS_TRUE);
166
167
}

168
#define PROP_FLAGS	JSPROP_ENUMERATE|JSPROP_READONLY
169

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

173
	{	"version",			PROP_VERSION,		PROP_FLAGS,			311 },
174
	{	"auto_terminate",	PROP_AUTO_TERMINATE,JSPROP_ENUMERATE,	311 },
175
	{	"terminated",		PROP_TERMINATED,	JSPROP_ENUMERATE,	311 },
176
177
178
179
	{	"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 },
180
181
	{	"yield_interval",	PROP_YIELD_INTERVAL,JSPROP_ENUMERATE,	311 },
	{	"gc_interval",		PROP_GC_INTERVAL,	JSPROP_ENUMERATE,	311 },
182
	{	"gc_attempts",		PROP_GC_ATTEMPTS,	PROP_FLAGS,			311 },
183
#ifdef jscntxt_h___
184
185
186
	{	"gc_counter",		PROP_GC_COUNTER,	PROP_FLAGS,			311 },
	{	"gc_last_bytes",	PROP_GC_LASTBYTES,	PROP_FLAGS,			311 },
	{	"bytes",			PROP_BYTES,			PROP_FLAGS,			311 },
187
	{	"max_bytes",		PROP_MAXBYTES,		JSPROP_ENUMERATE,	311 },
188
#endif
rswindell's avatar
rswindell committed
189
	{	"global",			PROP_GLOBAL,		PROP_FLAGS,			314 },
190
191
192
	{0}
};

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

214
JSBool DLLCALL
215
js_CommonOperationCallback(JSContext *cx, js_callback_t* cb)
216
{
217
	cb->counter++;
218

219
	/* Terminated? */
220
221
	if(cb->auto_terminate &&
		(cb->terminated!=NULL && *cb->terminated)) {
222
		JS_ReportWarning(cx,"Terminated");
223
		cb->counter=0;
224
225
226
		return(JS_FALSE);
	}

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

234
	/* Give up timeslices every once in a while */
235
	if(cb->yield_interval && (cb->counter%cb->yield_interval)==0) {
deuce's avatar
deuce committed
236
237
		jsrefcount	rc;

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

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

246
	/* Periodic Garbage Collection */
247
248
	if(cb->gc_interval && (cb->counter%cb->gc_interval)==0)
		JS_MaybeGC(cx), cb->gc_attempts++;
249
250
251
252

    return(JS_TRUE);
}

253
254
255
256
257
258
259
260
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
};

261
262
/* Execute a string in its own context (away from Synchronet objects) */
static JSBool
263
js_eval(JSContext *parent_cx, uintN argc, jsval *arglist)
264
{
265
	jsval *argv=JS_ARGV(parent_cx, arglist);
266
	char*			buf;
267
268
	size_t			buflen;
	JSString*		str;
269
    JSObject*		script;
270
271
	JSContext*		cx;
	JSObject*		obj;
272
	JSErrorReporter	reporter;
273

274
275
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

276
277
278
	if(argc<1)
		return(JS_TRUE);

279
	if((str=JS_ValueToString(parent_cx, argv[0]))==NULL)
280
		return(JS_FALSE);
281
	JSSTRING_TO_STRING(parent_cx, str, buf, NULL);
deuce's avatar
deuce committed
282
	if(buf==NULL)
283
284
		return(JS_FALSE);
	buflen=JS_GetStringLength(str);
285

286
	if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL)
287
288
		return(JS_FALSE);

289
	/* Use the error reporter from the parent context */
290
291
	reporter=JS_SetErrorReporter(parent_cx,NULL);
	JS_SetErrorReporter(parent_cx,reporter);
292
	JS_SetErrorReporter(cx,reporter);
293

294
	/* Use the branch callback from the parent context */
295
	JS_SetContextPrivate(cx, JS_GetContextPrivate(parent_cx));
deuce's avatar
deuce committed
296
	JS_SetOperationCallback(cx, JS_GetOperationCallback(parent_cx));
297

298
	if((obj=JS_NewCompartmentAndGlobalObject(cx, &eval_class, NULL))==NULL
299
300
301
302
		|| !JS_InitStandardClasses(cx,obj)) {
		JS_DestroyContext(cx);
		return(JS_FALSE);
	}
303

304
	if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
deuce's avatar
deuce committed
305
306
307
308
		jsval	rval;

		JS_ExecuteScript(cx, obj, script, &rval);
		JS_SET_RVAL(cx, arglist, rval);
309
310
311
312
313
314
315
	}

	JS_DestroyContext(cx);

    return(JS_TRUE);
}

316
static JSBool
317
js_gc(JSContext *cx, uintN argc, jsval *arglist)
318
{
319
320
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
321
	JSBool			forced=JS_TRUE;
322
	js_callback_t*	cb;
323

deuce's avatar
deuce committed
324
325
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

326
	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
327
		return(JS_FALSE);
328
329
330
331
332
333
334
335
336

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

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

337
	cb->gc_attempts++;
338

339
340
341
	return(JS_TRUE);
}

342
static JSBool
343
js_report_error(JSContext *cx, uintN argc, jsval *arglist)
344
{
345
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
346
347
	char	*p;

348
	JSVALUE_TO_STRING(cx, argv[0], p, NULL);
deuce's avatar
deuce committed
349
	JS_ReportError(cx,"%s",p);
350

351
352
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

353
354
355
356
357
	if(argc>1 && argv[1]==JSVAL_TRUE)
		return(JS_FALSE);	/* fatal */

	return(JS_TRUE);
}
358

359
static JSBool
360
js_on_exit(JSContext *cx, uintN argc, jsval *arglist)
361
{
362
363
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
364
	js_callback_t*	cb;
deuce's avatar
deuce committed
365
	char		*p;
366

deuce's avatar
deuce committed
367
368
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

372
373
	if(cb->exit_func==NULL)
		cb->exit_func=strListInit();
374

375
	JSVALUE_TO_STRING(cx, argv[0], p, NULL);
376
	strListPush(&cb->exit_func,p);
377
378
379
380

	return(JS_TRUE);
}

381
static JSBool
382
js_get_parent(JSContext *cx, uintN argc, jsval *arglist)
383
{
384
	jsval *argv=JS_ARGV(cx, arglist);
385
386
387
388
389
390
	JSObject* child=NULL;
	JSObject* parent;

	if(JS_ValueToObject(cx, argv[0], &child)
		&& child!=NULL
		&& (parent=JS_GetParent(cx,child))!=NULL)
391
		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(parent));
392
393
394
395

	return(JS_TRUE);
}

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

424
static JSBool js_internal_resolve(JSContext *cx, JSObject *obj, jsid id)
deuce's avatar
deuce committed
425
426
427
{
	char*			name=NULL;

deuce's avatar
deuce committed
428
429
430
431
	if(id != JSID_VOID && id != JSID_EMPTY) {
		jsval idval;
		
		JS_IdToValue(cx, id, &idval);
432
433
		if(JSVAL_IS_STRING(idval))
			JSSTRING_TO_STRING(cx, JSVAL_TO_STRING(idval), name, NULL);
deuce's avatar
deuce committed
434
	}
deuce's avatar
deuce committed
435
436
437
438
439
440

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

static JSBool js_internal_enumerate(JSContext *cx, JSObject *obj)
{
deuce's avatar
deuce committed
441
	return(js_internal_resolve(cx, obj, JSID_VOID));
deuce's avatar
deuce committed
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
}

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

457
void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_callback_t* cb)
458
459
460
{
	char*	p;
	jsval	rval;
461
	JSObject* script;
462
	BOOL	auto_terminate=cb->auto_terminate;
463

464
	cb->auto_terminate=FALSE;
465

466
	while((p=strListPop(&cb->exit_func))!=NULL) {
467
468
469
		if((script=JS_CompileScript(cx, obj, p, strlen(p), NULL, 0))!=NULL) {
			JS_ExecuteScript(cx, obj, script, &rval);
		}
deuce's avatar
deuce committed
470
		free(p);
471
472
	}

473
	strListFree(&cb->exit_func);
474

475
	if(auto_terminate)
476
		cb->auto_terminate = TRUE;
477
478
}

479
JSObject* DLLCALL js_CreateInternalJsObject(JSContext* cx, JSObject* parent, js_callback_t* cb, js_startup_t* startup)
480
481
482
483
484
485
486
{
	JSObject*	obj;

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

487
	if(!JS_SetPrivate(cx, obj, cb))	/* Store a pointer to js_callback_t */
488
489
		return(NULL);

490
491
492
	if(startup!=NULL) {
		JSObject*	load_path_list;
		jsval		val;
493
		str_list_t	load_path;
494
495
496
497
498
499
500

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

501
		if((load_path=strListSplitCopy(NULL, startup->load_path, ",")) != NULL) {
502
503
504
			JSString*	js_str;
			unsigned	i;

505
506
507
			for(i=0; load_path[i]!=NULL; i++) {
				if((js_str=JS_NewStringCopyZ(cx, load_path[i]))==NULL)
					break;
508
509
				val=STRING_TO_JSVAL(js_str);
				if(!JS_SetElement(cx, load_path_list, i, &val))
510
					break;
511
			}
512
			strListFree(&load_path);
513
514
515
		}
	}

516
#ifdef BUILD_JSDOCS
517
	js_DescribeSyncObject(cx,obj,"JavaScript execution and garbage collection control object",311);
518
519
520
521
522
	js_CreateArrayOfStrings(cx, obj, "_property_desc_list", prop_desc, JSPROP_READONLY);
#endif

	return(obj);
}
523

524
void DLLCALL js_PrepareToExecute(JSContext *cx, JSObject *obj, const char *filename, const char* startup_dir)
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
{
	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);
		}
549
550
551
552
553
		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);
554
555
	}
}