Skip to content

Commit 6c605ba

Browse files
Allow to wrap arbitrary JS Object by Ruby's JS::Object
1 parent 7837497 commit 6c605ba

File tree

4 files changed

+63
-13
lines changed

4 files changed

+63
-13
lines changed

ext/js/bindgen/rb-js-abi-host.wit

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ rb-object-to-js-rb-value: function(raw-rb-abi-value: u32) -> js-abi-value
1111

1212
js-value-to-string: function(value: js-abi-value) -> string
1313

14-
take-js-value: function(value: js-abi-value)
14+
export-js-value-to-host: function(value: js-abi-value)
15+
import-js-value-from-host: function() -> js-abi-value
1516

1617
reflect-apply: function(target: js-abi-value, this-argument: js-abi-value, arguments: list<js-abi-value>) -> js-abi-value
1718
reflect-construct: function(target: js-abi-value, arguments: list<js-abi-value>) -> js-abi-value

ext/js/js-core.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,13 @@ static VALUE _rb_js_obj_inspect(VALUE obj) {
239239
*/
240240
static VALUE _rb_js_export_to_js(VALUE obj) {
241241
struct jsvalue *p = check_jsvalue(obj);
242-
rb_js_abi_host_take_js_value(p->abi);
242+
rb_js_abi_host_export_js_value_to_host(p->abi);
243243
return Qnil;
244244
}
245245

246+
static VALUE _rb_js_import_from_js(VALUE obj) {
247+
return jsvalue_s_new(rb_js_abi_host_import_js_value_from_host());
248+
}
246249

247250
/*
248251
* call-seq:
@@ -317,6 +320,7 @@ void Init_js() {
317320
rb_define_method(rb_cJS_Object, "[]=", _rb_js_obj_aset, 2);
318321
rb_define_method(rb_cJS_Object, "call", _rb_js_obj_call, -1);
319322
rb_define_method(rb_cJS_Object, "__export_to_js", _rb_js_export_to_js, 0);
323+
rb_define_singleton_method(rb_cJS_Object, "__import_from_js", _rb_js_import_from_js, 0);
320324
rb_define_method(rb_cJS_Object, "inspect", _rb_js_obj_inspect, 0);
321325
rb_define_singleton_method(rb_cJS_Object, "wrap", _rb_js_obj_wrap, 1);
322326

packages/npm-packages/ruby-wasm-wasi/src/index.ts

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ import { addRbJsAbiHostToImports, JsAbiValue } from "./bindgen/rb-js-abi-host";
2323
export class RubyVM {
2424
guest: RbAbi.RbAbiGuest;
2525
private instance: WebAssembly.Instance | null = null;
26-
private exporter: JsValueExporter;
26+
private transport: JsValueTransport;
2727
private exceptionFormatter: RbExceptionFormatter;
2828

2929
constructor() {
3030
this.guest = new RbAbi.RbAbiGuest();
31-
this.exporter = new JsValueExporter();
31+
this.transport = new JsValueTransport();
3232
this.exceptionFormatter = new RbExceptionFormatter();
3333
}
3434

@@ -101,9 +101,12 @@ export class RubyVM {
101101
jsValueToString: (value) => {
102102
return value.toString();
103103
},
104-
takeJsValue: (value) => {
104+
exportJsValueToHost: (value) => {
105105
// See `JsValueExporter` for the reason why we need to do this
106-
this.exporter.takeJsValue(value);
106+
this.transport.takeJsValue(value);
107+
},
108+
importJsValueFromHost: () => {
109+
return this.transport.consumeJsValue();
107110
},
108111
instanceOf: (value, klass) => {
109112
if (typeof klass === "function") {
@@ -180,8 +183,21 @@ export class RubyVM {
180183
return evalRbCode(this, this.privateObject(), code);
181184
}
182185

186+
/**
187+
* Wrap a JavaScript value into a Ruby JS::Object
188+
* @param value The value to convert to RbValue
189+
* @returns the RbValue object representing the given JS value
190+
*
191+
* @example
192+
* const hash = vm.eval(`Hash.new`)
193+
* hash.call("store", vm.eval(`"key1"`), vm.wrap(new Object()));
194+
*/
195+
wrap(value: any): RbValue {
196+
return this.transport.importJsValue(value, this);
197+
}
198+
183199
private privateObject(): RubyVMPrivate {
184-
return { exporter: this.exporter, exceptionFormatter: this.exceptionFormatter }
200+
return { transport: this.transport, exceptionFormatter: this.exceptionFormatter }
185201
}
186202
}
187203

@@ -204,14 +220,26 @@ export class RubyVM {
204220
*
205221
* Note that `exportJsValue` is not reentrant.
206222
*/
207-
class JsValueExporter {
208-
private _takenJsValues: JsAbiValue = null;
223+
class JsValueTransport {
224+
private _takenJsValue: JsAbiValue = null;
209225
takeJsValue(value: JsAbiValue) {
210-
this._takenJsValues = value;
226+
this._takenJsValue = value;
227+
}
228+
consumeJsValue(): JsAbiValue {
229+
return this._takenJsValue;
211230
}
231+
212232
exportJsValue(value: RbValue): JsAbiValue {
213233
value.call("__export_to_js");
214-
return this._takenJsValues;
234+
return this._takenJsValue;
235+
}
236+
237+
importJsValue(value: JsAbiValue, vm: RubyVM): RbValue {
238+
this._takenJsValue = value;
239+
return vm.eval(`
240+
require "js"
241+
JS::Object.__import_from_js
242+
`);
215243
}
216244
}
217245

@@ -284,7 +312,7 @@ export class RbValue {
284312
if (jsValue.call("nil?").toString() === "true") {
285313
return null;
286314
}
287-
return this.privateObject.exporter.exportJsValue(jsValue);
315+
return this.privateObject.transport.exportJsValue(jsValue);
288316
}
289317
}
290318

@@ -302,7 +330,7 @@ enum ruby_tag_type {
302330
}
303331

304332
type RubyVMPrivate = {
305-
exporter: JsValueExporter,
333+
transport: JsValueTransport,
306334
exceptionFormatter: RbExceptionFormatter,
307335
};
308336

packages/npm-packages/ruby-wasm-wasi/test/vm.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,21 @@ eval:11:in \`<main>'`);
170170
expect(o1.call("hash").toString()).toBe(o2.call("hash").toString());
171171
expect(o2.call("hash").toString()).toBe(o3.call("hash").toString());
172172
});
173+
174+
test("Wrap arbitrary JS object to RbValue", async () => {
175+
const vm = await initRubyVM();
176+
const o1 = { v() { return 42 } }
177+
const X = vm.eval(`
178+
module X
179+
def self.identity(x) = x
180+
end
181+
X
182+
`);
183+
const o1Clone = X.call("identity", vm.wrap(o1));
184+
expect(o1Clone.call("call", vm.eval(`"v"`)).toJS().toString()).toBe("42");
185+
186+
// Check that JS object can be stored in Ruby Hash
187+
const hash = vm.eval(`Hash.new`)
188+
hash.call("store", vm.eval(`"key1"`), vm.wrap(new Object()));
189+
});
173190
});

0 commit comments

Comments
 (0)