js_internal.c 15.8 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
/* Execute a string in its own context (away from Synchronet objects) */
static JSBool
247
js_eval(JSContext *parent_cx, uintN argc, jsval *arglist)
248
{
249
250
	JSObject *parent_obj=JS_THIS_OBJECT(parent_cx, arglist);
	jsval *argv=JS_ARGV(parent_cx, arglist);
251
	char*			buf;
252
253
	size_t			buflen;
	JSString*		str;
254
    JSObject*		script;
255
256
	JSContext*		cx;
	JSObject*		obj;
257
	JSErrorReporter	reporter;
258

259
260
261
	if(argc<1)
		return(JS_TRUE);

262
	if((str=JS_ValueToString(parent_cx, argv[0]))==NULL)
263
		return(JS_FALSE);
264
265
266
	if((buf=JS_GetStringBytes(str))==NULL)
		return(JS_FALSE);
	buflen=JS_GetStringLength(str);
267

268
	if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL)
269
270
		return(JS_FALSE);

271
	/* Use the error reporter from the parent context */
272
273
	reporter=JS_SetErrorReporter(parent_cx,NULL);
	JS_SetErrorReporter(parent_cx,reporter);
274
	JS_SetErrorReporter(cx,reporter);
275

276
#ifdef EVAL_BRANCH_CALLBACK
277
	JS_SetContextPrivate(cx, JS_GetPrivate(parent_cx, parent_obj));
278
#if JS_VERSION>180
279
280
	JS_SetOperationCallback(cx, js_OperationCallback);
#else
281
	JS_SetBranchCallback(cx, js_BranchCallback);
282
#endif
283
284
#else	/* Use the branch callback from the parent context */
	JS_SetContextPrivate(cx, JS_GetContextPrivate(parent_cx));
285
#if JS_VERSION>180
deuce's avatar
deuce committed
286
	JS_SetOperationCallback(cx, JS_GetOperationCallback(parent_cx));
287
#else
288
289
290
	callback=JS_SetBranchCallback(parent_cx,NULL);
	JS_SetBranchCallback(parent_cx, callback);
	JS_SetBranchCallback(cx, callback);
291
#endif
292
#endif
293

294
295
296
297
298
	if((obj=JS_NewObject(cx, NULL, NULL, NULL))==NULL
		|| !JS_InitStandardClasses(cx,obj)) {
		JS_DestroyContext(cx);
		return(JS_FALSE);
	}
299

300
	if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
deuce's avatar
deuce committed
301
302
303
		jsval	rval;

		JS_ExecuteScript(cx, obj, script, &rval);
304
		JS_DestroyScript(cx, script);
deuce's avatar
deuce committed
305
		JS_SET_RVAL(cx, arglist, rval);
306
307
308
309
310
311
312
	}

	JS_DestroyContext(cx);

    return(JS_TRUE);
}

313
static JSBool
314
js_gc(JSContext *cx, uintN argc, jsval *arglist)
315
{
316
317
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
318
319
320
321
322
	JSBool			forced=JS_TRUE;
	js_branch_t*	branch;

	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);
323
324
325
326
327
328
329
330
331

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

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

332
333
	branch->gc_attempts++;

334
335
336
	return(JS_TRUE);
}

337
static JSBool
338
js_report_error(JSContext *cx, uintN argc, jsval *arglist)
339
{
340
341
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
342
343
344
345
346
347
348
	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);
}
349

350
static JSBool
351
js_on_exit(JSContext *cx, uintN argc, jsval *arglist)
352
{
353
354
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
355
356
357
358
359
360
361
362
363
364
365
366
367
	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);
}

368
static JSBool
369
js_get_parent(JSContext *cx, uintN argc, jsval *arglist)
370
{
371
372
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
373
374
375
376
377
378
	JSObject* child=NULL;
	JSObject* parent;

	if(JS_ValueToObject(cx, argv[0], &child)
		&& child!=NULL
		&& (parent=JS_GetParent(cx,child))!=NULL)
379
		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(parent));
380
381
382
383

	return(JS_TRUE);
}

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

412
static JSBool js_internal_resolve(JSContext *cx, JSObject *obj, jsid id)
deuce's avatar
deuce committed
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
{
	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		*/
};

440
#if JS_VERSION >= 185
441
char* DLLCALL JS_GetStringBytes_dumbass(JSContext *cx, JSString *str)
442
443
{
	size_t			len;
444
	size_t			pos;
445
446
447
448
449
	const jschar	*val;
	char			*ret;

	if(!(val=JS_GetStringCharsAndLength(cx, str, &len)))
		return NULL;
450
	if(!(ret=malloc(len+1)))
451
		return NULL;
452
453
	for(pos=0; pos<len; pos++)
		ret[pos]=val[pos];
454
455
456
457
458
	ret[len]=0;
	return ret;
}
#endif

459
460
461
462
void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_branch_t* branch)
{
	char*	p;
	jsval	rval;
463
	JSObject* script;
464
	BOOL	auto_terminate=branch->auto_terminate;
465

rswindell's avatar
rswindell committed
466
	branch->auto_terminate=FALSE;
467

468
469
470
471
472
	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
473
		free(p);
474
475
476
	}

	strListFree(&branch->exit_func);
477

478
479
	if(auto_terminate)
		branch->auto_terminate = TRUE;
480
481
}

482
JSObject* DLLCALL js_CreateInternalJsObject(JSContext* cx, JSObject* parent, js_branch_t* branch, js_startup_t* startup)
483
484
485
486
487
488
489
490
491
492
{
	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);

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

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

504
		if((load_path=strListSplitCopy(NULL, startup->load_path, ",")) != NULL) {
505
506
507
			JSString*	js_str;
			unsigned	i;

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

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

	return(obj);
}
526

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