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

enum {
42
43
	 PROP_VERSION
	,PROP_TERMINATED
44
	,PROP_AUTO_TERMINATE
45
	,PROP_BRANCH_COUNTER
46
47
48
	,PROP_BRANCH_LIMIT
	,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
63
64
65
66
67
    jsint			tiny;
	js_branch_t*	branch;

	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		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
77
78
			if(branch->terminated==NULL)
				*vp=JSVAL_FALSE;
			else
79
80
				*vp=BOOLEAN_TO_JSVAL(*branch->terminated);
			break;
81
82
83
		case PROP_AUTO_TERMINATE:
			*vp=BOOLEAN_TO_JSVAL(branch->auto_terminate);
			break;
84
		case PROP_BRANCH_COUNTER:
85
			*vp=DOUBLE_TO_JSVAL((double)branch->counter);
86
87
			break;
		case PROP_BRANCH_LIMIT:
deuce's avatar
deuce committed
88
			*vp=DOUBLE_TO_JSVAL(branch->limit);
89
90
			break;
		case PROP_YIELD_INTERVAL:
91
			*vp=DOUBLE_TO_JSVAL((double)branch->yield_interval);
92
93
			break;
		case PROP_GC_INTERVAL:
94
			*vp=DOUBLE_TO_JSVAL((double)branch->gc_interval);
95
			break;
96
		case PROP_GC_ATTEMPTS:
97
			*vp=DOUBLE_TO_JSVAL((double)branch->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
125
126
127
128
129
    jsint			tiny;
	js_branch_t*	branch;

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

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

	switch(tiny) {
134
135
		case PROP_TERMINATED:
			if(branch->terminated!=NULL)
136
				JS_ValueToBoolean(cx, *vp, (int *)branch->terminated);
137
			break;
138
139
140
		case PROP_AUTO_TERMINATE:
			JS_ValueToBoolean(cx,*vp,&branch->auto_terminate);
			break;
141
142
143
144
145
146
147
		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:
148
			JS_ValueToInt32(cx, *vp, (int32*)&branch->gc_interval);
149
150
			break;
		case PROP_YIELD_INTERVAL:
151
			JS_ValueToInt32(cx, *vp, (int32*)&branch->yield_interval);
152
			break;
153
#ifdef jscntxt_h___
154
155
156
		case PROP_MAXBYTES:
			JS_ValueToInt32(cx, *vp, (int32*)&cx->runtime->gcMaxBytes);
			break;
157
#endif
158
159
	}

160
	return(JS_TRUE);
161
162
}

163
#define PROP_FLAGS	JSPROP_ENUMERATE|JSPROP_READONLY
164

165
166
167
static jsSyncPropertySpec js_properties[] = {
/*		 name,				tinyid,						flags,		ver	*/

168
	{	"version",			PROP_VERSION,		PROP_FLAGS,			311 },
169
	{	"auto_terminate",	PROP_AUTO_TERMINATE,JSPROP_ENUMERATE,	311 },
170
171
172
173
174
	{	"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 },
175
	{	"gc_attempts",		PROP_GC_ATTEMPTS,	PROP_FLAGS,			311 },
176
#ifdef jscntxt_h___
177
178
179
	{	"gc_counter",		PROP_GC_COUNTER,	PROP_FLAGS,			311 },
	{	"gc_last_bytes",	PROP_GC_LASTBYTES,	PROP_FLAGS,			311 },
	{	"bytes",			PROP_BYTES,			PROP_FLAGS,			311 },
180
	{	"max_bytes",		PROP_MAXBYTES,		JSPROP_ENUMERATE,	311 },
181
#endif
rswindell's avatar
rswindell committed
182
	{	"global",			PROP_GLOBAL,		PROP_FLAGS,			314 },
183
184
185
	{0}
};

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

207
JSBool DLLCALL
208
js_CommonBranchCallback(JSContext *cx, js_branch_t* branch)
209
210
211
{
	branch->counter++;

212
213
214
	/* Terminated? */
	if(branch->auto_terminate &&
		(branch->terminated!=NULL && *branch->terminated)) {
215
		JS_ReportWarning(cx,"Terminated");
216
217
218
219
		branch->counter=0;
		return(JS_FALSE);
	}

220
221
222
223
224
225
	/* 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);
	}
226

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

232
		rc=JS_SUSPENDREQUEST(cx);
233
		YIELD();
234
		JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
235
	}
236

237
	/* Periodic Garbage Collection */
238
239
	if(branch->gc_interval && (branch->counter%branch->gc_interval)==0)
		JS_MaybeGC(cx), branch->gc_attempts++;
240
#endif
241
242
243
244

    return(JS_TRUE);
}

245
246
247
248
249
250
251
252
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
};

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

267
268
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

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

272
	if((str=JS_ValueToString(parent_cx, argv[0]))==NULL)
273
		return(JS_FALSE);
274
	JSSTRING_TO_STRING(parent_cx, str, buf, NULL);
deuce's avatar
deuce committed
275
	if(buf==NULL)
276
277
		return(JS_FALSE);
	buflen=JS_GetStringLength(str);
278

279
	if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL)
