Line data Source code
1 0 : /*
2 :
3 : OOJavaScriptEngine.m
4 :
5 : JavaScript support for Oolite
6 : Copyright (C) 2007-2013 David Taylor and Jens Ayton.
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License
10 : as published by the Free Software Foundation; either version 2
11 : of the License, or (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 : MA 02110-1301, USA.
22 :
23 : */
24 :
25 : #include <jsdbgapi.h>
26 : #import "OOJavaScriptEngine.h"
27 : #import "OOJSEngineTimeManagement.h"
28 : #import "OOJSScript.h"
29 :
30 : #import "OOCollectionExtractors.h"
31 : #import "Universe.h"
32 : #import "OOPlanetEntity.h"
33 : #import "NSStringOOExtensions.h"
34 : #import "OOWeakReference.h"
35 : #import "EntityOOJavaScriptExtensions.h"
36 : #import "ResourceManager.h"
37 : #import "NSNumberOOExtensions.h"
38 : #import "OOConstToJSString.h"
39 : #import "OOVisualEffectEntity.h"
40 : #import "OOWaypointEntity.h"
41 :
42 : #import "OOJSGlobal.h"
43 : #import "OOJSMissionVariables.h"
44 : #import "OOJSMission.h"
45 : #import "OOJSVector.h"
46 : #import "OOJSQuaternion.h"
47 : #import "OOJSEntity.h"
48 : #import "OOJSShip.h"
49 : #import "OOJSStation.h"
50 : #import "OOJSDock.h"
51 : #import "OOJSVisualEffect.h"
52 : #import "OOJSExhaustPlume.h"
53 : #import "OOJSFlasher.h"
54 : #import "OOJSWormhole.h"
55 : #import "OOJSWaypoint.h"
56 : #import "OOJSPlayer.h"
57 : #import "OOJSPlayerShip.h"
58 : #import "OOJSManifest.h"
59 : #import "OOJSPlanet.h"
60 : #import "OOJSSystem.h"
61 : #import "OOJSOolite.h"
62 : #import "OOJSTimer.h"
63 : #import "OOJSClock.h"
64 : #import "OOJSSun.h"
65 : #import "OOJSWorldScripts.h"
66 : #import "OOJSSound.h"
67 : #import "OOJSSoundSource.h"
68 : #import "OOJSSpecialFunctions.h"
69 : #import "OOJSSystemInfo.h"
70 : #import "OOJSEquipmentInfo.h"
71 : #import "OOJSShipGroup.h"
72 : #import "OOJSFrameCallbacks.h"
73 : #import "OOJSFont.h"
74 :
75 : #import "OOProfilingStopwatch.h"
76 : #import "OOLoggingExtended.h"
77 :
78 : #include <stdlib.h>
79 :
80 :
81 0 : #define OOJSENGINE_JSVERSION JSVERSION_ECMA_5
82 : #ifdef DEBUG
83 : #define JIT_OPTIONS 0
84 : #else
85 0 : #define JIT_OPTIONS JSOPTION_JIT | JSOPTION_METHODJIT | JSOPTION_PROFILING
86 : #endif
87 0 : #define OOJSENGINE_CONTEXT_OPTIONS JSOPTION_VAROBJFIX | JSOPTION_RELIMIT | JSOPTION_ANONFUNFIX | JIT_OPTIONS
88 :
89 :
90 0 : #define OOJS_STACK_SIZE 8192
91 0 : #define OOJS_RUNTIME_SIZE_MiB 256
92 :
93 :
94 0 : static OOJavaScriptEngine *sSharedEngine = nil;
95 0 : static unsigned sErrorHandlerStackSkip = 0;
96 :
97 0 : JSContext *gOOJSMainThreadContext = NULL;
98 :
99 :
100 0 : NSString * const kOOJavaScriptEngineWillResetNotification = @"org.aegidian.oolite OOJavaScriptEngine will reset";
101 0 : NSString * const kOOJavaScriptEngineDidResetNotification = @"org.aegidian.oolite OOJavaScriptEngine did reset";
102 :
103 :
104 : #if OOJSENGINE_MONITOR_SUPPORT
105 :
106 : @interface OOJavaScriptEngine (OOMonitorSupportInternal)
107 :
108 0 : - (void)sendMonitorError:(JSErrorReport *)errorReport
109 : withMessage:(NSString *)message
110 : inContext:(JSContext *)context;
111 :
112 0 : - (void)sendMonitorLogMessage:(NSString *)message
113 : withMessageClass:(NSString *)messageClass
114 : inContext:(JSContext *)context;
115 :
116 : @end
117 :
118 : #endif
119 :
120 :
121 : @interface OOJavaScriptEngine (Private)
122 :
123 0 : - (BOOL) lookUpStandardClassPointers;
124 0 : - (void) registerStandardObjectConverters;
125 :
126 0 : - (void) createMainThreadContext;
127 0 : - (void) destroyMainThreadContext;
128 :
129 : @end
130 :
131 :
132 : static void ReportJSError(JSContext *context, const char *message, JSErrorReport *report);
133 :
134 : static id JSArrayConverter(JSContext *context, JSObject *object);
135 : static id JSStringConverter(JSContext *context, JSObject *object);
136 : static id JSNumberConverter(JSContext *context, JSObject *object);
137 : static id JSBooleanConverter(JSContext *context, JSObject *object);
138 :
139 :
140 : static void UnregisterObjectConverters(void);
141 : static void UnregisterSubclasses(void);
142 :
143 :
144 0 : static void ReportJSError(JSContext *context, const char *message, JSErrorReport *report)
145 : {
146 : NSString *severity = @"error";
147 : NSString *messageText = nil;
148 : NSString *lineBuf = nil;
149 : NSString *messageClass = nil;
150 : NSString *highlight = @"*****";
151 : NSString *activeScript = nil;
152 : OOJavaScriptEngine *jsEng = [OOJavaScriptEngine sharedEngine];
153 : BOOL showLocation = [jsEng showErrorLocations];
154 :
155 : // Not OOJS_BEGIN_FULL_NATIVE() - we use JSAPI while paused.
156 : OOJSPauseTimeLimiter();
157 :
158 : jschar empty[1] = { 0 };
159 : JSErrorReport blankReport =
160 : {
161 : .filename = "<unspecified file>",
162 : .linebuf = "",
163 : .uclinebuf = empty,
164 : .uctokenptr = empty,
165 : .ucmessage = empty
166 : };
167 : if (EXPECT_NOT(report == NULL)) report = &blankReport;
168 : if (EXPECT_NOT(message == NULL || *message == '\0')) message = "<unspecified error>";
169 :
170 : // Type of problem: error, warning or exception? (Strict flag wilfully ignored.)
171 : if (report->flags & JSREPORT_EXCEPTION) severity = @"exception";
172 : else if (report->flags & JSREPORT_WARNING)
173 : {
174 : severity = @"warning";
175 : highlight = @"-----";
176 : }
177 :
178 : // The error message itself
179 : messageText = [NSString stringWithUTF8String:message];
180 :
181 : // Get offending line, if present, and trim trailing line breaks
182 : lineBuf = [NSString stringWithUTF16String:report->uclinebuf];
183 : while ([lineBuf hasSuffix:@"\n"] || [lineBuf hasSuffix:@"\r"]) lineBuf = [lineBuf substringToIndex:[lineBuf length] - 1];
184 :
185 : // Get string for error number, for useful log message classes
186 : NSDictionary *errorNames = [ResourceManager dictionaryFromFilesNamed:@"javascript-errors.plist" inFolder:@"Config" andMerge:YES];
187 : NSString *errorNumberStr = [NSString stringWithFormat:@"%u", report->errorNumber];
188 : NSString *errorName = [errorNames oo_stringForKey:errorNumberStr];
189 : if (errorName == nil) errorName = errorNumberStr;
190 :
191 : // Log message class
192 : messageClass = [NSString stringWithFormat:@"script.javaScript.%@.%@", severity, errorName];
193 :
194 : // Skip the rest if this is a warning being ignored.
195 : if ((report->flags & JSREPORT_WARNING) == 0 || OOLogWillDisplayMessagesInClass(messageClass))
196 : {
197 : // First line: problem description
198 : // avoid windows DEP exceptions!
199 : OOJSScript *thisScript = [[OOJSScript currentlyRunningScript] weakRetain];
200 : activeScript = [[thisScript weakRefUnderlyingObject] displayName];
201 : [thisScript release];
202 :
203 : if (activeScript == nil) activeScript = @"<unidentified script>";
204 : OOLog(messageClass, @"%@ JavaScript %@ (%@): %@", highlight, severity, activeScript, messageText);
205 :
206 : if (showLocation && sErrorHandlerStackSkip == 0 && report->filename != NULL)
207 : {
208 : // Second line: where error occured, and line if provided. (The line is only provided for compile-time errors, not run-time errors.)
209 : if ([lineBuf length] != 0)
210 : {
211 : OOLog(messageClass, @" %s, line %d: %@", report->filename, report->lineno, lineBuf);
212 : }
213 : else
214 : {
215 : OOLog(messageClass, @" %s, line %d.", report->filename, report->lineno);
216 : }
217 : }
218 :
219 : #ifndef NDEBUG
220 : BOOL dump;
221 : if (report->flags & JSREPORT_WARNING) dump = [jsEng dumpStackForWarnings];
222 : else dump = [jsEng dumpStackForErrors];
223 : if (dump) OOJSDumpStack(context);
224 : #endif
225 :
226 : #if OOJSENGINE_MONITOR_SUPPORT
227 : JSExceptionState *exState = JS_SaveExceptionState(context);
228 : [[OOJavaScriptEngine sharedEngine] sendMonitorError:report
229 : withMessage:messageText
230 : inContext:context];
231 : JS_RestoreExceptionState(context, exState);
232 : #endif
233 : }
234 :
235 : OOJSResumeTimeLimiter();
236 : }
237 :
238 :
239 : //===========================================================================
240 : // JavaScript engine initialisation and shutdown
241 : //===========================================================================
242 :
243 : @implementation OOJavaScriptEngine
244 :
245 : + (OOJavaScriptEngine *) sharedEngine
246 : {
247 : if (sSharedEngine == nil) sSharedEngine = [[self alloc] init];
248 :
249 : return sSharedEngine;
250 : }
251 :
252 :
253 : - (void) runMissionCallback
254 : {
255 : MissionRunCallback();
256 : }
257 :
258 :
259 0 : - (id) init
260 : {
261 : NSAssert(sSharedEngine == nil, @"Attempt to create multiple OOJavaScriptEngines.");
262 :
263 : if (!(self = [super init])) return nil;
264 : sSharedEngine = self;
265 :
266 : JS_SetCStringsAreUTF8();
267 :
268 : NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
269 : #ifndef NDEBUG
270 : /* Set stack trace preferences from preferences. These will be overriden
271 : by the debug OXP script if installed, but being able to enable traces
272 : without setting up the debug console could be useful for debugging
273 : users' problems.
274 : */
275 : [self setDumpStackForErrors:[defaults boolForKey:@"dump-stack-for-errors"]];
276 : [self setDumpStackForWarnings:[defaults boolForKey:@"dump-stack-for-warnings"]];
277 : #endif
278 :
279 : assert(sizeof(jschar) == sizeof(unichar));
280 :
281 : // initialize the JS run time, and return result in runtime.
282 : uint32_t jsRuntimeInMiB = [defaults oo_intForKey:@"jsruntime-size-mib" defaultValue:OOJS_RUNTIME_SIZE_MiB];
283 : _runtime = JS_NewRuntime(jsRuntimeInMiB * 1024L * 1024L);
284 :
285 : // if runtime creation failed, end the program here.
286 : if (_runtime == NULL)
287 : {
288 : OOLog(@"script.javaScript.init.error", @"***** FATAL ERROR: failed to create JavaScript runtime with size %uMiB.", jsRuntimeInMiB);
289 : exit(1);
290 : }
291 :
292 : // OOJSTimeManagementInit() must be called before any context is created!
293 : OOJSTimeManagementInit(self, _runtime);
294 :
295 : [self createMainThreadContext];
296 :
297 : return self;
298 : }
299 :
300 :
301 0 : - (void) createMainThreadContext
302 : {
303 : NSAssert(gOOJSMainThreadContext == NULL, @"-[OOJavaScriptEngine createMainThreadContext] called while the main thread context exists.");
304 :
305 : // create a context and associate it with the JS runtime.
306 : gOOJSMainThreadContext = JS_NewContext(_runtime, OOJS_STACK_SIZE);
307 :
308 : // if context creation failed, end the program here.
309 : if (gOOJSMainThreadContext == NULL)
310 : {
311 : OOLog(@"script.javaScript.init.error", @"%@", @"***** FATAL ERROR: failed to create JavaScript context.");
312 : exit(1);
313 : }
314 :
315 : JS_BeginRequest(gOOJSMainThreadContext);
316 :
317 : JS_SetOptions(gOOJSMainThreadContext, OOJSENGINE_CONTEXT_OPTIONS);
318 : JS_SetVersion(gOOJSMainThreadContext, OOJSENGINE_JSVERSION);
319 :
320 : #if JS_GC_ZEAL
321 : uint8_t gcZeal = [[NSUserDefaults standardUserDefaults] oo_unsignedCharForKey:@"js-gc-zeal"];
322 : if (gcZeal > 0)
323 : {
324 : // Useful js-gc-zeal values are 0 (off), 1 and 2.
325 : OOLog(@"script.javaScript.debug.gcZeal", @"Setting JavaScript garbage collector zeal to %u.", gcZeal);
326 : JS_SetGCZeal(gOOJSMainThreadContext, gcZeal);
327 : }
328 : #endif
329 :
330 : JS_SetErrorReporter(gOOJSMainThreadContext, ReportJSError);
331 :
332 : // Create the global object.
333 : CreateOOJSGlobal(gOOJSMainThreadContext, &_globalObject);
334 :
335 : // Initialize the built-in JS objects and the global object.
336 : JS_InitStandardClasses(gOOJSMainThreadContext, _globalObject);
337 : if (![self lookUpStandardClassPointers])
338 : {
339 : OOLog(@"script.javaScript.init.error", @"%@", @"***** FATAL ERROR: failed to look up standard JavaScript classes.");
340 : exit(1);
341 : }
342 : [self registerStandardObjectConverters];
343 :
344 : SetUpOOJSGlobal(gOOJSMainThreadContext, _globalObject);
345 : OOConstToJSStringInit(gOOJSMainThreadContext);
346 :
347 : // Initialize Oolite classes.
348 : InitOOJSMissionVariables(gOOJSMainThreadContext, _globalObject);
349 : InitOOJSMission(gOOJSMainThreadContext, _globalObject);
350 : InitOOJSOolite(gOOJSMainThreadContext, _globalObject);
351 : InitOOJSVector(gOOJSMainThreadContext, _globalObject);
352 : InitOOJSQuaternion(gOOJSMainThreadContext, _globalObject);
353 : InitOOJSSystem(gOOJSMainThreadContext, _globalObject);
354 : InitOOJSEntity(gOOJSMainThreadContext, _globalObject);
355 : InitOOJSShip(gOOJSMainThreadContext, _globalObject);
356 : InitOOJSStation(gOOJSMainThreadContext, _globalObject);
357 : InitOOJSDock(gOOJSMainThreadContext, _globalObject);
358 : InitOOJSVisualEffect(gOOJSMainThreadContext, _globalObject);
359 : InitOOJSExhaustPlume(gOOJSMainThreadContext, _globalObject);
360 : InitOOJSFlasher(gOOJSMainThreadContext, _globalObject);
361 : InitOOJSWormhole(gOOJSMainThreadContext, _globalObject);
362 : InitOOJSWaypoint(gOOJSMainThreadContext, _globalObject);
363 : InitOOJSPlayer(gOOJSMainThreadContext, _globalObject);
364 : InitOOJSPlayerShip(gOOJSMainThreadContext, _globalObject);
365 : InitOOJSManifest(gOOJSMainThreadContext, _globalObject);
366 : InitOOJSSun(gOOJSMainThreadContext, _globalObject);
367 : InitOOJSPlanet(gOOJSMainThreadContext, _globalObject);
368 : InitOOJSScript(gOOJSMainThreadContext, _globalObject);
369 : InitOOJSTimer(gOOJSMainThreadContext, _globalObject);
370 : InitOOJSClock(gOOJSMainThreadContext, _globalObject);
371 : InitOOJSWorldScripts(gOOJSMainThreadContext, _globalObject);
372 : InitOOJSSound(gOOJSMainThreadContext, _globalObject);
373 : InitOOJSSoundSource(gOOJSMainThreadContext, _globalObject);
374 : InitOOJSSpecialFunctions(gOOJSMainThreadContext, _globalObject);
375 : InitOOJSSystemInfo(gOOJSMainThreadContext, _globalObject);
376 : InitOOJSEquipmentInfo(gOOJSMainThreadContext, _globalObject);
377 : InitOOJSShipGroup(gOOJSMainThreadContext, _globalObject);
378 : InitOOJSFrameCallbacks(gOOJSMainThreadContext, _globalObject);
379 : InitOOJSFont(gOOJSMainThreadContext, _globalObject);
380 :
381 : // Run prefix scripts.
382 : [OOJSScript jsScriptFromFileNamed:@"oolite-global-prefix.js"
383 : properties:[NSDictionary dictionaryWithObject:JSSpecialFunctionsObjectWrapper(gOOJSMainThreadContext)
384 : forKey:@"special"]];
385 :
386 : JS_EndRequest(gOOJSMainThreadContext);
387 :
388 : OOLog(@"script.javaScript.init.success", @"%@", @"Set up JavaScript context.");
389 : }
390 :
391 :
392 0 : - (void) destroyMainThreadContext
393 : {
394 : if (gOOJSMainThreadContext != NULL)
395 : {
396 : JSContext *context = OOJSAcquireContext();
397 : JS_ClearScope(gOOJSMainThreadContext, _globalObject);
398 :
399 : _globalObject = NULL;
400 : _objectClass = NULL;
401 : _stringClass = NULL;
402 : _arrayClass = NULL;
403 : _numberClass = NULL;
404 : _booleanClass = NULL;
405 :
406 : UnregisterObjectConverters();
407 : UnregisterSubclasses();
408 : OOConstToJSStringDestroy();
409 :
410 : OOJSRelinquishContext(context);
411 :
412 : _globalObject = NULL;
413 : JS_DestroyContext(gOOJSMainThreadContext); // Forces unconditional GC.
414 : gOOJSMainThreadContext = NULL;
415 : }
416 : }
417 :
418 :
419 : - (BOOL) reset
420 : {
421 : NSAssert(gOOJSMainThreadContext != NULL, @"JavaScript engine not active. Can't reset.");
422 :
423 : OOJSFrameCallbacksRemoveAll();
424 :
425 : # if 0
426 : // deferred JS reset - test harness.
427 : static int counter = 3; // loading a savegame with different strict mode calls js reset twice
428 : if (counter-- == 0) {
429 : counter = 3;
430 : OOLog(@"script.javascript.init.error", @"%@", @"JavaScript processes still pending. Can't reset JavaScript engine.");
431 : return NO;
432 : }
433 : else
434 : {
435 : OOLog(@"script.javascript.init", @"%@", @"JavaScript reset successful.");
436 : }
437 : #endif
438 :
439 : #if JS_THREADSAFE
440 : //NSAssert(!JS_IsInRequest(gOOJSMainThreadContext), @"JavaScript processes still pending. Can't reset JavaScript engine.");
441 :
442 : if (JS_IsInRequest(gOOJSMainThreadContext))
443 : {
444 : // some threads are still pending, this should mean timers are still being removed.
445 : OOLog(@"script.javascript.init.error", @"%@", @"JavaScript processes still pending. Can't reset JavaScript engine.");
446 : return NO;
447 : }
448 : else
449 : {
450 : OOLog(@"script.javascript.init", @"%@", @"JavaScript reset successful.");
451 : }
452 : #endif
453 :
454 : JSContext *context = OOJSAcquireContext();
455 : [[NSNotificationCenter defaultCenter] postNotificationName:kOOJavaScriptEngineWillResetNotification object:self];
456 : OOJSRelinquishContext(context);
457 :
458 : [self destroyMainThreadContext];
459 : [self createMainThreadContext];
460 :
461 : context = OOJSAcquireContext();
462 : [[NSNotificationCenter defaultCenter] postNotificationName:kOOJavaScriptEngineDidResetNotification object:self];
463 : OOJSRelinquishContext(context);
464 :
465 : [self garbageCollectionOpportunity:YES];
466 : return YES;
467 : }
468 :
469 :
470 0 : - (void) dealloc
471 : {
472 : sSharedEngine = nil;
473 :
474 : OOJSFrameCallbacksRemoveAll();
475 :
476 : [self destroyMainThreadContext];
477 : JS_DestroyRuntime(_runtime);
478 :
479 : [super dealloc];
480 : }
481 :
482 :
483 : - (JSObject *) globalObject
484 : {
485 : return _globalObject;
486 : }
487 :
488 :
489 : - (BOOL) callJSFunction:(jsval)function
490 : forObject:(JSObject *)jsThis
491 : argc:(uintN)argc
492 : argv:(jsval *)argv
493 : result:(jsval *)outResult
494 : {
495 : JSContext *context = NULL;
496 : BOOL result;
497 :
498 : NSParameterAssert(OOJSValueIsFunction(context, function));
499 :
500 : context = OOJSAcquireContext();
501 :
502 : OOJSStartTimeLimiter();
503 : result = JS_CallFunctionValue(context, jsThis, function, argc, argv, outResult);
504 : OOJSStopTimeLimiter();
505 :
506 : JS_ReportPendingException(context);
507 : OOJSRelinquishContext(context);
508 :
509 : return result;
510 : }
511 :
512 :
513 : - (void) removeGCObjectRoot:(JSObject **)rootPtr
514 : {
515 : JSContext *context = OOJSAcquireContext();
516 : JS_RemoveObjectRoot(context, rootPtr);
517 : OOJSRelinquishContext(context);
518 : }
519 :
520 :
521 : - (void) removeGCValueRoot:(jsval *)rootPtr
522 : {
523 : JSContext *context = OOJSAcquireContext();
524 : JS_RemoveValueRoot(context, rootPtr);
525 : OOJSRelinquishContext(context);
526 : }
527 :
528 :
529 : - (void) garbageCollectionOpportunity:(BOOL)force
530 : {
531 : JSContext *context = OOJSAcquireContext();
532 : if (force)
533 : {
534 : JS_GC(context);
535 : }
536 : else
537 : {
538 : JS_MaybeGC(context);
539 : }
540 : OOJSRelinquishContext(context);
541 : }
542 :
543 :
544 : - (BOOL) showErrorLocations
545 : {
546 : return _showErrorLocations;
547 : }
548 :
549 :
550 : - (void) setShowErrorLocations:(BOOL)value
551 : {
552 : _showErrorLocations = !!value;
553 : }
554 :
555 :
556 : - (JSClass *) objectClass
557 : {
558 : return _objectClass;
559 : }
560 :
561 :
562 : - (JSClass *) stringClass
563 : {
564 : return _stringClass;
565 : }
566 :
567 :
568 : - (JSClass *) arrayClass
569 : {
570 : return _arrayClass;
571 : }
572 :
573 :
574 : - (JSClass *) numberClass
575 : {
576 : return _numberClass;
577 : }
578 :
579 :
580 : - (JSClass *) booleanClass
581 : {
582 : return _booleanClass;
583 : }
584 :
585 :
586 0 : - (BOOL) lookUpStandardClassPointers
587 : {
588 : JSObject *templateObject = NULL;
589 :
590 : templateObject = JS_NewObject(gOOJSMainThreadContext, NULL, NULL, NULL);
591 : if (EXPECT_NOT(templateObject == NULL)) return NO;
592 : _objectClass = OOJSGetClass(gOOJSMainThreadContext, templateObject);
593 :
594 : if (EXPECT_NOT(!JS_ValueToObject(gOOJSMainThreadContext, JS_GetEmptyStringValue(gOOJSMainThreadContext), &templateObject))) return NO;
595 : _stringClass = OOJSGetClass(gOOJSMainThreadContext, templateObject);
596 :
597 : templateObject = JS_NewArrayObject(gOOJSMainThreadContext, 0, NULL);
598 : if (EXPECT_NOT(templateObject == NULL)) return NO;
599 : _arrayClass = OOJSGetClass(gOOJSMainThreadContext, templateObject);
600 :
601 : if (EXPECT_NOT(!JS_ValueToObject(gOOJSMainThreadContext, INT_TO_JSVAL(0), &templateObject))) return NO;
602 : _numberClass = OOJSGetClass(gOOJSMainThreadContext, templateObject);
603 :
604 : if (EXPECT_NOT(!JS_ValueToObject(gOOJSMainThreadContext, JSVAL_FALSE, &templateObject))) return NO;
605 : _booleanClass = OOJSGetClass(gOOJSMainThreadContext, templateObject);
606 :
607 : return YES;
608 : }
609 :
610 :
611 0 : - (void) registerStandardObjectConverters
612 : {
613 : OOJSRegisterObjectConverter([self objectClass], (OOJSClassConverterCallback)OOJSDictionaryFromJSObject);
614 : OOJSRegisterObjectConverter([self stringClass], JSStringConverter);
615 : OOJSRegisterObjectConverter([self arrayClass], JSArrayConverter);
616 : OOJSRegisterObjectConverter([self numberClass], JSNumberConverter);
617 : OOJSRegisterObjectConverter([self booleanClass], JSBooleanConverter);
618 : }
619 :
620 :
621 : #ifndef NDEBUG
622 0 : static JSTrapStatus DebuggerHook(JSContext *context, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
623 : {
624 : OOJSPauseTimeLimiter();
625 :
626 : OOLog(@"script.javaScript.debugger", @"debugger invoked during %@:", [[OOJSScript currentlyRunningScript] displayName]);
627 : OOJSDumpStack(context);
628 :
629 : OOJSResumeTimeLimiter();
630 :
631 : return JSTRAP_CONTINUE;
632 : }
633 :
634 :
635 : - (BOOL) dumpStackForErrors
636 : {
637 : return _dumpStackForErrors;
638 : }
639 :
640 :
641 : - (void) setDumpStackForErrors:(BOOL)value
642 : {
643 : _dumpStackForErrors = !!value;
644 : }
645 :
646 :
647 : - (BOOL) dumpStackForWarnings
648 : {
649 : return _dumpStackForWarnings;
650 : }
651 :
652 :
653 : - (void) setDumpStackForWarnings:(BOOL)value
654 : {
655 : _dumpStackForWarnings = !!value;
656 : }
657 :
658 :
659 : - (void) enableDebuggerStatement
660 : {
661 : JS_SetDebuggerHandler(_runtime, DebuggerHook, self);
662 : }
663 : #endif
664 :
665 : @end
666 :
667 :
668 : #if OOJSENGINE_MONITOR_SUPPORT
669 :
670 : @implementation OOJavaScriptEngine (OOMonitorSupport)
671 :
672 0 : - (void) setMonitor:(id<OOJavaScriptEngineMonitor>)inMonitor
673 : {
674 : [_monitor autorelease];
675 : _monitor = [inMonitor retain];
676 : }
677 :
678 : @end
679 :
680 :
681 : @implementation OOJavaScriptEngine (OOMonitorSupportInternal)
682 :
683 : - (void) sendMonitorError:(JSErrorReport *)errorReport
684 : withMessage:(NSString *)message
685 : inContext:(JSContext *)theContext
686 : {
687 : if ([_monitor respondsToSelector:@selector(jsEngine:context:error:stackSkip:showingLocation:withMessage:)])
688 : {
689 : [_monitor jsEngine:self context:theContext error:errorReport stackSkip:sErrorHandlerStackSkip showingLocation:[self showErrorLocations] withMessage:message];
690 : }
691 : }
692 :
693 :
694 : - (void) sendMonitorLogMessage:(NSString *)message
695 : withMessageClass:(NSString *)messageClass
696 : inContext:(JSContext *)theContext
697 : {
698 : if ([_monitor respondsToSelector:@selector(jsEngine:context:logMessage:ofClass:)])
699 : {
700 : [_monitor jsEngine:self context:theContext logMessage:message ofClass:messageClass];
701 : }
702 : }
703 :
704 : @end
705 :
706 : #endif
707 :
708 :
709 : #ifndef NDEBUG
710 :
711 0 : static void DumpVariable(JSContext *context, JSPropertyDesc *prop)
712 : {
713 : NSString *name = OOStringFromJSValueEvenIfNull(context, prop->id);
714 : NSString *value = OOJSDescribeValue(context, prop->value, YES);
715 :
716 : enum
717 : {
718 : kInterestingFlags = ~(JSPD_ENUMERATE | JSPD_PERMANENT | JSPD_VARIABLE | JSPD_ARGUMENT)
719 : };
720 :
721 : NSString *flagStr = @"";
722 : if ((prop->flags & kInterestingFlags) != 0)
723 : {
724 : NSMutableArray *flags = [NSMutableArray array];
725 : if (prop->flags & JSPD_READONLY) [flags addObject:@"read-only"];
726 : if (prop->flags & JSPD_ALIAS) [flags addObject:[NSString stringWithFormat:@"alias (%@)", OOJSDescribeValue(context, prop->alias, YES)]];
727 : if (prop->flags & JSPD_EXCEPTION) [flags addObject:@"exception"];
728 : if (prop->flags & JSPD_ERROR) [flags addObject:@"error"];
729 :
730 : flagStr = [NSString stringWithFormat:@" [%@]", [flags componentsJoinedByString:@", "]];
731 : }
732 :
733 : OOLog(@"script.javaScript.stackTrace", @" %@: %@%@", name, value, flagStr);
734 : }
735 :
736 :
737 0 : void OOJSDumpStack(JSContext *context)
738 : {
739 : NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
740 :
741 : @try
742 : {
743 : JSStackFrame *frame = NULL;
744 : unsigned idx = 0;
745 : unsigned skip = sErrorHandlerStackSkip;
746 :
747 : while (JS_FrameIterator(context, &frame) != NULL)
748 : {
749 : JSScript *script = JS_GetFrameScript(context, frame);
750 : NSString *desc = nil;
751 : JSPropertyDescArray properties = { 0 , NULL };
752 : BOOL gotProperties = NO;
753 :
754 : idx++;
755 :
756 : if (!JS_IsScriptFrame(context, frame))
757 : {
758 : continue;
759 : }
760 :
761 : if (skip != 0)
762 : {
763 : skip--;
764 : continue;
765 : }
766 :
767 : if (script != NULL)
768 : {
769 : NSString *location = OOJSDescribeLocation(context, frame);
770 : JSObject *scope = JS_GetFrameScopeChain(context, frame);
771 :
772 : if (scope != NULL) gotProperties = JS_GetPropertyDescArray(context, scope, &properties);
773 :
774 : NSString *funcDesc = nil;
775 : JSFunction *function = JS_GetFrameFunction(context, frame);
776 : if (function != NULL)
777 : {
778 : JSString *funcName = JS_GetFunctionId(function);
779 : if (funcName != NULL)
780 : {
781 : funcDesc = OOStringFromJSString(context, funcName);
782 : if (!JS_IsConstructorFrame(context, frame))
783 : {
784 : funcDesc = [funcDesc stringByAppendingString:@"()"];
785 : }
786 : else
787 : {
788 : funcDesc = [NSString stringWithFormat:@"new %@()", funcDesc];
789 : }
790 :
791 : }
792 : else
793 : {
794 : funcDesc = @"<anonymous function>";
795 : }
796 : }
797 : else
798 : {
799 : funcDesc = @"<not a function frame>";
800 : }
801 :
802 : desc = [NSString stringWithFormat:@"(%@) %@", location, funcDesc];
803 : }
804 : else if (JS_IsDebuggerFrame(context, frame))
805 : {
806 : desc = @"<debugger frame>";
807 : }
808 : else
809 : {
810 : desc = @"<Oolite native>";
811 : }
812 :
813 : OOLog(@"script.javaScript.stackTrace", @"%2u %@", idx - 1, desc);
814 :
815 : if (gotProperties)
816 : {
817 : jsval this;
818 : if (JS_GetFrameThis(context, frame, &this))
819 : {
820 : static BOOL haveThis = NO;
821 : static jsval thisAtom;
822 : if (EXPECT_NOT(!haveThis))
823 : {
824 : thisAtom = STRING_TO_JSVAL(JS_InternString(context, "this"));
825 : haveThis = YES;
826 : }
827 : JSPropertyDesc thisDesc = { .id = thisAtom, .value = this };
828 : DumpVariable(context, &thisDesc);
829 : }
830 :
831 : // Dump arguments.
832 : unsigned i;
833 : for (i = 0; i < properties.length; i++)
834 : {
835 : JSPropertyDesc *prop = &properties.array[i];
836 : if (prop->flags & JSPD_ARGUMENT) DumpVariable(context, prop);
837 : }
838 :
839 : // Dump locals.
840 : for (i = 0; i < properties.length; i++)
841 : {
842 : JSPropertyDesc *prop = &properties.array[i];
843 : if (prop->flags & JSPD_VARIABLE) DumpVariable(context, prop);
844 : }
845 :
846 : // Dump anything else.
847 : for (i = 0; i < properties.length; i++)
848 : {
849 : JSPropertyDesc *prop = &properties.array[i];
850 : if (!(prop->flags & (JSPD_ARGUMENT | JSPD_VARIABLE))) DumpVariable(context, prop);
851 : }
852 :
853 : JS_PutPropertyDescArray(context, &properties);
854 : }
855 : }
856 : }
857 : @catch (NSException *exception)
858 : {
859 : OOLog(kOOLogException, @"Exception during JavaScript stack trace: %@:%@", [exception name], [exception reason]);
860 : }
861 :
862 : [pool release];
863 : }
864 :
865 :
866 0 : static const char *sConsoleScriptName; // Lifetime is lifetime of script object, which is forever.
867 0 : static NSUInteger sConsoleEvalLineNo;
868 :
869 :
870 0 : static void GetLocationNameAndLine(JSContext *context, JSStackFrame *stackFrame, const char **name, NSUInteger *line)
871 : {
872 : NSCParameterAssert(context != NULL && stackFrame != NULL && name != NULL && line != NULL);
873 :
874 : *name = NULL;
875 : *line = 0;
876 :
877 : JSScript *script = JS_GetFrameScript(context, stackFrame);
878 : if (script != NULL)
879 : {
880 : *name = JS_GetScriptFilename(context, script);
881 : if (name != NULL)
882 : {
883 : jsbytecode *PC = JS_GetFramePC(context, stackFrame);
884 : *line = JS_PCToLineNumber(context, script, PC);
885 : }
886 : }
887 : else if (JS_IsDebuggerFrame(context, stackFrame))
888 : {
889 : *name = "<debugger frame>";
890 : }
891 : }
892 :
893 :
894 0 : NSString *OOJSDescribeLocation(JSContext *context, JSStackFrame *stackFrame)
895 : {
896 : NSCParameterAssert(context != NULL && stackFrame != NULL);
897 :
898 : const char *fileName;
899 : NSUInteger lineNo;
900 : GetLocationNameAndLine(context, stackFrame, &fileName, &lineNo);
901 : if (fileName == NULL) return nil;
902 :
903 : // If this stops working, we probably need to switch to strcmp().
904 : if (fileName == sConsoleScriptName && lineNo >= sConsoleEvalLineNo) return @"<console input>";
905 :
906 : // Objectify it.
907 : NSString *fileNameObj = [NSString stringWithUTF8String:fileName];
908 : if (fileNameObj == nil) fileNameObj = [NSString stringWithCString:fileName encoding:NSISOLatin1StringEncoding];
909 : if (fileNameObj == nil) return nil;
910 :
911 : NSString *shortFileName = [fileNameObj lastPathComponent];
912 : if (![[shortFileName lowercaseString] isEqualToString:@"script.js"]) fileNameObj = shortFileName;
913 :
914 : return [NSString stringWithFormat:@"%@:%lu", fileNameObj, lineNo];
915 : }
916 :
917 :
918 0 : void OOJSMarkConsoleEvalLocation(JSContext *context, JSStackFrame *stackFrame)
919 : {
920 : GetLocationNameAndLine(context, stackFrame, &sConsoleScriptName, &sConsoleEvalLineNo);
921 : }
922 : #endif
923 :
924 :
925 0 : void OOJSInitJSIDCachePRIVATE(const char *name, jsid *idCache)
926 : {
927 : NSCParameterAssert(name != NULL && name[0] != '\0' && idCache != NULL);
928 :
929 : JSContext *context = OOJSAcquireContext();
930 :
931 : JSString *string = JS_InternString(context, name);
932 : if (EXPECT_NOT(string == NULL))
933 : {
934 : [NSException raise:NSGenericException format:@"Failed to initialize JS ID cache for \"%s\".", name];
935 : }
936 :
937 : *idCache = INTERNED_STRING_TO_JSID(string);
938 :
939 : OOJSRelinquishContext(context);
940 : }
941 :
942 :
943 0 : jsid OOJSIDFromString(NSString *string)
944 : {
945 : if (EXPECT_NOT(string == nil)) return JSID_VOID;
946 :
947 : JSContext *context = OOJSAcquireContext();
948 :
949 : enum { kStackBufSize = 1024 };
950 : unichar stackBuf[kStackBufSize];
951 : unichar *buffer;
952 : size_t length = [string length];
953 : if (length < kStackBufSize)
954 : {
955 : buffer = stackBuf;
956 : }
957 : else
958 : {
959 : buffer = malloc(sizeof (unichar) * length);
960 : if (EXPECT_NOT(buffer == NULL)) return JSID_VOID;
961 : }
962 : [string getCharacters:buffer];
963 :
964 : JSString *jsString = JS_InternUCStringN(context, buffer, length);
965 :
966 : if (EXPECT_NOT(buffer != stackBuf)) free(buffer);
967 :
968 : OOJSRelinquishContext(context);
969 :
970 : if (EXPECT(jsString != NULL)) return INTERNED_STRING_TO_JSID(jsString);
971 : else return JSID_VOID;
972 : }
973 :
974 :
975 0 : NSString *OOStringFromJSID(jsid propID)
976 : {
977 : JSContext *context = OOJSAcquireContext();
978 :
979 : jsval value;
980 : NSString *result = nil;
981 : if (JS_IdToValue(context, propID, &value))
982 : {
983 : result = OOStringFromJSString(context, JS_ValueToString(context, value));
984 : }
985 :
986 : OOJSRelinquishContext(context);
987 :
988 : return result;
989 : }
990 :
991 :
992 0 : static NSString *CallerPrefix(NSString *scriptClass, NSString *function)
993 : {
994 : if (function == nil) return @"";
995 : if (scriptClass == nil) return [function stringByAppendingString:@": "];
996 : return [NSString stringWithFormat:@"%@.%@: ", scriptClass, function];
997 : }
998 :
999 :
1000 0 : void OOJSReportError(JSContext *context, NSString *format, ...)
1001 : {
1002 : va_list args;
1003 :
1004 : va_start(args, format);
1005 : OOJSReportErrorWithArguments(context, format, args);
1006 : va_end(args);
1007 : }
1008 :
1009 :
1010 0 : void OOJSReportErrorForCaller(JSContext *context, NSString *scriptClass, NSString *function, NSString *format, ...)
1011 : {
1012 : va_list args;
1013 : NSString *msg = nil;
1014 :
1015 : @try
1016 : {
1017 : va_start(args, format);
1018 : msg = [[NSString alloc] initWithFormat:format arguments:args];
1019 : va_end(args);
1020 :
1021 : OOJSReportError(context, @"%@%@", CallerPrefix(scriptClass, function), msg);
1022 : }
1023 : @catch (id exception)
1024 : {
1025 : // Squash any secondary errors during error handling.
1026 : }
1027 : [msg release];
1028 : }
1029 :
1030 :
1031 0 : void OOJSReportErrorWithArguments(JSContext *context, NSString *format, va_list args)
1032 : {
1033 : NSString *msg = nil;
1034 :
1035 : NSCParameterAssert(JS_IsInRequest(context));
1036 :
1037 : @try
1038 : {
1039 : msg = [[NSString alloc] initWithFormat:format arguments:args];
1040 : JS_ReportError(context, "%s", [msg UTF8String]);
1041 : }
1042 : @catch (id exception)
1043 : {
1044 : // Squash any secondary errors during error handling.
1045 : }
1046 : [msg release];
1047 : }
1048 :
1049 :
1050 0 : void OOJSReportWrappedException(JSContext *context, id exception)
1051 : {
1052 : if (!JS_IsExceptionPending(context))
1053 : {
1054 : if ([exception isKindOfClass:[NSException class]]) OOJSReportError(context, @"Native exception: %@", [exception reason]);
1055 : else OOJSReportError(context, @"Unidentified native exception");
1056 : }
1057 : // Else, let the pending exception propagate.
1058 : }
1059 :
1060 :
1061 : #ifndef NDEBUG
1062 :
1063 0 : void OOJSUnreachable(const char *function, const char *file, unsigned line)
1064 : {
1065 : OOLog(@"fatal.unreachable", @"Supposedly unreachable statement reached in %s (%@:%u) -- terminating.", function, OOLogAbbreviatedFileName(file), line);
1066 : abort();
1067 : }
1068 :
1069 : #endif
1070 :
1071 :
1072 0 : void OOJSReportWarning(JSContext *context, NSString *format, ...)
1073 : {
1074 : va_list args;
1075 :
1076 : va_start(args, format);
1077 : OOJSReportWarningWithArguments(context, format, args);
1078 : va_end(args);
1079 : }
1080 :
1081 :
1082 0 : void OOJSReportWarningForCaller(JSContext *context, NSString *scriptClass, NSString *function, NSString *format, ...)
1083 : {
1084 : va_list args;
1085 : NSString *msg = nil;
1086 :
1087 : @try
1088 : {
1089 : va_start(args, format);
1090 : msg = [[NSString alloc] initWithFormat:format arguments:args];
1091 : va_end(args);
1092 :
1093 : OOJSReportWarning(context, @"%@%@", CallerPrefix(scriptClass, function), msg);
1094 : }
1095 : @catch (id exception)
1096 : {
1097 : // Squash any secondary errors during error handling.
1098 : }
1099 : [msg release];
1100 : }
1101 :
1102 :
1103 0 : void OOJSReportWarningWithArguments(JSContext *context, NSString *format, va_list args)
1104 : {
1105 : NSString *msg = nil;
1106 :
1107 : @try
1108 : {
1109 : msg = [[NSString alloc] initWithFormat:format arguments:args];
1110 : JS_ReportWarning(context, "%s", [msg UTF8String]);
1111 : }
1112 : @catch (id exception)
1113 : {
1114 : // Squash any secondary errors during error handling.
1115 : }
1116 : [msg release];
1117 : }
1118 :
1119 :
1120 0 : void OOJSReportBadPropertySelector(JSContext *context, JSObject *thisObj, jsid propID, JSPropertySpec *propertySpec)
1121 : {
1122 : NSString *propName = OOStringFromJSPropertyIDAndSpec(context, propID, propertySpec);
1123 : const char *className = OOJSGetClass(context, thisObj)->name;
1124 :
1125 : OOJSReportError(context, @"Invalid property identifier %@ for instance of %s.", propName, className);
1126 : }
1127 :
1128 :
1129 0 : void OOJSReportBadPropertyValue(JSContext *context, JSObject *thisObj, jsid propID, JSPropertySpec *propertySpec, jsval value)
1130 : {
1131 : NSString *propName = OOStringFromJSPropertyIDAndSpec(context, propID, propertySpec);
1132 : const char *className = OOJSGetClass(context, thisObj)->name;
1133 : NSString *valueDesc = OOJSDescribeValue(context, value, YES);
1134 :
1135 : OOJSReportError(context, @"Cannot set property %@ of instance of %s to invalid value %@.", propName, className, valueDesc);
1136 : }
1137 :
1138 :
1139 0 : void OOJSReportBadArguments(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, NSString *message, NSString *expectedArgsDescription)
1140 : {
1141 : @try
1142 : {
1143 : if (message == nil) message = @"Invalid arguments";
1144 : message = [NSString stringWithFormat:@"%@ %@", message, [NSString stringWithJavaScriptParameters:argv count:argc inContext:context]];
1145 : if (expectedArgsDescription != nil) message = [NSString stringWithFormat:@"%@ -- expected %@", message, expectedArgsDescription];
1146 :
1147 : OOJSReportErrorForCaller(context, scriptClass, function, @"%@.", message);
1148 : }
1149 : @catch (id exception)
1150 : {
1151 : // Squash any secondary errors during error handling.
1152 : }
1153 : }
1154 :
1155 :
1156 0 : void OOJSSetWarningOrErrorStackSkip(unsigned skip)
1157 : {
1158 : sErrorHandlerStackSkip = skip;
1159 : }
1160 :
1161 :
1162 0 : BOOL OOJSArgumentListGetNumber(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, double *outNumber, uintN *outConsumed)
1163 : {
1164 : if (OOJSArgumentListGetNumberNoError(context, argc, argv, outNumber, outConsumed))
1165 : {
1166 : return YES;
1167 : }
1168 : else
1169 : {
1170 : OOJSReportBadArguments(context, scriptClass, function, argc, argv,
1171 : @"Expected number, got", NULL);
1172 : return NO;
1173 : }
1174 : }
1175 :
1176 :
1177 0 : BOOL OOJSArgumentListGetNumberNoError(JSContext *context, uintN argc, jsval *argv, double *outNumber, uintN *outConsumed)
1178 : {
1179 : OOJS_PROFILE_ENTER
1180 :
1181 : double value;
1182 :
1183 : NSCParameterAssert(context != NULL && (argv != NULL || argc == 0) && outNumber != NULL);
1184 :
1185 : // Get value, if possible.
1186 : if (EXPECT_NOT(!JS_ValueToNumber(context, argv[0], &value) || isnan(value)))
1187 : {
1188 : if (outConsumed != NULL) *outConsumed = 0;
1189 : return NO;
1190 : }
1191 :
1192 : // Success.
1193 : *outNumber = value;
1194 : if (outConsumed != NULL) *outConsumed = 1;
1195 : return YES;
1196 :
1197 : OOJS_PROFILE_EXIT
1198 : }
1199 :
1200 :
1201 0 : static JSObject *JSArrayFromNSArray(JSContext *context, NSArray *array)
1202 : {
1203 : OOJS_PROFILE_ENTER
1204 :
1205 : JSObject *result = NULL;
1206 :
1207 : if (array == nil) return NULL;
1208 :
1209 : @try
1210 : {
1211 : NSUInteger fullCount = [array count];
1212 : if (EXPECT_NOT(fullCount > INT32_MAX))
1213 : {
1214 : return NULL;
1215 : }
1216 :
1217 : uint32_t i, count = (int32_t)fullCount;
1218 :
1219 : result = JS_NewArrayObject(context, 0, NULL);
1220 : if (result != NULL)
1221 : {
1222 : for (i = 0; i != count; ++i)
1223 : {
1224 : jsval value = [[array objectAtIndex:i] oo_jsValueInContext:context];
1225 : BOOL OK = JS_SetElement(context, result, i, &value);
1226 :
1227 : if (EXPECT_NOT(!OK))
1228 : {
1229 : result = NULL;
1230 : break;
1231 : }
1232 : }
1233 : }
1234 : }
1235 : @catch (id ex)
1236 : {
1237 : result = NULL;
1238 : }
1239 :
1240 : return (JSObject *)result;
1241 :
1242 : OOJS_PROFILE_EXIT
1243 : }
1244 :
1245 :
1246 0 : static BOOL JSNewNSArrayValue(JSContext *context, NSArray *array, jsval *value)
1247 : {
1248 : OOJS_PROFILE_ENTER
1249 :
1250 : JSObject *object = NULL;
1251 : BOOL OK = YES;
1252 :
1253 : if (value == NULL) return NO;
1254 :
1255 : // NOTE: should be called within a local root scope or have *value be a set root for GC reasons.
1256 : if (!JS_EnterLocalRootScope(context)) return NO;
1257 :
1258 : object = JSArrayFromNSArray(context, array);
1259 : if (object == NULL)
1260 : {
1261 : *value = JSVAL_VOID;
1262 : OK = NO;
1263 : }
1264 : else
1265 : {
1266 : *value = OBJECT_TO_JSVAL(object);
1267 : }
1268 :
1269 : JS_LeaveLocalRootScopeWithResult(context, *value);
1270 : return OK;
1271 :
1272 : OOJS_PROFILE_EXIT
1273 : }
1274 :
1275 :
1276 : /* Convert an NSDictionary to a JavaScript Object.
1277 : Only properties whose keys are either strings or non-negative NSNumbers,
1278 : and whose values have a non-void JS representation, are converted.
1279 : */
1280 0 : static JSObject *JSObjectFromNSDictionary(JSContext *context, NSDictionary *dictionary)
1281 : {
1282 : OOJS_PROFILE_ENTER
1283 :
1284 : JSObject *result = NULL;
1285 : BOOL OK = YES;
1286 : NSEnumerator *keyEnum = nil;
1287 : id key = nil;
1288 : jsval value;
1289 : jsint index;
1290 :
1291 : if (dictionary == nil) return NULL;
1292 :
1293 : @try
1294 : {
1295 : result = JS_NewObject(context, NULL, NULL, NULL); // create object of class Object
1296 : if (result != NULL)
1297 : {
1298 : for (keyEnum = [dictionary keyEnumerator]; (key = [keyEnum nextObject]); )
1299 : {
1300 : if ([key isKindOfClass:[NSString class]] && [key length] != 0)
1301 : {
1302 : #ifndef __GNUC__
1303 : value = [[dictionary objectForKey:key] oo_jsValueInContext:context];
1304 : #else
1305 : #if __GNUC__ > 4 || __GNUC_MINOR__ > 6
1306 : value = [[dictionary objectForKey:key] oo_jsValueInContext:context];
1307 : #else
1308 : // GCC before 4.7 seems to have problems with this
1309 : // bit if the object is a weakref, causing crashes
1310 : // in docking code.
1311 : id tmp = [dictionary objectForKey:key];
1312 : if ([tmp respondsToSelector:@selector(weakRefUnderlyingObject)])
1313 : {
1314 : tmp = [tmp weakRefUnderlyingObject];
1315 : }
1316 : value = [tmp oo_jsValueInContext:context];
1317 : #endif
1318 : #endif
1319 : if (!JSVAL_IS_VOID(value))
1320 : {
1321 : OK = JS_SetPropertyById(context, result, OOJSIDFromString(key), &value);
1322 : if (EXPECT_NOT(!OK)) break;
1323 : }
1324 : }
1325 : else if ([key isKindOfClass:[NSNumber class]])
1326 : {
1327 : index = [key intValue];
1328 : if (0 < index)
1329 : {
1330 : value = [[dictionary objectForKey:key] oo_jsValueInContext:context];
1331 : if (!JSVAL_IS_VOID(value))
1332 : {
1333 : OK = JS_SetElement(context, (JSObject *)result, index, &value);
1334 : if (EXPECT_NOT(!OK)) break;
1335 : }
1336 : }
1337 : }
1338 :
1339 : if (EXPECT_NOT(!OK)) break;
1340 : }
1341 : }
1342 : }
1343 : @catch (id exception)
1344 : {
1345 : OK = NO;
1346 : }
1347 :
1348 : if (EXPECT_NOT(!OK))
1349 : {
1350 : result = NULL;
1351 : }
1352 :
1353 : return (JSObject *)result;
1354 :
1355 : OOJS_PROFILE_EXIT
1356 : }
1357 :
1358 :
1359 0 : static BOOL JSNewNSDictionaryValue(JSContext *context, NSDictionary *dictionary, jsval *value)
1360 : {
1361 : OOJS_PROFILE_ENTER
1362 :
1363 : JSObject *object = NULL;
1364 : BOOL OK = YES;
1365 :
1366 : if (value == NULL) return NO;
1367 :
1368 : // NOTE: should be called within a local root scope or have *value be a set root for GC reasons.
1369 : if (!JS_EnterLocalRootScope(context)) return NO;
1370 :
1371 : object = JSObjectFromNSDictionary(context, dictionary);
1372 : if (object == NULL)
1373 : {
1374 : *value = JSVAL_VOID;
1375 : OK = NO;
1376 : }
1377 : else
1378 : {
1379 : *value = OBJECT_TO_JSVAL(object);
1380 : }
1381 :
1382 : JS_LeaveLocalRootScopeWithResult(context, *value);
1383 : return OK;
1384 :
1385 : OOJS_PROFILE_EXIT
1386 : }
1387 :
1388 :
1389 : @implementation NSObject (OOJavaScriptConversion)
1390 :
1391 0 : - (jsval) oo_jsValueInContext:(JSContext *)context
1392 : {
1393 : return JSVAL_VOID;
1394 : }
1395 :
1396 :
1397 0 : - (NSString *) oo_jsClassName
1398 : {
1399 : return nil;
1400 : }
1401 :
1402 :
1403 0 : - (NSString *) oo_jsDescription
1404 : {
1405 : return [self oo_jsDescriptionWithClassName:[self oo_jsClassName]];
1406 : }
1407 :
1408 :
1409 0 : - (NSString *) oo_jsDescriptionWithClassName:(NSString *)className
1410 : {
1411 : OOJS_PROFILE_ENTER
1412 :
1413 : NSString *components = nil;
1414 : NSString *description = nil;
1415 :
1416 : components = [self descriptionComponents];
1417 : if (className == nil) className = [[self class] description];
1418 :
1419 : if (components != nil)
1420 : {
1421 : description = [NSString stringWithFormat:@"[%@ %@]", className, components];
1422 : }
1423 : else
1424 : {
1425 : description = [NSString stringWithFormat:@"[object %@]", className];
1426 : }
1427 :
1428 : return description;
1429 :
1430 : OOJS_PROFILE_EXIT
1431 : }
1432 :
1433 :
1434 0 : - (void) oo_clearJSSelf:(JSObject *)selfVal
1435 : {
1436 :
1437 : }
1438 :
1439 : @end
1440 :
1441 :
1442 0 : JSObject *OOJSObjectFromNativeObject(JSContext *context, id object)
1443 : {
1444 : jsval value = OOJSValueFromNativeObject(context, object);
1445 : JSObject *result = NULL;
1446 : if (JS_ValueToObject(context, value, &result)) return result;
1447 : return NULL;
1448 : }
1449 :
1450 :
1451 : @implementation OOJSValue
1452 :
1453 : + (id) valueWithJSValue:(jsval)value inContext:(JSContext *)context
1454 : {
1455 : OOJS_PROFILE_ENTER
1456 :
1457 : return [[[self alloc] initWithJSValue:value inContext:context] autorelease];
1458 :
1459 : OOJS_PROFILE_EXIT
1460 : }
1461 :
1462 :
1463 : + (id) valueWithJSObject:(JSObject *)object inContext:(JSContext *)context
1464 : {
1465 : OOJS_PROFILE_ENTER
1466 :
1467 : return [[[self alloc] initWithJSObject:object inContext:context] autorelease];
1468 :
1469 : OOJS_PROFILE_EXIT
1470 : }
1471 :
1472 :
1473 : - (id) initWithJSValue:(jsval)value inContext:(JSContext *)context
1474 : {
1475 : OOJS_PROFILE_ENTER
1476 :
1477 : self = [super init];
1478 : if (self != nil)
1479 : {
1480 : BOOL tempCtxt = NO;
1481 : if (context == NULL)
1482 : {
1483 : context = OOJSAcquireContext();
1484 : tempCtxt = YES;
1485 : }
1486 :
1487 : _val = value;
1488 : if (!JSVAL_IS_VOID(_val))
1489 : {
1490 : JS_AddNamedValueRoot(context, &_val, "OOJSValue");
1491 :
1492 : [[NSNotificationCenter defaultCenter] addObserver:self
1493 : selector:@selector(deleteJSValue)
1494 : name:kOOJavaScriptEngineWillResetNotification
1495 : object:[OOJavaScriptEngine sharedEngine]];
1496 : }
1497 :
1498 : if (tempCtxt) OOJSRelinquishContext(context);
1499 : }
1500 : return self;
1501 :
1502 : OOJS_PROFILE_EXIT
1503 : }
1504 :
1505 :
1506 : - (id) initWithJSObject:(JSObject *)object inContext:(JSContext *)context
1507 : {
1508 : return [self initWithJSValue:OBJECT_TO_JSVAL(object) inContext:context];
1509 : }
1510 :
1511 :
1512 0 : - (void) deleteJSValue
1513 : {
1514 : if (!JSVAL_IS_VOID(_val))
1515 : {
1516 : JSContext *context = OOJSAcquireContext();
1517 : JS_RemoveValueRoot(context, &_val);
1518 : OOJSRelinquishContext(context);
1519 :
1520 : _val = JSVAL_VOID;
1521 : [[NSNotificationCenter defaultCenter] removeObserver:self
1522 : name:kOOJavaScriptEngineWillResetNotification
1523 : object:[OOJavaScriptEngine sharedEngine]];
1524 : }
1525 : }
1526 :
1527 :
1528 0 : - (void) dealloc
1529 : {
1530 : [self deleteJSValue];
1531 : [super dealloc];
1532 : }
1533 :
1534 :
1535 0 : - (jsval) oo_jsValueInContext:(JSContext *)context
1536 : {
1537 : return _val;
1538 : }
1539 :
1540 : @end
1541 :
1542 :
1543 0 : void OOJSStrLiteralCachePRIVATE(const char *string, jsval *strCache, BOOL *inited)
1544 : {
1545 : NSCParameterAssert(string != NULL && strCache != NULL && inited != NULL && !*inited);
1546 :
1547 : JSContext *context = OOJSAcquireContext();
1548 :
1549 : JSString *jsString = JS_InternString(context, string);
1550 : if (EXPECT_NOT(string == NULL))
1551 : {
1552 : [NSException raise:NSGenericException format:@"Failed to initialize JavaScript string literal cache for \"%@\".", [[NSString stringWithUTF8String:string] escapedForJavaScriptLiteral]];
1553 : }
1554 :
1555 : *strCache = STRING_TO_JSVAL(jsString);
1556 : *inited = YES;
1557 :
1558 : OOJSRelinquishContext(context);
1559 : }
1560 :
1561 :
1562 0 : NSString *OOStringFromJSString(JSContext *context, JSString *string)
1563 : {
1564 : OOJS_PROFILE_ENTER
1565 :
1566 : if (EXPECT_NOT(string == NULL)) return nil;
1567 :
1568 : size_t length;
1569 : const jschar *chars = JS_GetStringCharsAndLength(context, string, &length);
1570 :
1571 : if (EXPECT(chars != NULL))
1572 : {
1573 : return [NSString stringWithCharacters:chars length:length];
1574 : }
1575 : else
1576 : {
1577 : return nil;
1578 : }
1579 :
1580 : OOJS_PROFILE_EXIT
1581 : }
1582 :
1583 :
1584 0 : NSString *OOStringFromJSValueEvenIfNull(JSContext *context, jsval value)
1585 : {
1586 : OOJS_PROFILE_ENTER
1587 :
1588 : NSCParameterAssert(context != NULL && JS_IsInRequest(context));
1589 :
1590 : JSString *string = JS_ValueToString(context, value); // Calls the value's toString method if needed.
1591 : return OOStringFromJSString(context, string);
1592 :
1593 : OOJS_PROFILE_EXIT
1594 : }
1595 :
1596 :
1597 0 : NSString *OOStringFromJSValue(JSContext *context, jsval value)
1598 : {
1599 : OOJS_PROFILE_ENTER
1600 :
1601 : if (EXPECT(!JSVAL_IS_NULL(value) && !JSVAL_IS_VOID(value)))
1602 : {
1603 : return OOStringFromJSValueEvenIfNull(context, value);
1604 : }
1605 : return nil;
1606 :
1607 : OOJS_PROFILE_EXIT
1608 : }
1609 :
1610 :
1611 0 : NSString *OOStringFromJSPropertyIDAndSpec(JSContext *context, jsid propID, JSPropertySpec *propertySpec)
1612 : {
1613 : if (JSID_IS_STRING(propID))
1614 : {
1615 : return OOStringFromJSString(context, JSID_TO_STRING(propID));
1616 : }
1617 : else if (JSID_IS_INT(propID) && propertySpec != NULL)
1618 : {
1619 : int tinyid = JSID_TO_INT(propID);
1620 :
1621 : while (propertySpec->name != NULL)
1622 : {
1623 : if (propertySpec->tinyid == tinyid) return [NSString stringWithUTF8String:propertySpec->name];
1624 : propertySpec++;
1625 : }
1626 : }
1627 :
1628 : jsval value;
1629 : if (!JS_IdToValue(context, propID, &value)) return @"unknown";
1630 : return OOStringFromJSString(context, JS_ValueToString(context, value));
1631 : }
1632 :
1633 :
1634 0 : static NSString *DescribeValue(JSContext *context, jsval value, BOOL abbreviateObjects, BOOL recursing)
1635 : {
1636 : OOJS_PROFILE_ENTER
1637 :
1638 : NSCParameterAssert(context != NULL && JS_IsInRequest(context));
1639 :
1640 : if (OOJSValueIsFunction(context, value))
1641 : {
1642 : JSString *name = JS_GetFunctionId(JS_ValueToFunction(context, value));
1643 : if (name != NULL) return [NSString stringWithFormat:@"function %@", OOStringFromJSString(context, name)];
1644 : else return @"function";
1645 : }
1646 :
1647 : NSString *result = nil;
1648 : JSClass *class = NULL;
1649 : OOJavaScriptEngine *jsEng = [OOJavaScriptEngine sharedEngine];
1650 :
1651 : if (JSVAL_IS_OBJECT(value) && !JSVAL_IS_NULL(value))
1652 : {
1653 : class = OOJSGetClass(context, JSVAL_TO_OBJECT(value));
1654 : }
1655 :
1656 : // Convert String objects to strings.
1657 : if (class == [jsEng stringClass])
1658 : {
1659 : value = STRING_TO_JSVAL(JS_ValueToString(context, value));
1660 : }
1661 :
1662 : if (JSVAL_IS_STRING(value))
1663 : {
1664 : enum { kMaxLength = 200 };
1665 :
1666 : JSString *string = JSVAL_TO_STRING(value);
1667 : size_t length;
1668 : const jschar *chars = JS_GetStringCharsAndLength(context, string, &length);
1669 :
1670 : result = [NSString stringWithCharacters:chars length:MIN(length, (size_t)kMaxLength)];
1671 : result = [NSString stringWithFormat:@"\"%@%@\"", [result escapedForJavaScriptLiteral], (length > kMaxLength) ? @"..." : @""];
1672 : }
1673 : else if (class == [jsEng arrayClass])
1674 : {
1675 : // Descibe up to four elements of an array.
1676 : jsuint count;
1677 : JSObject *obj = JSVAL_TO_OBJECT(value);
1678 : if (JS_GetArrayLength(context, obj, &count))
1679 : {
1680 : if (!recursing)
1681 : {
1682 : NSMutableString *arrayDesc = [NSMutableString stringWithString:@"["];
1683 : jsuint i, effectiveCount = MIN(count, (jsuint)4);
1684 : for (i = 0; i < effectiveCount; i++)
1685 : {
1686 : jsval item;
1687 : NSString *itemDesc = @"?";
1688 : if (JS_GetElement(context, obj, i, &item))
1689 : {
1690 : itemDesc = DescribeValue(context, item, YES /* always abbreviate objects in arrays */, YES);
1691 : }
1692 : if (i != 0) [arrayDesc appendString:@", "];
1693 : [arrayDesc appendString:itemDesc];
1694 : }
1695 : if (effectiveCount != count)
1696 : {
1697 : [arrayDesc appendFormat:@", ... <%u items total>]", count];
1698 : }
1699 : else
1700 : {
1701 : [arrayDesc appendString:@"]"];
1702 : }
1703 :
1704 : result = arrayDesc;
1705 : }
1706 : else
1707 : {
1708 : result = [NSString stringWithFormat:@"[<%u items>]", count];
1709 : }
1710 : }
1711 : else
1712 : {
1713 : result = @"[...]";
1714 : }
1715 :
1716 : }
1717 :
1718 : if (result == nil)
1719 : {
1720 : result = OOStringFromJSValueEvenIfNull(context, value);
1721 :
1722 : if (abbreviateObjects && class == [jsEng objectClass] && [result isEqualToString:@"[object Object]"])
1723 : {
1724 : result = @"{...}";
1725 : }
1726 :
1727 : if (result == nil) result = @"?";
1728 : }
1729 :
1730 : return result;
1731 :
1732 : OOJS_PROFILE_EXIT
1733 : }
1734 :
1735 :
1736 0 : NSString *OOJSDescribeValue(JSContext *context, jsval value, BOOL abbreviateObjects)
1737 : {
1738 : return DescribeValue(context, value, abbreviateObjects, NO);
1739 : }
1740 :
1741 :
1742 : @implementation NSString (OOJavaScriptExtensions)
1743 :
1744 : + (NSString *) stringWithJavaScriptParameters:(jsval *)params count:(uintN)count inContext:(JSContext *)context
1745 : {
1746 : OOJS_PROFILE_ENTER
1747 :
1748 : if (params == NULL && count != 0) return nil;
1749 :
1750 : uintN i;
1751 : NSMutableString *result = [NSMutableString stringWithString:@"("];
1752 :
1753 : for (i = 0; i < count; ++i)
1754 : {
1755 : if (i != 0) [result appendString:@", "];
1756 : [result appendString:OOJSDescribeValue(context, params[i], NO)];
1757 : }
1758 :
1759 : [result appendString:@")"];
1760 : return result;
1761 :
1762 : OOJS_PROFILE_EXIT
1763 : }
1764 :
1765 :
1766 0 : - (jsval) oo_jsValueInContext:(JSContext *)context
1767 : {
1768 : OOJS_PROFILE_ENTER
1769 :
1770 : size_t length = [self length];
1771 : unichar *buffer = NULL;
1772 : JSString *string = NULL;
1773 :
1774 : if (length == 0)
1775 : {
1776 : jsval result = JS_GetEmptyStringValue(context);
1777 : return result;
1778 : }
1779 : else
1780 : {
1781 : buffer = malloc(length * sizeof *buffer);
1782 : if (buffer == NULL) return JSVAL_VOID;
1783 :
1784 : [self getCharacters:buffer];
1785 :
1786 : string = JS_NewUCStringCopyN(context, buffer, length);
1787 :
1788 : free(buffer);
1789 : return STRING_TO_JSVAL(string);
1790 : }
1791 :
1792 : OOJS_PROFILE_EXIT_JSVAL
1793 : }
1794 :
1795 :
1796 : + (NSString *) concatenationOfStringsFromJavaScriptValues:(jsval *)values count:(size_t)count separator:(NSString *)separator inContext:(JSContext *)context
1797 : {
1798 : OOJS_PROFILE_ENTER
1799 :
1800 : size_t i;
1801 : NSMutableString *result = nil;
1802 : NSString *element = nil;
1803 :
1804 : if (count < 1) return nil;
1805 : if (values == NULL) return NULL;
1806 :
1807 : for (i = 0; i != count; ++i)
1808 : {
1809 : element = OOStringFromJSValueEvenIfNull(context, values[i]);
1810 : if (result == nil) result = [[element mutableCopy] autorelease];
1811 : else
1812 : {
1813 : if (separator != nil) [result appendString:separator];
1814 : [result appendString:element];
1815 : }
1816 : }
1817 :
1818 : return result;
1819 :
1820 : OOJS_PROFILE_EXIT
1821 : }
1822 :
1823 :
1824 : - (NSString *)escapedForJavaScriptLiteral
1825 : {
1826 : OOJS_PROFILE_ENTER
1827 :
1828 : NSMutableString *result = nil;
1829 : NSUInteger i, length;
1830 : unichar c;
1831 : NSAutoreleasePool *pool = nil;
1832 :
1833 : length = [self length];
1834 : result = [NSMutableString stringWithCapacity:length];
1835 :
1836 : // Not hugely efficient.
1837 : pool = [[NSAutoreleasePool alloc] init];
1838 : for (i = 0; i != length; ++i)
1839 : {
1840 : c = [self characterAtIndex:i];
1841 : switch (c)
1842 : {
1843 : case '\\':
1844 : [result appendString:@"\\\\"];
1845 : break;
1846 :
1847 : case '\b':
1848 : [result appendString:@"\\b"];
1849 : break;
1850 :
1851 : case '\f':
1852 : [result appendString:@"\\f"];
1853 : break;
1854 :
1855 : case '\n':
1856 : [result appendString:@"\\n"];
1857 : break;
1858 :
1859 : case '\r':
1860 : [result appendString:@"\\r"];
1861 : break;
1862 :
1863 : case '\t':
1864 : [result appendString:@"\\t"];
1865 : break;
1866 :
1867 : case '\v':
1868 : [result appendString:@"\\v"];
1869 : break;
1870 :
1871 : case '\'':
1872 : [result appendString:@"\\\'"];
1873 : break;
1874 :
1875 : case '\"':
1876 : [result appendString:@"\\\""];
1877 : break;
1878 :
1879 : default:
1880 : [result appendString:[NSString stringWithCharacters:&c length:1]];
1881 : }
1882 : }
1883 : [pool release];
1884 : return result;
1885 :
1886 : OOJS_PROFILE_EXIT
1887 : }
1888 :
1889 :
1890 0 : - (NSString *) oo_jsClassName
1891 : {
1892 : return @"String";
1893 : }
1894 :
1895 : @end
1896 :
1897 :
1898 : @implementation NSArray (OOJavaScriptConversion)
1899 :
1900 0 : - (jsval)oo_jsValueInContext:(JSContext *)context
1901 : {
1902 : jsval value = JSVAL_VOID;
1903 : JSNewNSArrayValue(context, self, &value);
1904 : return value;
1905 : }
1906 :
1907 : @end
1908 :
1909 : @implementation OONativeVector (OOJavaScriptConversion)
1910 :
1911 0 : - (jsval)oo_jsValueInContext:(JSContext *)context
1912 : {
1913 : jsval value = JSVAL_VOID;
1914 : VectorToJSValue(context, v, &value);
1915 : return value;
1916 : }
1917 :
1918 : @end
1919 :
1920 :
1921 : @implementation NSDictionary (OOJavaScriptConversion)
1922 :
1923 0 : - (jsval)oo_jsValueInContext:(JSContext *)context
1924 : {
1925 : jsval value = JSVAL_VOID;
1926 : JSNewNSDictionaryValue(context, self, &value);
1927 : return value;
1928 : }
1929 :
1930 : @end
1931 :
1932 :
1933 : @implementation NSNumber (OOJavaScriptConversion)
1934 :
1935 0 : - (jsval)oo_jsValueInContext:(JSContext *)context
1936 : {
1937 : OOJS_PROFILE_ENTER
1938 :
1939 : jsval result;
1940 : BOOL isFloat = NO;
1941 : long long longLongValue;
1942 :
1943 : isFloat = [self oo_isFloatingPointNumber];
1944 : if (!isFloat)
1945 : {
1946 : longLongValue = [self longLongValue];
1947 : if (longLongValue < (long long)JSVAL_INT_MIN || (long long)JSVAL_INT_MAX < longLongValue)
1948 : {
1949 : // values outside JSVAL_INT range are returned as doubles.
1950 : isFloat = YES;
1951 : }
1952 : }
1953 :
1954 : if (isFloat)
1955 : {
1956 : if (!JS_NewNumberValue(context, [self doubleValue], &result)) result = JSVAL_VOID;
1957 : }
1958 : else
1959 : {
1960 : result = INT_TO_JSVAL((int32_t)longLongValue);
1961 : }
1962 :
1963 : return result;
1964 :
1965 : OOJS_PROFILE_EXIT_JSVAL
1966 : }
1967 :
1968 :
1969 0 : - (NSString *) oo_jsClassName
1970 : {
1971 : return @"Number";
1972 : }
1973 :
1974 : @end
1975 :
1976 :
1977 : @implementation NSNull (OOJavaScriptConversion)
1978 :
1979 0 : - (jsval)oo_jsValueInContext:(JSContext *)context
1980 : {
1981 : return JSVAL_NULL;
1982 : }
1983 :
1984 : @end
1985 :
1986 :
1987 0 : JSBool OOJSUnconstructableConstruct(JSContext *context, uintN argc, jsval *vp)
1988 : {
1989 : OOJS_NATIVE_ENTER(context)
1990 :
1991 : JSFunction *function = JS_ValueToFunction(context, JS_CALLEE(context, vp));
1992 : NSString *name = OOStringFromJSString(context, JS_GetFunctionId(function));
1993 :
1994 : OOJSReportError(context, @"%@ cannot be used as a constructor.", name);
1995 : return NO;
1996 :
1997 : OOJS_NATIVE_EXIT
1998 : }
1999 :
2000 :
2001 0 : void OOJSObjectWrapperFinalize(JSContext *context, JSObject *this)
2002 : {
2003 : OOJS_PROFILE_ENTER
2004 :
2005 : id object = JS_GetPrivate(context, this);
2006 : if (object != nil)
2007 : {
2008 : [[object weakRefUnderlyingObject] oo_clearJSSelf:this];
2009 : [object release];
2010 : JS_SetPrivate(context, this, nil);
2011 : }
2012 :
2013 : OOJS_PROFILE_EXIT_VOID
2014 : }
2015 :
2016 :
2017 0 : JSBool OOJSObjectWrapperToString(JSContext *context, uintN argc, jsval *vp)
2018 : {
2019 : OOJS_NATIVE_ENTER(context)
2020 :
2021 : id object = nil;
2022 : NSString *description = nil;
2023 : JSClass *jsClass = NULL;
2024 :
2025 : object = OOJSNativeObjectFromJSObject(context, OOJS_THIS);
2026 : if (object != nil)
2027 : {
2028 : description = [object oo_jsDescription];
2029 : if (description == nil) description = [object description];
2030 : }
2031 : if (description == nil)
2032 : {
2033 : jsClass = OOJSGetClass(context, OOJS_THIS);
2034 : if (jsClass != NULL)
2035 : {
2036 : description = [NSString stringWithFormat:@"[object %@]", [NSString stringWithUTF8String:jsClass->name]];
2037 : }
2038 : }
2039 : if (description == nil) description = @"[object]";
2040 :
2041 : OOJS_RETURN_OBJECT(description);
2042 :
2043 : OOJS_NATIVE_EXIT
2044 : }
2045 :
2046 :
2047 0 : BOOL JSFunctionPredicate(Entity *entity, void *parameter)
2048 : {
2049 : OOJS_PROFILE_ENTER
2050 :
2051 : JSFunctionPredicateParameter *param = parameter;
2052 : jsval args[1];
2053 : jsval rval = JSVAL_VOID;
2054 : JSBool result = NO;
2055 :
2056 : NSCParameterAssert(entity != nil && param != NULL);
2057 : NSCParameterAssert(param->context != NULL && JS_IsInRequest(param->context));
2058 : NSCParameterAssert(OOJSValueIsFunction(param->context, param->function));
2059 :
2060 : if (EXPECT_NOT(param->errorFlag)) return NO;
2061 :
2062 : args[0] = [entity oo_jsValueInContext:param->context]; // entity is required to be non-nil (asserted above), so oo_jsValueInContext: is safe.
2063 :
2064 : OOJSStartTimeLimiter();
2065 : OOJSResumeTimeLimiter();
2066 : BOOL success = JS_CallFunctionValue(param->context, param->jsThis, param->function, 1, args, &rval);
2067 : OOJSPauseTimeLimiter();
2068 : OOJSStopTimeLimiter();
2069 :
2070 : if (success)
2071 : {
2072 : if (!JS_ValueToBoolean(param->context, rval, &result)) result = NO;
2073 : if (JS_IsExceptionPending(param->context))
2074 : {
2075 : JS_ReportPendingException(param->context);
2076 : param->errorFlag = YES;
2077 : }
2078 : }
2079 : else
2080 : {
2081 : param->errorFlag = YES;
2082 : }
2083 :
2084 : return result;
2085 :
2086 : OOJS_PROFILE_EXIT
2087 : }
2088 :
2089 :
2090 0 : BOOL JSEntityIsJavaScriptVisiblePredicate(Entity *entity, void *parameter)
2091 : {
2092 : OOJS_PROFILE_ENTER
2093 :
2094 : return [entity isVisibleToScripts];
2095 :
2096 : OOJS_PROFILE_EXIT
2097 : }
2098 :
2099 :
2100 0 : BOOL JSEntityIsJavaScriptSearchablePredicate(Entity *entity, void *parameter)
2101 : {
2102 : OOJS_PROFILE_ENTER
2103 :
2104 : if (![entity isVisibleToScripts]) return NO;
2105 : if ([entity isShip])
2106 : {
2107 : if ([entity isSubEntity]) return NO;
2108 : if ([entity status] == STATUS_COCKPIT_DISPLAY) return NO; // Demo ship
2109 : return YES;
2110 : }
2111 : else if ([entity isPlanet])
2112 : {
2113 : switch ([(OOPlanetEntity *)entity planetType])
2114 : {
2115 : case STELLAR_TYPE_MOON:
2116 : case STELLAR_TYPE_NORMAL_PLANET:
2117 : case STELLAR_TYPE_SUN:
2118 : return YES;
2119 :
2120 : #if !NEW_PLANETS
2121 : case STELLAR_TYPE_ATMOSPHERE:
2122 : #endif
2123 : case STELLAR_TYPE_MINIATURE:
2124 : return NO;
2125 : }
2126 : }
2127 :
2128 : return YES; // would happen if we added a new script-visible class
2129 :
2130 : OOJS_PROFILE_EXIT
2131 : }
2132 :
2133 :
2134 0 : BOOL JSEntityIsDemoShipPredicate(Entity *entity, void *parameter)
2135 : {
2136 : return ([entity isVisibleToScripts] && [entity isShip] && [entity status] == STATUS_COCKPIT_DISPLAY && ![entity isSubEntity]);
2137 : }
2138 :
2139 0 : static NSMapTable *sRegisteredSubClasses;
2140 :
2141 0 : void OOJSRegisterSubclass(JSClass *subclass, JSClass *superclass)
2142 : {
2143 : NSCParameterAssert(subclass != NULL && superclass != NULL);
2144 :
2145 : if (sRegisteredSubClasses == NULL)
2146 : {
2147 : sRegisteredSubClasses = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0);
2148 : }
2149 :
2150 : NSCAssert(NSMapGet(sRegisteredSubClasses, subclass) == NULL, @"A JS class cannot be registered as a subclass of multiple classes.");
2151 :
2152 : NSMapInsertKnownAbsent(sRegisteredSubClasses, subclass, superclass);
2153 : }
2154 :
2155 :
2156 0 : static void UnregisterSubclasses(void)
2157 : {
2158 : NSFreeMapTable(sRegisteredSubClasses);
2159 : sRegisteredSubClasses = NULL;
2160 : }
2161 :
2162 :
2163 0 : BOOL OOJSIsSubclass(JSClass *putativeSubclass, JSClass *superclass)
2164 : {
2165 : NSCParameterAssert(putativeSubclass != NULL && superclass != NULL);
2166 : NSCAssert(sRegisteredSubClasses != NULL, @"OOJSIsSubclass() called before any subclasses registered (disallowed for hot path efficiency).");
2167 :
2168 : do
2169 : {
2170 : if (putativeSubclass == superclass) return YES;
2171 :
2172 : putativeSubclass = NSMapGet(sRegisteredSubClasses, putativeSubclass);
2173 : }
2174 : while (putativeSubclass != NULL);
2175 :
2176 : return NO;
2177 : }
2178 :
2179 :
2180 0 : BOOL OOJSObjectGetterImplPRIVATE(JSContext *context, JSObject *object, JSClass *requiredJSClass,
2181 : #ifndef NDEBUG
2182 : Class requiredObjCClass, const char *name,
2183 : #endif
2184 : id *outObject)
2185 : {
2186 : #ifndef NDEBUG
2187 : OOJS_PROFILE_ENTER_NAMED(name)
2188 : NSCParameterAssert(requiredObjCClass != Nil);
2189 : NSCParameterAssert(context != NULL && object != NULL && requiredJSClass != NULL && outObject != NULL);
2190 : #else
2191 : OOJS_PROFILE_ENTER
2192 : #endif
2193 :
2194 : /*
2195 : Ensure it's a valid type of JS object. This is absolutely necessary,
2196 : because if we don't check it we'll crash trying to get the private
2197 : field of something that isn't an ObjC object wrapper - for example,
2198 : Ship.setAI.call(new Vector3D, "") is valid JavaScript.
2199 :
2200 : Alternatively, we could abuse JSCLASS_PRIVATE_IS_NSISUPPORTS as a
2201 : flag for ObjC object wrappers (SpiderMonkey only uses it internally
2202 : in a debug function we don't use), but we'd still need to do an
2203 : Objective-C class test, and I don't think that's any faster.
2204 : TODO: profile.
2205 : */
2206 : JSClass *actualClass = OOJSGetClass(context, object);
2207 : if (EXPECT_NOT(!OOJSIsSubclass(actualClass, requiredJSClass)))
2208 : {
2209 : OOJSReportError(context, @"Native method expected %s, got %@.", requiredJSClass->name, OOStringFromJSValue(context, OBJECT_TO_JSVAL(object)));
2210 : return NO;
2211 : }
2212 : NSCAssert(actualClass->flags & JSCLASS_HAS_PRIVATE, @"Native object accessor requires JS class with private storage.");
2213 :
2214 : // Get the underlying object.
2215 : *outObject = [(id)JS_GetPrivate(context, object) weakRefUnderlyingObject];
2216 :
2217 : #ifndef NDEBUG
2218 : // Double-check that the underlying object is of the expected ObjC class.
2219 : if (EXPECT_NOT(*outObject != nil && ![*outObject isKindOfClass:requiredObjCClass]))
2220 : {
2221 : OOJSReportError(context, @"Native method expected %@ from %s and got correct JS type but incorrect native object %@", requiredObjCClass, requiredJSClass->name, *outObject);
2222 : return NO;
2223 : }
2224 : #endif
2225 :
2226 : return YES;
2227 :
2228 : OOJS_PROFILE_EXIT
2229 : }
2230 :
2231 :
2232 0 : NSDictionary *OOJSDictionaryFromJSValue(JSContext *context, jsval value)
2233 : {
2234 : OOJS_PROFILE_ENTER
2235 :
2236 : JSObject *object = NULL;
2237 : if (EXPECT_NOT(!JS_ValueToObject(context, value, &object) || object == NULL))
2238 : {
2239 : return nil;
2240 : }
2241 : return OOJSDictionaryFromJSObject(context, object);
2242 :
2243 : OOJS_PROFILE_EXIT
2244 : }
2245 :
2246 :
2247 0 : NSDictionary *OOJSDictionaryFromJSObject(JSContext *context, JSObject *object)
2248 : {
2249 : OOJS_PROFILE_ENTER
2250 :
2251 : JSIdArray *ids = NULL;
2252 : jsint i;
2253 : NSMutableDictionary *result = nil;
2254 : jsval value = JSVAL_VOID;
2255 : id objKey = nil;
2256 : id objValue = nil;
2257 :
2258 : ids = JS_Enumerate(context, object);
2259 : if (EXPECT_NOT(ids == NULL))
2260 : {
2261 : return nil;
2262 : }
2263 :
2264 : result = [NSMutableDictionary dictionaryWithCapacity:ids->length];
2265 : for (i = 0; i != ids->length; ++i)
2266 : {
2267 : jsid thisID = ids->vector[i];
2268 :
2269 : if (JSID_IS_STRING(thisID))
2270 : {
2271 : objKey = OOStringFromJSString(context, JSID_TO_STRING(thisID));
2272 : }
2273 : else if (JSID_IS_INT(thisID))
2274 : {
2275 : /* this causes problems with native functions which expect string keys
2276 : * e.g. in mission.runScreen with the 'choices' parameter
2277 : * should this instead be making the objKey a string?
2278 : * is there anything that relies on the current behaviour?
2279 : * - CIM 15/2/13 */
2280 : objKey = [NSNumber numberWithInt:JSID_TO_INT(thisID)];
2281 : }
2282 : else
2283 : {
2284 : objKey = nil;
2285 : }
2286 :
2287 : value = JSVAL_VOID;
2288 : if (objKey != nil && !JS_LookupPropertyById(context, object, thisID, &value)) value = JSVAL_VOID;
2289 :
2290 : if (objKey != nil && !JSVAL_IS_VOID(value))
2291 : {
2292 : objValue = OOJSNativeObjectFromJSValue(context, value);
2293 : if (objValue != nil)
2294 : {
2295 : [result setObject:objValue forKey:objKey];
2296 : }
2297 : }
2298 : }
2299 :
2300 : JS_DestroyIdArray(context, ids);
2301 : return result;
2302 :
2303 : OOJS_PROFILE_EXIT
2304 : }
2305 :
2306 :
2307 0 : NSDictionary *OOJSDictionaryFromStringTable(JSContext *context, jsval tableValue)
2308 : {
2309 : OOJS_PROFILE_ENTER
2310 :
2311 : JSObject *tableObject = NULL;
2312 : JSIdArray *ids;
2313 : jsint i;
2314 : NSMutableDictionary *result = nil;
2315 : jsval value = JSVAL_VOID;
2316 : id objKey = nil;
2317 : id objValue = nil;
2318 :
2319 : if (EXPECT_NOT(JSVAL_IS_NULL(tableValue) || !JS_ValueToObject(context, tableValue, &tableObject)))
2320 : {
2321 : return nil;
2322 : }
2323 :
2324 : ids = JS_Enumerate(context, tableObject);
2325 : if (EXPECT_NOT(ids == NULL))
2326 : {
2327 : return nil;
2328 : }
2329 :
2330 : result = [NSMutableDictionary dictionaryWithCapacity:ids->length];
2331 : for (i = 0; i != ids->length; ++i)
2332 : {
2333 : jsid thisID = ids->vector[i];
2334 :
2335 : if (JSID_IS_STRING(thisID))
2336 : {
2337 : objKey = OOStringFromJSString(context, JSID_TO_STRING(thisID));
2338 : }
2339 : else
2340 : {
2341 : objKey = nil;
2342 : }
2343 :
2344 : value = JSVAL_VOID;
2345 : if (objKey != nil && !JS_LookupPropertyById(context, tableObject, thisID, &value)) value = JSVAL_VOID;
2346 :
2347 : if (objKey != nil && !JSVAL_IS_VOID(value))
2348 : {
2349 : objValue = OOStringFromJSValueEvenIfNull(context, value);
2350 :
2351 : if (objValue != nil)
2352 : {
2353 : [result setObject:objValue forKey:objKey];
2354 : }
2355 : }
2356 : }
2357 :
2358 : JS_DestroyIdArray(context, ids);
2359 : return result;
2360 :
2361 : OOJS_PROFILE_EXIT
2362 : }
2363 :
2364 :
2365 0 : static NSMutableDictionary *sObjectConverters;
2366 :
2367 :
2368 0 : id OOJSNativeObjectFromJSValue(JSContext *context, jsval value)
2369 : {
2370 : OOJS_PROFILE_ENTER
2371 :
2372 : if (JSVAL_IS_NULL(value) || JSVAL_IS_VOID(value)) return nil;
2373 :
2374 : if (JSVAL_IS_INT(value))
2375 : {
2376 : return [NSNumber numberWithInt:JSVAL_TO_INT(value)];
2377 : }
2378 : if (JSVAL_IS_DOUBLE(value))
2379 : {
2380 : return [NSNumber numberWithDouble:JSVAL_TO_DOUBLE(value)];
2381 : }
2382 : if (JSVAL_IS_BOOLEAN(value))
2383 : {
2384 : return [NSNumber numberWithBool:JSVAL_TO_BOOLEAN(value)];
2385 : }
2386 : if (JSVAL_IS_STRING(value))
2387 : {
2388 : return OOStringFromJSValue(context, value);
2389 : }
2390 : if (JSVAL_IS_OBJECT(value))
2391 : {
2392 : return OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(value));
2393 : }
2394 : return nil;
2395 :
2396 : OOJS_PROFILE_EXIT
2397 : }
2398 :
2399 :
2400 0 : id OOJSNativeObjectFromJSObject(JSContext *context, JSObject *tableObject)
2401 : {
2402 : OOJS_PROFILE_ENTER
2403 :
2404 : NSValue *wrappedClass = nil;
2405 : NSValue *wrappedConverter = nil;
2406 : OOJSClassConverterCallback converter = NULL;
2407 : JSClass *class = NULL;
2408 :
2409 : if (tableObject == NULL) return nil;
2410 :
2411 : class = OOJSGetClass(context, tableObject);
2412 : wrappedClass = [NSValue valueWithPointer:class];
2413 : if (wrappedClass != nil) wrappedConverter = [sObjectConverters objectForKey:wrappedClass];
2414 : if (wrappedConverter != nil)
2415 : {
2416 : converter = [wrappedConverter pointerValue];
2417 : return converter(context, tableObject);
2418 : }
2419 : return nil;
2420 :
2421 : OOJS_PROFILE_EXIT
2422 : }
2423 :
2424 :
2425 0 : id OOJSNativeObjectOfClassFromJSValue(JSContext *context, jsval value, Class requiredClass)
2426 : {
2427 : id result = OOJSNativeObjectFromJSValue(context, value);
2428 : if (![result isKindOfClass:requiredClass]) result = nil;
2429 : return result;
2430 : }
2431 :
2432 :
2433 0 : id OOJSNativeObjectOfClassFromJSObject(JSContext *context, JSObject *object, Class requiredClass)
2434 : {
2435 : id result = OOJSNativeObjectFromJSObject(context, object);
2436 : if (![result isKindOfClass:requiredClass]) result = nil;
2437 : return result;
2438 : }
2439 :
2440 :
2441 0 : id OOJSBasicPrivateObjectConverter(JSContext *context, JSObject *object)
2442 : {
2443 : id result;
2444 :
2445 : /* This will do the right thing - for non-OOWeakReferences,
2446 : weakRefUnderlyingObject returns the object itself. For nil, of course,
2447 : it returns nil.
2448 : */
2449 : result = JS_GetPrivate(context, object);
2450 : return [result weakRefUnderlyingObject];
2451 : }
2452 :
2453 :
2454 0 : void OOJSRegisterObjectConverter(JSClass *theClass, OOJSClassConverterCallback converter)
2455 : {
2456 : NSValue *wrappedClass = nil;
2457 : NSValue *wrappedConverter = nil;
2458 :
2459 : if (theClass == NULL) return;
2460 : if (sObjectConverters == nil) sObjectConverters = [[NSMutableDictionary alloc] init];
2461 :
2462 : wrappedClass = [NSValue valueWithPointer:theClass];
2463 : if (converter != NULL)
2464 : {
2465 : wrappedConverter = [NSValue valueWithPointer:converter];
2466 : [sObjectConverters setObject:wrappedConverter forKey:wrappedClass];
2467 : }
2468 : else
2469 : {
2470 : [sObjectConverters removeObjectForKey:wrappedClass];
2471 : }
2472 : }
2473 :
2474 :
2475 0 : static void UnregisterObjectConverters(void)
2476 : {
2477 : DESTROY(sObjectConverters);
2478 : }
2479 :
2480 :
2481 0 : static id JSArrayConverter(JSContext *context, JSObject *array)
2482 : {
2483 : jsuint i, count;
2484 : id *values = NULL;
2485 : jsval value = JSVAL_VOID;
2486 : id object = nil;
2487 : NSArray *result = nil;
2488 :
2489 : // Convert a JS array to an NSArray by calling OOJSNativeObjectFromJSValue() on all its elements.
2490 : if (!JS_IsArrayObject(context, array)) return nil;
2491 : if (!JS_GetArrayLength(context, array, &count)) return nil;
2492 :
2493 : if (count == 0) return [NSArray array];
2494 :
2495 : values = calloc(count, sizeof *values);
2496 : if (values == NULL) return nil;
2497 :
2498 : for (i = 0; i != count; ++i)
2499 : {
2500 : value = JSVAL_VOID;
2501 : if (!JS_GetElement(context, array, i, &value)) value = JSVAL_VOID;
2502 :
2503 : object = OOJSNativeObjectFromJSValue(context, value);
2504 : if (object == nil) object = [NSNull null];
2505 : values[i] = object;
2506 : }
2507 :
2508 : result = [NSArray arrayWithObjects:values count:count];
2509 : free(values);
2510 : return result;
2511 : }
2512 :
2513 :
2514 0 : static id JSStringConverter(JSContext *context, JSObject *object)
2515 : {
2516 : return OOStringFromJSValue(context, OBJECT_TO_JSVAL(object));
2517 : }
2518 :
2519 :
2520 0 : static id JSNumberConverter(JSContext *context, JSObject *object)
2521 : {
2522 : jsdouble value;
2523 : if (JS_ValueToNumber(context, OBJECT_TO_JSVAL(object), &value))
2524 : {
2525 : return [NSNumber numberWithDouble:value];
2526 : }
2527 : return nil;
2528 : }
2529 :
2530 :
2531 0 : static id JSBooleanConverter(JSContext *context, JSObject *object)
2532 : {
2533 : /* Fun With JavaScript: Boolean(false) is a truthy value, since it's a
2534 : non-null object. JS_ValueToBoolean() therefore reports true.
2535 : However, Boolean objects are transformed to numbers sanely, so this
2536 : works.
2537 : */
2538 : jsdouble value;
2539 : if (JS_ValueToNumber(context, OBJECT_TO_JSVAL(object), &value))
2540 : {
2541 : return [NSNumber numberWithBool:(value != 0)];
2542 : }
2543 : return nil;
2544 : }
|