Class: Dommy::Js::Wasmtime::Engines::Quickjs

Inherits:
Object
  • Object
show all
Defined in:
lib/dommy/js/wasmtime/engines/quickjs.rb

Overview

Real-JS engine backing the wasmtime host’s ‘js.*` bridge.

mruby-in-wasm reaches the host through ~25 ‘js_*` imports that operate on opaque JS handles (js_eval / js_global / js_get / js_set / js_call / js_new / js_make_callback / …). In the browser those go to V8; here they go to a QuickJS VM bound to a Dommy DOM (dommy-js-quickjs’s WasmBridge), so the mruby inner loop runs the same JavaScript a browser would.

One global JS world: QuickJS owns globalThis, Dommy’s window/document are installed into it, and the guest’s ‘JS.global` IS quickjs globalThis — so a fetch stub installed with `engine.eval(“globalThis.fetch = …”)` and the fetch Fetchy calls are the same function (no split brain).

Every JS value crosses as a JsRef implementing the bridge ABI (js_get/js_set/js_call/js_new), so the VM’s duck-typed dispatch drives it unchanged.

Extracted from lilac’s reference host (test/ruby_spec/quickjs_bridge.rb).

Defined Under Namespace

Classes: JsRef

Constant Summary collapse

JSValue =
Dommy::Js::Quickjs::WasmBridge::JSValue

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(invoke:, window: Dommy.new_window) ⇒ Quickjs

Returns a new instance of Quickjs.

Parameters:

  • invoke (#call)

    invoke.(callback_id, ruby_args) -> guest return value; the VM passes its #invoke_callback so JS callbacks route into the wasm’s js_invoke_proc.

  • window (Dommy::Window) (defaults to: Dommy.new_window)

    the DOM to render into (a fresh one by default)



65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 65

def initialize(invoke:, window: Dommy.new_window)
  @window = window
  @runtime = Dommy::Js::Quickjs::Runtime.new
  @runtime.install_window(@window)
  @runtime.install_browser_globals
  @runtime.define_host_object("document", @window.document)
  @wb = @runtime.wasm_bridge
  @wb.on_invoke do |callback_id, packed_args|
    ruby_args = packed_args.map { |pa| wrap_result(@wb.unpack(pa)) }
    result = invoke.call(callback_id, ruby_args)
    @wb.pack(unwrap_arg(result))
  end
  @global = wrap_result(@wb.global_ref)
end

Instance Attribute Details

#globalObject (readonly)

Returns the value of attribute global.



59
60
61
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 59

def global
  @global
end

#wbObject (readonly)

Returns the value of attribute wb.



59
60
61
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 59

def wb
  @wb
end

#windowObject (readonly)

Returns the value of attribute window.



59
60
61
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 59

def window
  @window
end

Instance Method Details

#documentObject



80
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 80

def document = @window.document

#eval(src) ⇒ Object

Evaluate real JS source (the JS.eval_javascript escape hatch + the bridge’s js_eval import).



84
85
86
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 84

def eval(src)
  wrap_result(@wb.eval_js(src))
end

#instanceof(value, ctor) ⇒ Object



125
126
127
128
129
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 125

def instanceof(value, ctor)
  return false unless value.is_a?(JsRef) && ctor.is_a?(JsRef)

  @wb.instance_of?(value.jsvalue, ctor.jsvalue)
end

#make_callback(callback_id) ⇒ Object

A JS function that calls back into the guest’s callback table by id.



89
90
91
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 89

def make_callback(callback_id)
  wrap_result(@wb.make_callback(callback_id))
end

#on_logObject



99
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 99

def on_log(&) = @runtime.on_log(&)

#run_until_idleObject

Drive the event loop to quiescence (WHATWG-ordered): drain microtasks, advance Dommy’s deterministic scheduler to its next timer, repeat.



95
96
97
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 95

def run_until_idle
  @runtime.run_until_idle
end

#strict_equal(a, b) ⇒ Object



119
120
121
122
123
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 119

def strict_equal(a, b)
  return @wb.strict_equal(a.jsvalue, b.jsvalue) if a.is_a?(JsRef) && b.is_a?(JsRef)

  a == b
end

#to_string(value) ⇒ Object



113
114
115
116
117
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 113

def to_string(value)
  return @wb.to_string(value.jsvalue) if value.is_a?(JsRef)

  value.to_s
end

#typeof(value) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 101

def typeof(value)
  return @wb.typeof(value.jsvalue) if value.is_a?(JsRef)

  case value
  when nil then "object" # typeof null === "object"
  when Integer, Float then "number"
  when String then "string"
  when true, false then "boolean"
  else "object"
  end
end

#unwrap_arg(value) ⇒ Object

Handle value (primitive | JsRef | Array | Hash) -> WasmBridge arg.



137
138
139
140
141
142
143
144
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 137

def unwrap_arg(value)
  case value
  when JsRef then value.jsvalue
  when Array then value.map { |e| unwrap_arg(e) }
  when Hash then value.transform_values { |e| unwrap_arg(e) }
  else value
  end
end

#wrap_result(value) ⇒ Object

WasmBridge value (primitive | JSValue) -> handle value (primitive | JsRef).



132
133
134
# File 'lib/dommy/js/wasmtime/engines/quickjs.rb', line 132

def wrap_result(value)
  value.is_a?(JSValue) ? JsRef.new(self, value) : value
end