public
Rubygem
Description: Johnson wraps JavaScript in a loving Ruby embrace.
Homepage: http://github.com/jbarnette/johnson/wikis
Clone URL: git://github.com/jbarnette/johnson.git
Search Repo:
aaronp (author)
Tue May 13 16:29:11 -0700 2008
commit  41b8a99fbc315a35868a84c3ca163039198eca0d
tree    695f4012ac02f1e538f0ae55c8ad97997556d071
parent  2fc2742302331b2a95a339c5c41977e08ce1ce66
johnson / ext / spidermonkey / context.c
100644 232 lines (189 sloc) 6.92 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#include "context.h"
#include "conversions.h"
#include "global.h"
#include "error.h"
#include "extensions.h"
#include "idhash.h"
 
/*
* call-seq:
* global
*
* Returns the global object used for this context.
*/
static VALUE global(VALUE self)
{
  OurContext* context;
  Data_Get_Struct(self, OurContext, context);
  return convert_to_ruby(context, OBJECT_TO_JSVAL(context->global));
}
 
/*
* call-seq:
* evaluate(script, filename=nil, linenum=nil)
*
* Evaluate +script+ with +filename+ using +linenum+
*/
static VALUE evaluate(int argc, VALUE* argv, VALUE self)
{
  VALUE script, filename, linenum;
 
  OurContext* context;
  Data_Get_Struct(self, OurContext, context);
 
  rb_scan_args( argc, argv, "12", &script, &filename, &linenum );
 
  // clean things up first
  context->ex = 0;
  memset(context->msg, 0, MAX_EXCEPTION_MESSAGE_SIZE);
 
  char* filenamez = RTEST(filename) ? StringValueCStr(filename) : "none";
  int linenumi = RTEST(linenum) ? NUM2INT(linenum) : 1;
 
  jsval js;
 
  // FIXME: should be able to pass in the 'file' name
  JSBool ok = JS_EvaluateScript(context->js, context->global,
    StringValuePtr(script), (unsigned)StringValueLen(script), filenamez, (unsigned)linenumi, &js);
 
  if (!ok)
  {
    if (JS_IsExceptionPending(context->js))
    {
      // If there's an exception pending here, it's a syntax error.
      JS_GetPendingException(context->js, &context->ex);
      JS_ClearPendingException(context->js);
    }
 
    if (context->ex)
    {
      return rb_funcall(self, rb_intern("handle_js_exception"),
        1, convert_to_ruby(context, context->ex));
      
      // VALUE message, file, line, stack;
      //
      // jsval js_message;
      // assert(JS_GetProperty(context->js, JSVAL_TO_OBJECT(context->ex), "message", &js_message));
      // message = convert_to_ruby(context, js_message);
      //
      // jsval js_file;
      // assert(JS_GetProperty(context->js, JSVAL_TO_OBJECT(context->ex), "fileName", &js_file));
      // file = convert_to_ruby(context, js_file);
      //
      // jsval js_line;
      // assert(JS_GetProperty(context->js, JSVAL_TO_OBJECT(context->ex), "lineNumber", &js_line));
      // line = convert_to_ruby(context, js_line);
      //
      // jsval js_stack;
      // assert(JS_GetProperty(context->js, JSVAL_TO_OBJECT(context->ex), "stack", &js_stack));
      // stack = convert_to_ruby(context, js_stack);
      //
      // return rb_funcall(self, rb_intern("handle_js_exception"),
      // 4, message, file, line, stack);
    }
    
    char* msg = context->msg;
 
    // toString() whatever the exception object is (if we have one)
    if (context->ex) msg = JS_GetStringBytes(JS_ValueToString(context->js, context->ex));
 
    return Johnson_Error_raise(msg);
  }
 
  return convert_to_ruby(context, js);
}
 
// callback for JS_SetErrorReporter
static void report_js_error(JSContext* js, const char* message, JSErrorReport* UNUSED(report))
{
  // first we find ourselves
  VALUE self = (VALUE)JS_GetContextPrivate(js);
 
  // then we find our bridge
  OurContext* context;
  Data_Get_Struct(self, OurContext, context);
 
  // NOTE: SpiderMonkey REALLY doesn't like being interrupted. If we
  // jump over to Ruby and raise here, segfaults and such ensue.
  // Instead, we store the exception (if any) and the error message
  // on the context. They're dealt with in the if (!ok) block of evaluate().
 
  strncpy(context->msg, message, MAX_EXCEPTION_MESSAGE_SIZE);
  JS_GetPendingException(context->js, &context->ex);
}
 
