This repository has been archived by the owner on Aug 18, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 33
/
js_proxy.c
220 lines (166 loc) · 6.94 KB
/
js_proxy.c
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
#include "js_proxy.h"
static JSBool get(JSContext* js_context, JSObject* obj, jsval id, jsval* retval);
static void finalize(JSContext* context, JSObject* obj);
static JSBool set(JSContext* context, JSObject* obj, jsval id, jsval* retval);
static JSClass JSProxyClass = {
"JSProxy", JSCLASS_HAS_PRIVATE,
JS_PropertyStub,
JS_PropertyStub,
get,
set,
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub,
finalize
};
static JSBool get(JSContext* js_context, JSObject* obj, jsval id, jsval* retval)
{
// pull out our Ruby object, which is embedded in js_context
VALUE ruby_context;
assert(ruby_context = (VALUE)JS_GetContextPrivate(js_context));
// get our struct, which is embedded in ruby_context
OurContext* context;
Data_Get_Struct(ruby_context, OurContext, context);
// get the Ruby object that backs this proxy
VALUE self;
assert(self = (VALUE)JS_GetInstancePrivate(context->js, obj, &JSProxyClass, NULL));
char* key = JS_GetStringBytes(JSVAL_TO_STRING(id));
VALUE ruby_id = rb_intern(key);
// if the Ruby object has a dynamic js property with a key
// matching the property we're looking for, pull the value out of
// that map.
if (rb_funcall(ruby_context, rb_intern("autovivified?"), 2, self, ID2SYM(ruby_id)))
{
*retval = convert_to_js(context,
rb_funcall(ruby_context, rb_intern("autovivified"), 2, self, ID2SYM(ruby_id)));
}
// if the Ruby object is a Module or Class and has a matching
// const defined, return the converted result of const_get
else if (rb_obj_is_kind_of(self, rb_cModule)
&& rb_is_const_id(ruby_id)
&& rb_funcall(self, rb_intern("const_defined?"), 1, ID2SYM(ruby_id)))
{
*retval = convert_to_js(context,
rb_funcall(self, rb_intern("const_get"), 1, ID2SYM(ruby_id)));
}
// otherwise, if the Ruby object has a 0-arity method named the same as
// the property we're trying to get, call it and return the converted result
else if (rb_funcall(self, rb_intern("respond_to?"), 1, ID2SYM(ruby_id)))
{
VALUE method = rb_funcall(self, rb_intern("method"), 1, ID2SYM(ruby_id));
int arity = NUM2INT(rb_funcall(method, rb_intern("arity"), 0));
if (arity == 0)
*retval = convert_to_js(context, rb_funcall(self, ruby_id, 0));
}
else
{
// otherwise, if the Ruby object quacks sorta like a hash (it responds to
// "[]" and "key?"), index it by key and return the converted result
VALUE is_indexable = rb_funcall(self, rb_intern("respond_to?"), 1, ID2SYM(rb_intern("[]")));
VALUE has_key_p = rb_funcall(self, rb_intern("respond_to?"), 1, ID2SYM(rb_intern("key?")));
if (is_indexable && has_key_p)
*retval = convert_to_js(context, rb_funcall(self, rb_intern("[]"), 1, rb_str_new2(key)));
}
return JS_TRUE;
}
static JSBool set(JSContext* js_context, JSObject* obj, jsval id, jsval* value)
{
VALUE ruby_context;
assert(ruby_context = (VALUE)JS_GetContextPrivate(js_context));
OurContext* context;
Data_Get_Struct(ruby_context, OurContext, context);
VALUE self;
assert(self = (VALUE)JS_GetInstancePrivate(context->js, obj, &JSProxyClass, NULL));
char* key = JS_GetStringBytes(JSVAL_TO_STRING(id));
VALUE ruby_key = rb_str_new2(key);
VALUE setter = rb_str_append(rb_str_new3(ruby_key), rb_str_new2("="));
VALUE setter_id = rb_intern(StringValuePtr(setter));
VALUE has_setter = rb_funcall(self, rb_intern("respond_to?"), 1, ID2SYM(setter_id));
VALUE is_index_assignable =
rb_funcall(self, rb_intern("respond_to?"), 1, ID2SYM(rb_intern("[]=")));
if (has_setter)
{
VALUE method = rb_funcall(self, rb_intern("method"), 1, ID2SYM(setter_id));
int arity = NUM2INT(rb_funcall(method, rb_intern("arity"), 0));
// if the Ruby object has a 1-arity method named "property=",
// call it with the converted value
if (arity == 1)
rb_funcall(self, setter_id, 1, convert_to_ruby(context, *value));
}
else if(is_index_assignable)
{
// otherwise, if the Ruby object quacks sorta like a hash for assignment
// (it responds to "[]="), assign it by key
rb_funcall(self, rb_intern("[]="), 2, ruby_key, convert_to_ruby(context, *value));
} else {
rb_funcall(ruby_context, rb_intern("autovivify"), 3, self, ruby_key, convert_to_ruby(context, *value));
}
return JS_TRUE;
}
static JSBool method_missing(JSContext* js_context, JSObject* obj, uintN argc, jsval* argv, jsval* retval)
{
VALUE ruby_context;
assert(ruby_context = (VALUE)JS_GetContextPrivate(js_context));
OurContext* context;
Data_Get_Struct(ruby_context, OurContext, context);
VALUE self;
assert(self = (VALUE)JS_GetInstancePrivate(context->js, obj, &JSProxyClass, NULL));
char* key = JS_GetStringBytes(JSVAL_TO_STRING(argv[0]));
VALUE ruby_id = rb_intern(key);
// FIXME: this could probably be a lot faster, to_a comes from enumerable on proxy
VALUE args = rb_funcall(convert_to_ruby(context, argv[1]), rb_intern("to_a"), 0);
// Context#jsend: if the last arg is a function, it'll get passed along as a &block
*retval = convert_to_js(context,
rb_funcall(ruby_context, rb_intern("jsend"), 3, self, ID2SYM(ruby_id), args));
return JS_TRUE;
}
JSBool js_value_is_proxy(OurContext* context, jsval maybe_proxy)
{
return JS_InstanceOf(context->js, JSVAL_TO_OBJECT(maybe_proxy), &JSProxyClass, NULL);
}
VALUE unwrap_js_proxy(OurContext* context, jsval proxy)
{
VALUE value;
assert(value = (VALUE)JS_GetInstancePrivate(context->js, JSVAL_TO_OBJECT(proxy), &JSProxyClass, NULL));
return value;
}
static void finalize(JSContext* js_context, JSObject* obj)
{
VALUE ruby_context = (VALUE)JS_GetContextPrivate(js_context);
if (ruby_context)
{
OurContext* context;
Data_Get_Struct(ruby_context, OurContext, context);
VALUE self;
assert(self = (VALUE)JS_GetInstancePrivate(context->js, obj, &JSProxyClass, NULL));
// remove the proxy OID from the id map
JS_HashTableRemove(context->rbids, (void *)rb_obj_id(self));
// free up the ruby value for GC
rb_funcall(ruby_context, rb_intern("remove_gcthing"), 1, self);
}
}
jsval make_js_proxy(OurContext* context, VALUE value)
{
jsid id = (jsid)JS_HashTableLookup(context->rbids, (void *)rb_obj_id(value));
jsval js;
if (id)
{
assert(JS_IdToValue(context->js, id, &js));
}
else
{
JSObject *jsobj;
assert(jsobj = JS_NewObject(context->js, &JSProxyClass, NULL, NULL));
assert(JS_SetPrivate(context->js, jsobj, (void*)value));
assert(JS_DefineFunction(context->js, jsobj, "__noSuchMethod__", method_missing, 2, 0));
js = OBJECT_TO_JSVAL(jsobj);
jsval newid;
assert(JS_ValueToId(context->js, js, &newid));
// put the proxy OID in the id map
assert(JS_HashTableAdd(context->rbids, (void *)rb_obj_id(value), (void *)newid));
// root the ruby value for GC
VALUE ruby_context = (VALUE)JS_GetContextPrivate(context->js);
rb_funcall(ruby_context, rb_intern("add_gcthing"), 1, value);
}
return js;
}