js_internal.c 16.2 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
269
	if(argc<1)
		return(JS_TRUE);

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

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

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

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

302
	if((obj=JS_NewCompartmentAndGlobalObject(cx, &eval_class, NULL))==NULL
303
304
305
306
		|| !JS_InitStandardClasses(cx,obj)) {
		JS_DestroyContext(cx);
		return(JS_FALSE);
	}
307

308
	if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
deuce's avatar
deuce committed
309
310
311
		jsval	rval;

		JS_ExecuteScript(cx, obj, script, &rval);
312
		JS_DestroyScript(cx, script);
deuce's avatar
deuce committed
313
		JS_SET_RVAL(cx, arglist, rval);
314
315
316
317
318
319
320
	}

	JS_DestroyContext(cx);

    return(JS_TRUE);
}

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

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

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

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

340
341
	branch->gc_attempts++;

342
343
344
	return(JS_TRUE);
}

345
static JSBool
346
js_report_error(JSContext *cx, uintN argc, jsval *arglist)
347
{
348
349
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
350
351
352
353
354
355
356
	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);
}
357

358
static JSBool
359
js_on_exit(JSContext *cx, uintN argc, jsval *arglist)
360
{
361
362
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
363
364
365
366
367
368
369
370
371
372
373
374
375
	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);
}

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

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

	return(JS_TRUE);
}

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

420
static JSBool js_internal_resolve(JSContext *cx, JSObject *obj, jsid id)
deuce's avatar
deuce committed
421
422
423
{
	char*			name=NULL;

deuce's avatar
deuce committed
424
425
426
427
428
429
	if(id != JSID_VOID && id != JSID_EMPTY) {
		jsval idval;
		
		JS_IdToValue(cx, id, &idval);
		name=JS_GetStringBytes(JSVAL_TO_STRING(idval));
	}
deuce's avatar
deuce committed
430
431
432
433
434
435

	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
436
	return(js_internal_resolve(cx, obj, JSID_VOID));
deuce's avatar
deuce committed
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
}

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

452
#if JS_VERSION >= 185
453
char* DLLCALL JS_GetStringBytes_dumbass(JSContext *cx, JSString *str)
454
455
{
	size_t			len;
456
	size_t			pos;
457
458
459
460
461
	const jschar	*val;
	char			*ret;

	if(!(val=JS_GetStringCharsAndLength(cx, str, &len)))
		return NULL;
462
	if(!(ret=malloc(len+1)))
463
		return NULL;
464
465
	for(pos=0; pos<len; pos++)
		ret[pos]=val[pos];
466
467
468
469
470
	ret[len]=0;
	return ret;
}
#endif

471
472
473
474
void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_branch_t* branch)
{
	char*	p;
	jsval	rval;
475
	JSObject* script;
476
	BOOL	auto_terminate=branch->auto_terminate;
477

rswindell's avatar
rswindell committed
478
	branch->auto_terminate=FALSE;
479

480
481
482
483
484
	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
485
		free(p);
486
487
488
	}

	strListFree(&branch->exit_func);
489

490
491
	if(auto_terminate)
		branch->auto_terminate = TRUE;
492
493
}

494
JSObject* DLLCALL js_CreateInternalJsObject(JSContext* cx, JSObject* parent, js_branch_t* branch, js_startup_t* startup)
495
496
497
498
499
500
501
502
503
504
{
	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);

505
506
507
	if(startup!=NULL) {
		JSObject*	load_path_list;
		jsval		val;
508
		str_list_t	load_path;
509
510
511
512
513
514
515

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

516
		if((load_path=strListSplitCopy(NULL, startup->load_path, ",")) != NULL) {
517
518
519
			JSString*	js_str;
			unsigned	i;

520
521
522
			for(i=0; load_path[i]!=NULL; i++) {
				if((js_str=JS_NewStringCopyZ(cx, load_path[i]))==NULL)
					break;
523
524
				val=STRING_TO_JSVAL(js_str);
				if(!JS_SetElement(cx, load_path_list, i, &val))
525
					break;
526
			}
527
			strListFree(&load_path);
528
529
530
		}
	}

531
#ifdef BUILD_JSDOCS
532
	js_DescribeSyncObject(cx,obj,"JavaScript execution and garbage collection control object",311);
533
534
535
536
537
	js_CreateArrayOfStrings(cx, obj, "_property_desc_list", prop_desc, JSPROP_READONLY);
#endif

	return(obj);
}
538

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