// callback for JS_SetBranchCallback
static JSBool branch_callback(JSContext* js, JSScript* UNUSED(script))
{
  static unsigned long branch_counter = 0;
  if( ++branch_counter % 0x1000 == 0 )
    JS_MaybeGC( js );
  return JS_TRUE;
}
 
/*
* call-seq:
* native_initialize(options={})
*
* Initializes the native spidermonkey values.
*/
static VALUE
initialize_native(VALUE self, VALUE UNUSED(options))
{
  OurContext* context;
  bool gthings_rooted_p = false;
 
  Data_Get_Struct(self, OurContext, context);
 
  if ((context->runtime = JS_NewRuntime(0x100000))
    && (context->js = JS_NewContext(context->runtime, 8192))
    && (context->jsids = create_id_hash())
    && (context->rbids = create_id_hash())
    && (context->gcthings = JS_NewObject(context->js, NULL, 0, 0))
    && (gthings_rooted_p = JS_AddNamedRoot(context->js, &(context->gcthings), "context->gcthings"))
    && (context->global = create_global_object(context))
    && (JS_AddNamedRoot(context->js, &(context->global), "context->global")))
  {
    JS_SetErrorReporter(context->js, report_js_error);
    JS_SetBranchCallback(context->js, branch_callback);
    JS_SetContextPrivate(context->js, (void *)self);
 
    JS_SetOptions(context->js, JS_GetOptions(context->js)
#ifdef JSOPTION_DONT_REPORT_UNCAUGHT
      | JSOPTION_DONT_REPORT_UNCAUGHT
#endif
#ifdef JSOPTION_VAROBJFIX
      | JSOPTION_VAROBJFIX
#endif
#ifdef JSOPTION_XML
      | JSOPTION_XML
#endif
    );
 
    return init_spidermonkey_extensions(context, self);
  }
 
  if (gthings_rooted_p)
    JS_RemoveRoot(context->js, &(context->gcthings));
 
  if (context->rbids)
    JS_HashTableDestroy(context->rbids);
 
  if (context->jsids)
    JS_HashTableDestroy(context->jsids);
 
  if (context->js)
    JS_DestroyContext(context->js);
 
  if (context->runtime)
    JS_DestroyRuntime(context->runtime);
 
  rb_raise(rb_eRuntimeError, "Failed to initialize SpiderMonkey context");
}
 
///////////////////////////////////////////////////////////////////////////
//// INFRASTRUCTURE BELOW HERE ////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
 
static void deallocate(OurContext* context)
{
  JS_SetContextPrivate(context->js, 0);
 
  JS_RemoveRoot(context->js, &(context->global));
  JS_RemoveRoot(context->js, &(context->gcthings));
  JS_HashTableDestroy(context->rbids);
  JS_HashTableDestroy(context->jsids);
 
  context->jsids = 0;
  context->rbids = 0;
 
  JS_DestroyContext(context->js);
  JS_DestroyRuntime(context->runtime);
 
  free(context);
}
 
static VALUE allocate(VALUE klass)
{
  OurContext* context = calloc(1, sizeof(OurContext));
  return Data_Wrap_Struct(klass, 0, deallocate, context);
}
 
void init_Johnson_SpiderMonkey_Context(VALUE spidermonkey)
{
  /* HACK: These comments are *only* to make RDoc happy.
VALUE johnson = rb_define_module("Johnson");
VALUE spidermonkey = rb_define_module_under(johnson, "SpiderMonkey");
*/
 
  /* This is the private context used for SpiderMonkey. */
  VALUE context = rb_define_class_under(spidermonkey, "Context", rb_cObject);
 
  rb_define_alloc_func(context, allocate);
  rb_define_private_method(context, "initialize_native", initialize_native, 1);
 
  rb_define_method(context, "global", global, 0);
  rb_define_method(context, "evaluate", evaluate, -1);
}
 
VALUE Johnson_SpiderMonkey_JSLandProxy()
{
  return rb_eval_string("Johnson::SpiderMonkey::JSLandProxy");
}