280
281
		return(JS_FALSE);

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

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

305
	if((obj=JS_NewCompartmentAndGlobalObject(cx, &eval_class, NULL))==NULL
306
307
308
309
		|| !JS_InitStandardClasses(cx,obj)) {
		JS_DestroyContext(cx);
		return(JS_FALSE);
	}
310

311
	if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
deuce's avatar
deuce committed
312
313
314
315
		jsval	rval;

		JS_ExecuteScript(cx, obj, script, &rval);
		JS_SET_RVAL(cx, arglist, rval);
316
317
318
319
320
321
322
	}

	JS_DestroyContext(cx);

    return(JS_TRUE);
}

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

deuce's avatar
deuce committed
331
332
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

333
334
	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);
335
336
337
338
339
340
341
342
343

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

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

344
345
	branch->gc_attempts++;

346
347
348
	return(JS_TRUE);
}

349
static JSBool
350
js_report_error(JSContext *cx, uintN argc, jsval *arglist)
351
{
352
353
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
354
355
	char	*p;

356
	JSVALUE_TO_STRING(cx, argv[0], p, NULL);
deuce's avatar
deuce committed
357
	JS_ReportError(cx,"%s",p);
358

359
360
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

361
362
363
364
365
	if(argc>1 && argv[1]==JSVAL_TRUE)
		return(JS_FALSE);	/* fatal */

	return(JS_TRUE);
}
366

367
static JSBool
368
js_on_exit(JSContext *cx, uintN argc, jsval *arglist)
369
{
370
371
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
372
	js_branch_t*	branch;
deuce's avatar
deuce committed
373
	char		*p;
374

deuce's avatar
deuce committed
375
376
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

377
378
379
380
381
382
	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);

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

383
	JSVALUE_TO_STRING(cx, argv[0], p, NULL);
deuce's avatar
deuce committed
384
	strListPush(&branch->exit_func,p);
385
386
387
388

	return(JS_TRUE);
}

389
static JSBool
390
js_get_parent(JSContext *cx, uintN argc, jsval *arglist)
391
{
392
393
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
394
395
396
397
398
399
	JSObject* child=NULL;
	JSObject* parent;

	if(JS_ValueToObject(cx, argv[0], &child)
		&& child!=NULL
		&& (parent=JS_GetParent(cx,child))!=NULL)
400
		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(parent));
401
402
403
404

	return(JS_TRUE);
}

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

433
static JSBool js_internal_resolve(JSContext *cx, JSObject *obj, jsid id)
deuce's avatar
deuce committed
434
435
436
{
	char*			name=NULL;

deuce's avatar
deuce committed
437
438
439
440
	if(id != JSID_VOID && id != JSID_EMPTY) {
		jsval idval;
		
		JS_IdToValue(cx, id, &idval);
441
		JSSTRING_TO_STRING(cx, JSVAL_TO_STRING(idval), name, NULL);
deuce's avatar
deuce committed
442
	}
deuce's avatar
deuce committed
443
444
445
446
447
448

	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
449
	return(js_internal_resolve(cx, obj, JSID_VOID));
deuce's avatar
deuce committed
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
}

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

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
	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);
		}
deuce's avatar
deuce committed
478
		free(p);
479
480
481
	}

	strListFree(&branch->exit_func);
482

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

487
JSObject* DLLCALL js_CreateInternalJsObject(JSContext* cx, JSObject* parent, js_branch_t* branch, js_startup_t* startup)
488
489
490
491
492
493
494
495
496
497
{
	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);

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

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

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

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

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

	return(obj);
}
531

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