Oolite 1.91.0.7604-240417-a536cbe
Loading...
Searching...
No Matches
OOJavaScriptEngine.m
Go to the documentation of this file.
1/*
2
3OOJavaScriptEngine.m
4
5JavaScript support for Oolite
6Copyright (C) 2007-2013 David Taylor and Jens Ayton.
7
8This program is free software; you can redistribute it and/or
9modify it under the terms of the GNU General Public License
10as published by the Free Software Foundation; either version 2
11of the License, or (at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program; if not, write to the Free Software
20Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21MA 02110-1301, USA.
22
23*/
24
25#include <jsdbgapi.h>
28#import "OOJSScript.h"
29
31#import "Universe.h"
32#import "OOPlanetEntity.h"
34#import "OOWeakReference.h"
36#import "ResourceManager.h"
38#import "OOConstToJSString.h"
40#import "OOWaypointEntity.h"
41
42#import "OOJSGlobal.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"
69#import "OOJSSystemInfo.h"
70#import "OOJSEquipmentInfo.h"
71#import "OOJSShipGroup.h"
73#import "OOJSFont.h"
74
76#import "OOLoggingExtended.h"
77
78#include <stdlib.h>
79
80
81#define OOJSENGINE_JSVERSION JSVERSION_ECMA_5
82#ifdef DEBUG
83#define JIT_OPTIONS 0
84#else
85#define JIT_OPTIONS JSOPTION_JIT | JSOPTION_METHODJIT | JSOPTION_PROFILING
86#endif
87#define OOJSENGINE_CONTEXT_OPTIONS JSOPTION_VAROBJFIX | JSOPTION_RELIMIT | JSOPTION_ANONFUNFIX | JIT_OPTIONS
88
89
90#define OOJS_STACK_SIZE 8192
91#define OOJS_RUNTIME_SIZE_MiB 32
92
93
95static unsigned sErrorHandlerStackSkip = 0;
96
97JSContext *gOOJSMainThreadContext = NULL;
98
99
100NSString * const kOOJavaScriptEngineWillResetNotification = @"org.aegidian.oolite OOJavaScriptEngine will reset";
101NSString * const kOOJavaScriptEngineDidResetNotification = @"org.aegidian.oolite OOJavaScriptEngine did reset";
102
103
104#if OOJSENGINE_MONITOR_SUPPORT
105
106@interface OOJavaScriptEngine (OOMonitorSupportInternal)
107
108- (void)sendMonitorError:(JSErrorReport *)errorReport
109 withMessage:(NSString *)message
110 inContext:(JSContext *)context;
111
112- (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- (BOOL) lookUpStandardClassPointers;
124- (void) registerStandardObjectConverters;
125
126- (void) createMainThreadContext;
127- (void) destroyMainThreadContext;
128
129@end
130
131
132static void ReportJSError(JSContext *context, const char *message, JSErrorReport *report);
133
134static id JSArrayConverter(JSContext *context, JSObject *object);
135static id JSStringConverter(JSContext *context, JSObject *object);
136static id JSNumberConverter(JSContext *context, JSObject *object);
137static id JSBooleanConverter(JSContext *context, JSObject *object);
138
139
140static void UnregisterObjectConverters(void);
141static void UnregisterSubclasses(void);
142
143
144static 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;
153 BOOL showLocation = [jsEng showErrorLocations];
154
155 // Not OOJS_BEGIN_FULL_NATIVE() - we use JSAPI while paused.
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!
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
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{
256}
257
258
259- (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- (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
316
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.
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
346
347 // Initialize Oolite classes.
355 InitOOJSShip(gOOJSMainThreadContext, _globalObject);
357 InitOOJSDock(gOOJSMainThreadContext, _globalObject);
366 InitOOJSSun(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
387
388 OOLog(@"script.javaScript.init.success", @"%@", @"Set up JavaScript context.");
389}
390
391
392- (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
409
410 OOJSRelinquishContext(context);
411
412 _globalObject = NULL;
413 JS_DestroyContext(gOOJSMainThreadContext); // Forces unconditional GC.
415 }
416}
417
418
419- (BOOL) reset
420{
421 NSAssert(gOOJSMainThreadContext != NULL, @"JavaScript engine not active. Can't reset.");
422
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
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- (void) dealloc
471{
473
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
503 result = JS_CallFunctionValue(context, jsThis, function, argc, argv, outResult);
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- (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- (void) registerStandardObjectConverters
612{
618}
619
620
621#ifndef NDEBUG
622static JSTrapStatus DebuggerHook(JSContext *context, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
623{
625
626 OOLog(@"script.javaScript.debugger", @"debugger invoked during %@:", [[OOJSScript currentlyRunningScript] displayName]);
627 OOJSDumpStack(context);
628
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- (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
711static 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
737void 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
866static const char *sConsoleScriptName; // Lifetime is lifetime of script object, which is forever.
867static NSUInteger sConsoleEvalLineNo;
868
869
870static 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
894NSString *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
918void OOJSMarkConsoleEvalLocation(JSContext *context, JSStackFrame *stackFrame)
919{
921}
922#endif
923
924
925void 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
943jsid 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
975NSString *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
992static 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
1000void 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
1010void 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
1031void 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
1050void 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
1063void 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
1072void 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
1082void 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
1103void 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
1120void 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
1129void 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
1139void 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
1157{
1159}
1160
1161
1162BOOL 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
1177BOOL OOJSArgumentListGetNumberNoError(JSContext *context, uintN argc, jsval *argv, double *outNumber, uintN *outConsumed)
1178{
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
1198}
1199
1200
1201static JSObject *JSArrayFromNSArray(JSContext *context, NSArray *array)
1202{
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
1243}
1244
1245
1246static BOOL JSNewNSArrayValue(JSContext *context, NSArray *array, jsval *value)
1247{
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
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*/
1280static JSObject *JSObjectFromNSDictionary(JSContext *context, NSDictionary *dictionary)
1281{
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
1356}
1357
1358
1359static BOOL JSNewNSDictionaryValue(JSContext *context, NSDictionary *dictionary, jsval *value)
1360{
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
1386}
1387
1388
1389@implementation NSObject (OOJavaScriptConversion)
1390
1391- (jsval) oo_jsValueInContext:(JSContext *)context
1392{
1393 return JSVAL_VOID;
1394}
1395
1396
1397- (NSString *) oo_jsClassName
1398{
1399 return nil;
1400}
1401
1402
1403- (NSString *) oo_jsDescription
1404{
1405 return [self oo_jsDescriptionWithClassName:[self oo_jsClassName]];
1406}
1407
1408
1409- (NSString *) oo_jsDescriptionWithClassName:(NSString *)className
1410{
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
1431}
1432
1433
1434- (void) oo_clearJSSelf:(JSObject *)selfVal
1435{
1436
1437}
1438
1439@end
1440
1441
1442JSObject *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{
1456
1457 return [[[self alloc] initWithJSValue:value inContext:context] autorelease];
1458
1460}
1461
1462
1463+ (id) valueWithJSObject:(JSObject *)object inContext:(JSContext *)context
1464{
1466
1467 return [[[self alloc] initWithJSObject:object inContext:context] autorelease];
1468
1470}
1471
1472
1473- (id) initWithJSValue:(jsval)value inContext:(JSContext *)context
1474{
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
1496 }
1497
1498 if (tempCtxt) OOJSRelinquishContext(context);
1499 }
1500 return self;
1501
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- (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
1524 }
1525}
1526
1527
1528- (void) dealloc
1529{
1530 [self deleteJSValue];
1531 [super dealloc];
1532}
1533
1534
1535- (jsval) oo_jsValueInContext:(JSContext *)context
1536{
1537 return _val;
1538}
1539
1540@end
1541
1542
1543void 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
1562NSString *OOStringFromJSString(JSContext *context, JSString *string)
1563{
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
1581}
1582
1583
1584NSString *OOStringFromJSValueEvenIfNull(JSContext *context, jsval value)
1585{
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
1594}
1595
1596
1597NSString *OOStringFromJSValue(JSContext *context, jsval value)
1598{
1600
1601 if (EXPECT(!JSVAL_IS_NULL(value) && !JSVAL_IS_VOID(value)))
1602 {
1603 return OOStringFromJSValueEvenIfNull(context, value);
1604 }
1605 return nil;
1606
1608}
1609
1610
1611NSString *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
1634static NSString *DescribeValue(JSContext *context, jsval value, BOOL abbreviateObjects, BOOL recursing)
1635{
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;
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
1733}
1734
1735
1736NSString *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{
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
1763}
1764
1765
1766- (jsval) oo_jsValueInContext:(JSContext *)context
1767{
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
1793}
1794
1795
1796+ (NSString *) concatenationOfStringsFromJavaScriptValues:(jsval *)values count:(size_t)count separator:(NSString *)separator inContext:(JSContext *)context
1797{
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
1821}
1822
1823
1824- (NSString *)escapedForJavaScriptLiteral
1825{
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
1887}
1888
1889
1890- (NSString *) oo_jsClassName
1891{
1892 return @"String";
1893}
1894
1895@end
1896
1897
1898@implementation NSArray (OOJavaScriptConversion)
1899
1900- (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- (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- (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- (jsval)oo_jsValueInContext:(JSContext *)context
1936{
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
1966}
1967
1968
1969- (NSString *) oo_jsClassName
1970{
1971 return @"Number";
1972}
1973
1974@end
1975
1976
1977@implementation NSNull (OOJavaScriptConversion)
1978
1979- (jsval)oo_jsValueInContext:(JSContext *)context
1980{
1981 return JSVAL_NULL;
1982}
1983
1984@end
1985
1986
1987JSBool 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
1998}
1999
2000
2001void OOJSObjectWrapperFinalize(JSContext *context, JSObject *this)
2002{
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
2014}
2015
2016
2017JSBool 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
2044}
2045
2046
2047BOOL JSFunctionPredicate(Entity *entity, void *parameter)
2048{
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
2066 BOOL success = JS_CallFunctionValue(param->context, param->jsThis, param->function, 1, args, &rval);
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
2087}
2088
2089
2090BOOL JSEntityIsJavaScriptVisiblePredicate(Entity *entity, void *parameter)
2091{
2093
2094 return [entity isVisibleToScripts];
2095
2097}
2098
2099
2101{
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:
2117 case STELLAR_TYPE_SUN:
2118 return YES;
2119
2120#if !NEW_PLANETS
2122#endif
2124 return NO;
2125 }
2126 }
2127
2128 return YES; // would happen if we added a new script-visible class
2129
2131}
2132
2133
2134BOOL JSEntityIsDemoShipPredicate(Entity *entity, void *parameter)
2135{
2136 return ([entity isVisibleToScripts] && [entity isShip] && [entity status] == STATUS_COCKPIT_DISPLAY && ![entity isSubEntity]);
2137}
2138
2139static NSMapTable *sRegisteredSubClasses;
2140
2141void 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
2156static void UnregisterSubclasses(void)
2157{
2158 NSFreeMapTable(sRegisteredSubClasses);
2159 sRegisteredSubClasses = NULL;
2160}
2161
2162
2163BOOL 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
2180BOOL 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
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
2229}
2230
2231
2232NSDictionary *OOJSDictionaryFromJSValue(JSContext *context, jsval value)
2233{
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
2244}
2245
2246
2247NSDictionary *OOJSDictionaryFromJSObject(JSContext *context, JSObject *object)
2248{
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
2304}
2305
2306
2307NSDictionary *OOJSDictionaryFromStringTable(JSContext *context, jsval tableValue)
2308{
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
2362}
2363
2364
2365static NSMutableDictionary *sObjectConverters;
2366
2367
2368id OOJSNativeObjectFromJSValue(JSContext *context, jsval value)
2369{
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
2397}
2398
2399
2400id OOJSNativeObjectFromJSObject(JSContext *context, JSObject *tableObject)
2401{
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
2422}
2423
2424
2425id 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
2433id 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
2441id 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
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
2476{
2478}
2479
2480
2481static 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
2514static id JSStringConverter(JSContext *context, JSObject *object)
2515{
2516 return OOStringFromJSValue(context, OBJECT_TO_JSVAL(object));
2517}
2518
2519
2520static 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
2531static 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}
#define DESTROY(x)
Definition OOCocoa.h:77
void OOConstToJSStringDestroy(void)
void OOConstToJSStringInit(JSContext *context)
#define EXPECT_NOT(x)
#define EXPECT(x)
void InitOOJSClock(JSContext *context, JSObject *global)
Definition OOJSClock.m:109
void InitOOJSDock(JSContext *context, JSObject *global)
Definition OOJSDock.m:94
#define OOJS_PROFILE_EXIT
#define OOJS_PROFILE_EXIT_VOID
#define OOJS_NATIVE_ENTER(cx)
#define OOJS_NATIVE_EXIT
#define OOJS_PROFILE_ENTER
#define OOJS_PROFILE_EXIT_JSVAL
#define OOJSStopTimeLimiter()
#define OOJSStartTimeLimiter()
void OOJSTimeManagementInit(OOJavaScriptEngine *engine, JSRuntime *runtime)
void InitOOJSEntity(JSContext *context, JSObject *global)
Definition OOJSEntity.m:139
void InitOOJSEquipmentInfo(JSContext *context, JSObject *global)
void InitOOJSExhaustPlume(JSContext *context, JSObject *global)
void InitOOJSFlasher(JSContext *context, JSObject *global)
Definition OOJSFlasher.m:96
void InitOOJSFont(JSContext *context, JSObject *global)
Definition OOJSFont.m:38
void InitOOJSFrameCallbacks(JSContext *context, JSObject *global)
void OOJSFrameCallbacksRemoveAll(void)
void CreateOOJSGlobal(JSContext *context, JSObject **outGlobal)
Definition OOJSGlobal.m:166
void SetUpOOJSGlobal(JSContext *context, JSObject *global)
Definition OOJSGlobal.m:177
void InitOOJSManifest(JSContext *context, JSObject *global)
void InitOOJSMissionVariables(JSContext *context, JSObject *global)
void MissionRunCallback(void)
void InitOOJSMission(JSContext *context, JSObject *global)
void InitOOJSOolite(JSContext *context, JSObject *global)
Definition OOJSOolite.m:92
void InitOOJSPlanet(JSContext *context, JSObject *global)
Definition OOJSPlanet.m:99
void InitOOJSPlayerShip(JSContext *context, JSObject *global)
void InitOOJSPlayer(JSContext *context, JSObject *global)
Definition OOJSPlayer.m:163
void InitOOJSQuaternion(JSContext *context, JSObject *global)
void InitOOJSScript(JSContext *context, JSObject *global)
Definition OOJSScript.m:663
void InitOOJSShipGroup(JSContext *context, JSObject *global)
void InitOOJSShip(JSContext *context, JSObject *global)
Definition OOJSShip.m:587
void InitOOJSSoundSource(JSContext *context, JSObject *global)
void InitOOJSSound(JSContext *context, JSObject *global)
Definition OOJSSound.m:104
void InitOOJSSpecialFunctions(JSContext *context, JSObject *global)
void InitOOJSStation(JSContext *context, JSObject *global)
void InitOOJSSun(JSContext *context, JSObject *global)
Definition OOJSSun.m:91
void InitOOJSSystemInfo(JSContext *context, JSObject *global)
void InitOOJSSystem(JSContext *context, JSObject *global)
Definition OOJSSystem.m:227
void InitOOJSTimer(JSContext *context, JSObject *global)
Definition OOJSTimer.m:254
void InitOOJSVector(JSContext *context, JSObject *global)
Definition OOJSVector.m:153
BOOL VectorToJSValue(JSContext *context, Vector vector, jsval *outValue) NONNULL_FUNC
Definition OOJSVector.m:185
void InitOOJSVisualEffect(JSContext *context, JSObject *global)
void InitOOJSWaypoint(JSContext *context, JSObject *global)
void InitOOJSWorldScripts(JSContext *context, JSObject *global)
void InitOOJSWormhole(JSContext *context, JSObject *global)
void OOJSPauseTimeLimiter(void)
id OOJSNativeObjectFromJSValue(JSContext *context, jsval value)
jsid OOJSIDFromString(NSString *string)
BOOL JSEntityIsDemoShipPredicate(Entity *entity, void *parameter)
BOOL OOJSArgumentListGetNumberNoError(JSContext *context, uintN argc, jsval *argv, double *outNumber, uintN *outConsumed)
void OOJSReportWarning(JSContext *context, NSString *format,...)
#define OOJS_THIS
BOOL OOJSArgumentListGetNumber(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, double *outNumber, uintN *outConsumed)
JSBool OOJSObjectWrapperToString(JSContext *context, uintN argc, jsval *vp)
BOOL OOJSObjectGetterImplPRIVATE(JSContext *context, JSObject *object, JSClass *requiredJSClass, Class requiredObjCClass, const char *name, id *outObject)
BOOL JSEntityIsJavaScriptSearchablePredicate(Entity *entity, void *parameter)
void OOJSReportWarningWithArguments(JSContext *context, NSString *format, va_list args)
NSString * OOStringFromJSPropertyIDAndSpec(JSContext *context, jsid propID, JSPropertySpec *propertySpec)
#define JS_IsInRequest(context)
NSString * OOStringFromJSID(jsid propID)
void OOJSRegisterObjectConverter(JSClass *theClass, OOJSClassConverterCallback converter)
void OOJSSetWarningOrErrorStackSkip(unsigned skip)
BOOL JSFunctionPredicate(Entity *entity, void *parameter)
OOINLINE jsval OOJSValueFromNativeObject(JSContext *context, id object)
id OOJSNativeObjectFromJSObject(JSContext *context, JSObject *object)
void OOJSObjectWrapperFinalize(JSContext *context, JSObject *this)
#define OOJS_RETURN_OBJECT(o)
void OOJSReportBadPropertySelector(JSContext *context, JSObject *thisObj, jsid propID, JSPropertySpec *propertySpec)
NSDictionary * OOJSDictionaryFromJSValue(JSContext *context, jsval value)
void OOJSReportWarningForCaller(JSContext *context, NSString *scriptClass, NSString *function, NSString *format,...)
void OOJSReportErrorForCaller(JSContext *context, NSString *scriptClass, NSString *function, NSString *format,...)
NSDictionary * OOJSDictionaryFromJSObject(JSContext *context, JSObject *object)
void OOJSMarkConsoleEvalLocation(JSContext *context, JSStackFrame *stackFrame)
id OOJSNativeObjectOfClassFromJSValue(JSContext *context, jsval value, Class requiredClass)
NSString * OOStringFromJSValue(JSContext *context, jsval value)
JSBool OOJSUnconstructableConstruct(JSContext *context, uintN argc, jsval *vp)
OOINLINE BOOL OOJSValueIsFunction(JSContext *context, jsval value)
void OOJSRegisterSubclass(JSClass *subclass, JSClass *superclass)
JSObject * OOJSObjectFromNativeObject(JSContext *context, id object)
OOINLINE JSContext * OOJSAcquireContext(void)
NSString * OOJSDescribeValue(JSContext *context, jsval value, BOOL abbreviateObjects)
void OOJSReportError(JSContext *context, NSString *format,...)
#define JS_BeginRequest(context)
NSString * OOStringFromJSValueEvenIfNull(JSContext *context, jsval value)
OOINLINE void OOJSRelinquishContext(JSContext *context)
id(* OOJSClassConverterCallback)(JSContext *context, JSObject *object)
void OOJSReportBadPropertyValue(JSContext *context, JSObject *thisObj, jsid propID, JSPropertySpec *propertySpec, jsval value)
id OOJSBasicPrivateObjectConverter(JSContext *context, JSObject *object)
void OOJSReportBadArguments(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, NSString *message, NSString *expectedArgsDescription)
#define JS_EndRequest(context)
NSDictionary * OOJSDictionaryFromStringTable(JSContext *context, jsval value)
void OOJSReportErrorWithArguments(JSContext *context, NSString *format, va_list args)
void OOJSResumeTimeLimiter(void)
BOOL JSEntityIsJavaScriptVisiblePredicate(Entity *entity, void *parameter)
NSString * OOJSDescribeLocation(JSContext *context, JSStackFrame *stackFrame)
BOOL OOJSIsSubclass(JSClass *putativeSubclass, JSClass *superclass)
void OOJSDumpStack(JSContext *context)
NSString * OOStringFromJSString(JSContext *context, JSString *string)
OOINLINE JSClass * OOJSGetClass(JSContext *cx, JSObject *obj) ALWAYS_INLINE_FUNC
void OOJSStrLiteralCachePRIVATE(const char *string, jsval *strCache, BOOL *inited)
id OOJSNativeObjectOfClassFromJSObject(JSContext *context, JSObject *object, Class requiredClass)
static id JSNumberConverter(JSContext *context, JSObject *object)
static NSUInteger sConsoleEvalLineNo
static NSString * CallerPrefix(NSString *scriptClass, NSString *function)
static const char * sConsoleScriptName
static id JSStringConverter(JSContext *context, JSObject *object)
static void DumpVariable(JSContext *context, JSPropertyDesc *prop)
NSString *const kOOJavaScriptEngineWillResetNotification
void OOJSReportWrappedException(JSContext *context, id exception)
NSString *const kOOJavaScriptEngineDidResetNotification
#define OOJSENGINE_JSVERSION
static OOJavaScriptEngine * sSharedEngine
static id JSArrayConverter(JSContext *context, JSObject *object)
static void ReportJSError(JSContext *context, const char *message, JSErrorReport *report)
#define OOJSENGINE_CONTEXT_OPTIONS
void OOJSUnreachable(const char *function, const char *file, unsigned line)
static NSMapTable * sRegisteredSubClasses
static NSString * DescribeValue(JSContext *context, jsval value, BOOL abbreviateObjects, BOOL recursing)
static void UnregisterSubclasses(void)
static id JSBooleanConverter(JSContext *context, JSObject *object)
static JSObject * JSArrayFromNSArray(JSContext *context, NSArray *array)
static JSObject * JSObjectFromNSDictionary(JSContext *context, NSDictionary *dictionary)
static void GetLocationNameAndLine(JSContext *context, JSStackFrame *stackFrame, const char **name, NSUInteger *line)
JSContext * gOOJSMainThreadContext
static NSMutableDictionary * sObjectConverters
#define OOJS_STACK_SIZE
static BOOL JSNewNSArrayValue(JSContext *context, NSArray *array, jsval *value)
static unsigned sErrorHandlerStackSkip
static BOOL JSNewNSDictionaryValue(JSContext *context, NSDictionary *dictionary, jsval *value)
static void UnregisterObjectConverters(void)
void OOJSInitJSIDCachePRIVATE(const char *name, jsid *idCache)
NSString *const kOOLogException
Definition OOLogging.m:651
BOOL OOLogWillDisplayMessagesInClass(NSString *inMessageClass)
Definition OOLogging.m:144
#define OOLog(class, format,...)
Definition OOLogging.h:88
NSString * OOLogAbbreviatedFileName(const char *inName)
Definition OOLogging.m:839
#define MIN(A, B)
Definition OOMaths.h:111
unsigned count
return nil
@ STELLAR_TYPE_MOON
@ STELLAR_TYPE_ATMOSPHERE
@ STELLAR_TYPE_MINIATURE
@ STELLAR_TYPE_SUN
@ STELLAR_TYPE_NORMAL_PLANET
jsval oo_jsValueInContext:(JSContext *context)
OOJSScript * currentlyRunningScript()
Definition OOJSScript.m:339
OOJavaScriptEngine * sharedEngine()
id jsScriptFromFileNamed:properties:(NSString *fileName,[properties] NSDictionary *properties)
Definition OOScript.m:192
NSDictionary * dictionaryFromFilesNamed:inFolder:andMerge:(NSString *fileName,[inFolder] NSString *folderName,[andMerge] BOOL mergeFiles)