Skip to content

Commit 75a0cb9

Browse files
authored
js: add MakeFullWrapper to expose exported methods and struct fields. (#8)
Currently the documentation for js.MakeWrapper is: > "MakeWrapper creates a JavaScript object which has wrappers for the exported methods of i. Use explicit getter and setter methods to expose struct fields to JavaScript." Where the value a struct value (or more interestingly a pointer to a struct value) we can actually auto-generate getters and setters for exported fields in the JavaScript world, rather than requiring explicit getters and setters to be defined on the Go side. We do this via a new MakeFullWrapper method.
1 parent e13dc1a commit 75a0cb9

File tree

8 files changed

+417
-141
lines changed

8 files changed

+417
-141
lines changed

compiler/gopherjspkg/fs_vfsdata.go

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler/natives/fs_vfsdata.go

Lines changed: 97 additions & 97 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler/prelude/jsmapping.js

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var $needsExternalization = function(t) {
2020
}
2121
};
2222

23-
var $externalize = function(v, t) {
23+
var $externalize = function(v, t, makeWrapper) {
2424
if (t === $jsObjectPtr) {
2525
return v;
2626
}
@@ -44,37 +44,37 @@ var $externalize = function(v, t) {
4444
case $kindArray:
4545
if ($needsExternalization(t.elem)) {
4646
return $mapArray(v, function(e) {
47-
return $externalize(e, t.elem);
47+
return $externalize(e, t.elem, makeWrapper);
4848
});
4949
}
5050
return v;
5151
case $kindFunc:
52-
return $externalizeFunction(v, t, false);
52+
return $externalizeFunction(v, t, false, makeWrapper);
5353
case $kindInterface:
5454
if (v === $ifaceNil) {
5555
return null;
5656
}
5757
if (v.constructor === $jsObjectPtr) {
5858
return v.$val.object;
5959
}
60-
return $externalize(v.$val, v.constructor);
60+
return $externalize(v.$val, v.constructor, makeWrapper);
6161
case $kindMap:
6262
var m = {};
6363
var keys = $keys(v);
6464
for (var i = 0; i < keys.length; i++) {
6565
var entry = v[keys[i]];
66-
m[$externalize(entry.k, t.key)] = $externalize(entry.v, t.elem);
66+
m[$externalize(entry.k, t.key, makeWrapper)] = $externalize(entry.v, t.elem, makeWrapper);
6767
}
6868
return m;
6969
case $kindPtr:
7070
if (v === t.nil) {
7171
return null;
7272
}
73-
return $externalize(v.$get(), t.elem);
73+
return $externalize(v.$get(), t.elem, makeWrapper);
7474
case $kindSlice:
7575
if ($needsExternalization(t.elem)) {
7676
return $mapArray($sliceToArray(v), function(e) {
77-
return $externalize(e, t.elem);
77+
return $externalize(e, t.elem, makeWrapper);
7878
});
7979
}
8080
return $sliceToArray(v);
@@ -128,20 +128,24 @@ var $externalize = function(v, t) {
128128
return o;
129129
}
130130

131+
if (makeWrapper !== undefined) {
132+
return makeWrapper(v);
133+
}
134+
131135
o = {};
132136
for (var i = 0; i < t.fields.length; i++) {
133137
var f = t.fields[i];
134138
if (!f.exported) {
135139
continue;
136140
}
137-
o[f.name] = $externalize(v[f.prop], f.typ);
141+
o[f.name] = $externalize(v[f.prop], f.typ, makeWrapper);
138142
}
139143
return o;
140144
}
141145
$throwRuntimeError("cannot externalize " + t.string);
142146
};
143147

144-
var $externalizeFunction = function(v, t, passThis) {
148+
var $externalizeFunction = function(v, t, passThis, makeWrapper) {
145149
if (v === $throwNilPointerError) {
146150
return null;
147151
}
@@ -154,22 +158,22 @@ var $externalizeFunction = function(v, t, passThis) {
154158
var vt = t.params[i].elem,
155159
varargs = [];
156160
for (var j = i; j < arguments.length; j++) {
157-
varargs.push($internalize(arguments[j], vt));
161+
varargs.push($internalize(arguments[j], vt, makeWrapper));
158162
}
159163
args.push(new t.params[i](varargs));
160164
break;
161165
}
162-
args.push($internalize(arguments[i], t.params[i]));
166+
args.push($internalize(arguments[i], t.params[i], makeWrapper));
163167
}
164168
var result = v.apply(passThis ? this : undefined, args);
165169
switch (t.results.length) {
166170
case 0:
167171
return;
168172
case 1:
169-
return $externalize(result, t.results[0]);
173+
return $externalize($copyIfRequired(result, t.results[0]), t.results[0], makeWrapper);
170174
default:
171175
for (var i = 0; i < t.results.length; i++) {
172-
result[i] = $externalize(result[i], t.results[i]);
176+
result[i] = $externalize($copyIfRequired(result[i], t.results[i]), t.results[i], makeWrapper);
173177
}
174178
return result;
175179
}
@@ -178,7 +182,7 @@ var $externalizeFunction = function(v, t, passThis) {
178182
return v.$externalizeWrapper;
179183
};
180184

181-
var $internalize = function(v, t, recv) {
185+
var $internalize = function(v, t, recv, makeWrapper) {
182186
if (t === $jsObjectPtr) {
183187
return v;
184188
}
@@ -226,7 +230,7 @@ var $internalize = function(v, t, recv) {
226230
$throwRuntimeError("got array with wrong size from JavaScript native");
227231
}
228232
return $mapArray(v, function(e) {
229-
return $internalize(e, t.elem);
233+
return $internalize(e, t.elem, makeWrapper);
230234
});
231235
case $kindFunc:
232236
return function() {
@@ -236,21 +240,21 @@ var $internalize = function(v, t, recv) {
236240
var vt = t.params[i].elem,
237241
varargs = arguments[i];
238242
for (var j = 0; j < varargs.$length; j++) {
239-
args.push($externalize(varargs.$array[varargs.$offset + j], vt));
243+
args.push($externalize(varargs.$array[varargs.$offset + j], vt, makeWrapper));
240244
}
241245
break;
242246
}
243-
args.push($externalize(arguments[i], t.params[i]));
247+
args.push($externalize(arguments[i], t.params[i], makeWrapper));
244248
}
245249
var result = v.apply(recv, args);
246250
switch (t.results.length) {
247251
case 0:
248252
return;
249253
case 1:
250-
return $internalize(result, t.results[0]);
254+
return $internalize(result, t.results[0], makeWrapper);
251255
default:
252256
for (var i = 0; i < t.results.length; i++) {
253-
result[i] = $internalize(result[i], t.results[i]);
257+
result[i] = $internalize(result[i], t.results[i], makeWrapper);
254258
}
255259
return result;
256260
}
@@ -283,45 +287,45 @@ var $internalize = function(v, t, recv) {
283287
case Float64Array:
284288
return new ($sliceType($Float64))(v);
285289
case Array:
286-
return $internalize(v, $sliceType($emptyInterface));
290+
return $internalize(v, $sliceType($emptyInterface), makeWrapper);
287291
case Boolean:
288292
return new $Bool(!!v);
289293
case Date:
290294
if (timePkg === undefined) {
291295
/* time package is not present, internalize as &js.Object{Date} so it can be externalized into original Date. */
292296
return new $jsObjectPtr(v);
293297
}
294-
return new timePkg.Time($internalize(v, timePkg.Time));
298+
return new timePkg.Time($internalize(v, timePkg.Time, makeWrapper));
295299
case Function:
296300
var funcType = $funcType([$sliceType($emptyInterface)], [$jsObjectPtr], true);
297-
return new funcType($internalize(v, funcType));
301+
return new funcType($internalize(v, funcType, makeWrapper));
298302
case Number:
299303
return new $Float64(parseFloat(v));
300304
case String:
301-
return new $String($internalize(v, $String));
305+
return new $String($internalize(v, $String, makeWrapper));
302306
default:
303307
if ($global.Node && v instanceof $global.Node) {
304308
return new $jsObjectPtr(v);
305309
}
306310
var mapType = $mapType($String, $emptyInterface);
307-
return new mapType($internalize(v, mapType));
311+
return new mapType($internalize(v, mapType, makeWrapper));
308312
}
309313
case $kindMap:
310314
var m = {};
311315
var keys = $keys(v);
312316
for (var i = 0; i < keys.length; i++) {
313-
var k = $internalize(keys[i], t.key);
314-
m[t.key.keyFor(k)] = { k: k, v: $internalize(v[keys[i]], t.elem) };
317+
var k = $internalize(keys[i], t.key, makeWrapper);
318+
m[t.key.keyFor(k)] = { k: k, v: $internalize(v[keys[i]], t.elem, makeWrapper) };
315319
}
316320
return m;
317321
case $kindPtr:
318322
if (t.elem.kind === $kindStruct) {
319-
return $internalize(v, t.elem);
323+
return $internalize(v, t.elem, makeWrapper);
320324
}
321325
case $kindSlice:
322326
return new t(
323327
$mapArray(v, function(e) {
324-
return $internalize(e, t.elem);
328+
return $internalize(e, t.elem, makeWrapper);
325329
})
326330
);
327331
case $kindString:
@@ -386,3 +390,17 @@ var $isASCII = function(s) {
386390
}
387391
return true;
388392
};
393+
394+
var $copyIfRequired = function(v, typ) {
395+
// interface values
396+
if (v.constructor.copy) {
397+
return new v.constructor($clone(v.$val, v.constructor));
398+
}
399+
// array and struct values
400+
if (typ.copy) {
401+
var clone = typ.zero();
402+
typ.copy(clone, v);
403+
return clone;
404+
}
405+
return v;
406+
};

compiler/prelude/jspmapping.js

Whitespace-only changes.

compiler/prelude/prelude.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compiler/prelude/prelude_min.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/js.go

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Package js provides functions for interacting with native JavaScript APIs. Calls to these functions are treated specially by GopherJS and translated directly to their corresponding JavaScript syntax.
22
//
3-
// Use MakeWrapper to expose methods to JavaScript. When passing values directly, the following type conversions are performed:
3+
// Use MakeWrapper to expose methods to JavaScript. Use MakeFullWrapper to expose methods AND fields to JavaScript. When passing values directly, the following type conversions are performed:
44
//
55
// | Go type | JavaScript type | Conversions back to interface{} |
66
// | --------------------- | --------------------- | ------------------------------- |
@@ -147,6 +147,103 @@ func MakeWrapper(i interface{}) *Object {
147147
return o
148148
}
149149

150+
// MakeFullWrapper creates a JavaScript object which has wrappers for the exported
151+
// methods of i, and, where i is a (pointer to a) struct value, wrapped getters
152+
// and setters
153+
// (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)
154+
// for the non-embedded exported fields of i. Values accessed via these methods
155+
// and getters are themsevles wrapped when accessed, but an important point to
156+
// note is that a new wrapped value is created on each access.
157+
func MakeFullWrapper(i interface{}) *Object {
158+
v := InternalObject(i)
159+
c := v.Get("constructor")
160+
161+
o := Global.Get("Object").New()
162+
163+
defineProperty := func(k string, fns ...func(*Object)) {
164+
op := Global.Get("Object").New()
165+
for _, f := range fns {
166+
f(op)
167+
}
168+
Global.Get("Object").Call("defineProperty", o, k, op)
169+
}
170+
171+
defineProperty("__internal_object__", func(op *Object) {
172+
op.Set("value", v)
173+
})
174+
175+
{
176+
// caculate a sensible type string
177+
178+
// we don't want to import any packages in this package
179+
// so we do some string operations by hand
180+
181+
typ := c.Get("string").String()
182+
pkg := c.Get("pkg").String()
183+
184+
ptr := ""
185+
if typ[0] == '*' {
186+
ptr = "*"
187+
}
188+
189+
for i := 0; i < len(typ); i++ {
190+
if typ[i] == '.' {
191+
typ = typ[i+1:]
192+
break
193+
}
194+
}
195+
196+
pkgTyp := pkg + "." + ptr + typ
197+
defineProperty("$type", func(op *Object) {
198+
op.Set("value", pkgTyp)
199+
})
200+
}
201+
202+
var fields *Object
203+
methods := Global.Get("Array").New()
204+
if ms := c.Get("methods"); ms != Undefined {
205+
methods = methods.Call("concat", ms)
206+
}
207+
// if we are a pointer value then add fields from element
208+
// else the constructor itself will have them
209+
if e := c.Get("elem"); e != Undefined {
210+
fields = e.Get("fields")
211+
methods = methods.Call("concat", e.Get("methods"))
212+
} else {
213+
fields = c.Get("fields")
214+
}
215+
for i := 0; i < methods.Length(); i++ {
216+
m := methods.Index(i)
217+
if m.Get("pkg").String() != "" { // not exported
218+
continue
219+
}
220+
defineProperty(m.Get("prop").String(), func(op *Object) {
221+
op.Set("value", func(args ...*Object) *Object {
222+
return Global.Call("$externalizeFunction", v.Get(m.Get("prop").String()), m.Get("typ"), true, InternalObject(MakeFullWrapper)).Call("apply", v, args)
223+
})
224+
})
225+
}
226+
if fields != Undefined {
227+
for i := 0; i < fields.Length(); i++ {
228+
f := fields.Index(i)
229+
if !f.Get("exported").Bool() {
230+
continue
231+
}
232+
defineProperty(f.Get("prop").String(), func(op *Object) {
233+
op.Set("get", func() *Object {
234+
vc := Global.Call("$copyIfRequired", v.Get("$val").Get(f.Get("prop").String()), f.Get("typ"))
235+
return Global.Call("$externalize", vc, f.Get("typ"), InternalObject(MakeFullWrapper))
236+
})
237+
op.Set("set", func(jv *Object) {
238+
gv := Global.Call("$internalize", jv, f.Get("typ"), InternalObject(MakeFullWrapper))
239+
v.Get("$val").Set(f.Get("prop").String(), gv)
240+
})
241+
})
242+
}
243+
}
244+
return o
245+
}
246+
150247
// NewArrayBuffer creates a JavaScript ArrayBuffer from a byte slice.
151248
func NewArrayBuffer(b []byte) *Object {
152249
slice := InternalObject(b)

0 commit comments

Comments
 (0)