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
		case PROP_BRANCH_COUNTER:
142
143
			if(!JS_ValueToInt32(cx, *vp, (int32*)&branch->counter))
				return JS_FALSE;
144
145
			break;
		case PROP_BRANCH_LIMIT:
146
147
			if(!JS_ValueToInt32(cx, *vp, (int32*)&branch->limit))
				return JS_FALSE;
148
149
			break;
		case PROP_GC_INTERVAL:
150
151
			if(!JS_ValueToInt32(cx, *vp, (int32*)&branch->gc_interval))
				return JS_FALSE;
152
153
			break;
		case PROP_YIELD_INTERVAL:
154
155
			if(!JS_ValueToInt32(cx, *vp, (int32*)&branch->yield_interval))
				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
176
177
178
179
	{	"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 },
180
	{	"gc_attempts",		PROP_GC_ATTEMPTS,	PROP_FLAGS,			311 },
181
#ifdef jscntxt_h___
182
183
184
	{	"gc_counter",		PROP_GC_COUNTER,	PROP_FLAGS,			311 },
	{	"gc_last_bytes",	PROP_GC_LASTBYTES,	PROP_FLAGS,			311 },
	{	"bytes",			PROP_BYTES,			PROP_FLAGS,			311 },
185
	{	"max_bytes",		PROP_MAXBYTES,		JSPROP_ENUMERATE,	311 },
186
#endif
rswindell's avatar
rswindell committed
187
	{	"global",			PROP_GLOBAL,		PROP_FLAGS,			314 },
188
189
190
	{0}
};

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

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

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

225
226
227
228
229
230
	/* 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);
	}
231

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

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

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

    return(JS_TRUE);
}

250
251
252
253
254
255
256
257
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
};

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

271
272
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

273
274
275
	if(argc<1)
		return(JS_TRUE);

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

283
	if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL)
284
285
		return(JS_FALSE);

286
	/* Use the error reporter from the parent context */
287
288
	reporter=JS_SetErrorReporter(parent_cx,NULL);
	JS_SetErrorReporter(parent_cx,reporter);
289
	JS_SetErrorReporter(cx,reporter);
290

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

309
	if((obj=JS_NewCompartmentAndGlobalObject(cx, &eval_class, NULL))==NULL
310
311
312
313
		|| !JS_InitStandardClasses(cx,obj)) {
		JS_DestroyContext(cx);
		return(JS_FALSE);
	}
314

315
	if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
deuce's avatar
deuce committed
316
317
318
319
		jsval	rval;

		JS_ExecuteScript(cx, obj, script, &rval);
		JS_SET_RVAL(cx, arglist, rval);
320
321
322
323
324
325
326
	}

	JS_DestroyContext(cx);

    return(JS_TRUE);
}

327
static JSBool
328
js_gc(JSContext *cx, uintN argc, jsval *arglist)
329
{
330
331
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
332
333
334
	JSBool			forced=JS_TRUE;
	js_branch_t*	branch;

deuce's avatar
deuce committed
335
336
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

337
338
	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);
339
340
341
342
343
344
345
346
347

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

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

348
349
	branch->gc_attempts++;

350
351
352
	return(JS_TRUE);
}

353
static JSBool
354
js_report_error(JSContext *cx, uintN argc, jsval *arglist)
355
{
356
	jsval *argv=JS_ARGV(cx, arglist);
deuce's avatar
deuce committed
357
358
	char	*p;

359
	JSVALUE_TO_STRING(cx, argv[0], p, NULL);
deuce's avatar
deuce committed
360
	JS_ReportError(cx,"%s",p);
361

362
363
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

364
365
366
367
368
	if(argc>1 && argv[1]==JSVAL_TRUE)
		return(JS_FALSE);	/* fatal */

	return(JS_TRUE);
}
369

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

deuce's avatar
deuce committed
378
379
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

380
381
382
383
384
385
	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);

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

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

	return(JS_TRUE);
}

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

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

	return(JS_TRUE);
}

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

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

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

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

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

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

rswindell's avatar
rswindell committed
474
	branch->auto_terminate=FALSE;
475

476
477
478
479
	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
480
		free(p);
481
482
483
	}

	strListFree(&branch->exit_func);
484

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

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

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

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

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

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

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

	return(obj);
}
533

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