dommy-js-quickjs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "quickjs"
4
+
5
+ module Dommy
6
+ module Js
7
+ module Quickjs
8
+ # Binds HostBridge's abstract backend contract to the `quickjs` gem.
9
+ class Backend
10
+ # The gem's default eval timeout is 100ms, which interrupts large
11
+ # synchronous bridge loops (every property crossing is a Ruby call).
12
+ DEFAULT_TIMEOUT_MSEC = 60_000
13
+
14
+ def initialize(**vm_opts)
15
+ vm_opts = {timeout_msec: DEFAULT_TIMEOUT_MSEC}.merge(vm_opts)
16
+ @vm = ::Quickjs::VM.new(**vm_opts)
17
+ end
18
+
19
+ def eval(js)
20
+ @vm.eval_code(js, async: false)
21
+ end
22
+
23
+ # Async eval: the gem awaits the top-level result and drains the
24
+ # microtask queue, so JS `await`/Promises resolve before returning.
25
+ def eval_awaited(js)
26
+ @vm.eval_code(js, async: true)
27
+ end
28
+
29
+ def define_host_function(name, &block)
30
+ @vm.define_function(name, &block)
31
+ end
32
+
33
+ def call_js(path, *args)
34
+ @vm.call(path, *args)
35
+ end
36
+
37
+ def drain_microtasks
38
+ @vm.drain_jobs!
39
+ end
40
+
41
+ # Register a handler for promise rejections that reach the microtask
42
+ # queue with no `.catch` — frameworks (Turbo, …) often swallow these,
43
+ # so surfacing them is essential for diagnosing failures.
44
+ def on_unhandled_rejection(&block)
45
+ @vm.on_unhandled_rejection(&block)
46
+ end
47
+
48
+ # Register a handler for console.(log|info|debug|warn|error). The block
49
+ # receives a log object (#severity / #to_s / #raw).
50
+ def on_log(&block)
51
+ @vm.on_log(&block)
52
+ end
53
+
54
+ def run_gc
55
+ @vm.gc!
56
+ end
57
+
58
+ def dispose
59
+ @vm.dispose!
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "capybara/dommy"
4
+ require_relative "../quickjs"
5
+
6
+ module Dommy
7
+ module Js
8
+ module Quickjs
9
+ # Opt-in Capybara integration. Requiring this file enables JS execution on
10
+ # Capybara::Dommy::Driver (via install_capybara! below), so execute_script /
11
+ # evaluate_script run against the current Dommy document through a QuickJS
12
+ # Runtime. Without this require, capybara-dommy stays JS-free (its default).
13
+ module CapybaraDriver
14
+ def execute_script(script, *_args)
15
+ dommy_js_runtime.execute(script)
16
+ nil
17
+ end
18
+
19
+ def evaluate_script(script, *_args)
20
+ decode_for_capybara(dommy_js_runtime.evaluate(script))
21
+ end
22
+
23
+ # No real async loop; evaluate synchronously. Sufficient for scripts
24
+ # that resolve immediately (the common Capybara case).
25
+ def evaluate_async_script(script, *args)
26
+ evaluate_script(script, *args)
27
+ end
28
+
29
+ private
30
+
31
+ # One Runtime per document. Rebuilt when navigation swaps the document
32
+ # so JS always sees the current page (and the old VM is released).
33
+ def dommy_js_runtime
34
+ doc = document
35
+ unless defined?(@dommy_js_doc) && @dommy_js_doc.equal?(doc)
36
+ @dommy_js_runtime&.dispose
37
+ @dommy_js_runtime = Runtime.new
38
+ @dommy_js_runtime.define_host_object("document", doc)
39
+ view = doc.default_view
40
+ @dommy_js_runtime.install_window(view) if view
41
+ @dommy_js_doc = doc
42
+ end
43
+ @dommy_js_runtime
44
+ end
45
+
46
+ # Map an evaluate() result to what Capybara expects:
47
+ # - Array -> recurse (so element collections wrap per item)
48
+ # - JS undefined -> nil
49
+ # - Dommy::Element -> Capybara::Dommy::Node (covers HTML/SVG subclasses)
50
+ # - other bridge obj -> nil (Document/Text/Comment/Fragment/NodeList/
51
+ # Window have no Capybara representation; a browser
52
+ # likewise returns non-serializable values as null)
53
+ # - primitive/Hash -> as-is
54
+ def decode_for_capybara(value)
55
+ case value
56
+ when Array
57
+ value.map { |element| decode_for_capybara(element) }
58
+ when ::Quickjs::Value::UNDEFINED
59
+ nil
60
+ when ::Dommy::Element
61
+ ::Capybara::Dommy::Node.new(self, value)
62
+ else
63
+ value.respond_to?(:__js_get__) ? nil : value
64
+ end
65
+ end
66
+ end
67
+
68
+ # Idempotently prepend JS-execution support onto Capybara::Dommy::Driver.
69
+ # Safe to call multiple times; only prepends once. Called on require, but
70
+ # exposed so integration can be enabled/controlled explicitly (e.g. tests).
71
+ def self.install_capybara!
72
+ return if ::Capybara::Dommy::Driver.ancestors.include?(CapybaraDriver)
73
+
74
+ ::Capybara::Dommy::Driver.prepend(CapybaraDriver)
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ Dommy::Js::Quickjs.install_capybara!
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dommy
4
+ module Js
5
+ module Quickjs
6
+ # Public entry point: a JS runtime that can drive a Dommy DOM.
7
+ #
8
+ # rt = Dommy::Js::Quickjs::Runtime.new
9
+ # rt.define_host_object("document", win.document)
10
+ # rt.evaluate('document.querySelector("h1").textContent') #=> "..."
11
+ #
12
+ # Wires the QuickJS Backend to the engine-agnostic HostBridge, seeded with
13
+ # the Dommy method manifest.
14
+ class Runtime
15
+ def initialize(**vm_opts)
16
+ @backend = Backend.new(**vm_opts)
17
+ @bridge = Dommy::Js::HostBridge.new(@backend)
18
+ end
19
+
20
+ def define_host_object(name, obj)
21
+ @bridge.define_host_object(name, obj)
22
+ end
23
+
24
+ # Inject the Dommy window and alias the bare browser timer globals to it,
25
+ # so `setTimeout(fn, ms)` routes into Dommy's deterministic scheduler.
26
+ # Drive callbacks with `win.scheduler.advance_time(ms)`. `window.setTimeout`
27
+ # already works via the Window manifest; this also wires the unqualified
28
+ # globals browsers expose.
29
+ def install_window(win)
30
+ @window = win
31
+ define_host_object("window", win)
32
+ @bridge.window = win
33
+ @backend.eval(<<~JS)
34
+ globalThis.setTimeout = (fn, delay) => window.setTimeout(fn, delay);
35
+ globalThis.clearTimeout = (id) => window.clearTimeout(id);
36
+ globalThis.setInterval = (fn, delay) => window.setInterval(fn, delay);
37
+ globalThis.clearInterval = (id) => window.clearInterval(id);
38
+ globalThis.requestAnimationFrame = (fn) => window.requestAnimationFrame(fn);
39
+ globalThis.cancelAnimationFrame = (id) => window.cancelAnimationFrame(id);
40
+ // queueMicrotask must share the engine's promise-job (microtask)
41
+ // queue so its callbacks are FIFO-ordered with Promise reactions
42
+ // (the WHATWG single-microtask-queue model). Routing through the
43
+ // Ruby scheduler instead would drain on a separate pass, reordering
44
+ // it after all native promise jobs.
45
+ globalThis.queueMicrotask = (fn) => {
46
+ if (typeof fn !== "function") throw new TypeError("queueMicrotask requires a function");
47
+ Promise.resolve().then(() => { fn(); });
48
+ };
49
+ JS
50
+ win
51
+ end
52
+
53
+ # Expose the seeded interface constructors on a secondary window (an
54
+ # iframe's contentWindow), so cross-window instanceof / defaultView work.
55
+ # Call after install_window (the constructors must already be seeded).
56
+ def expose_constructors_on(window_obj)
57
+ @bridge.expose_constructors_on(window_obj)
58
+ end
59
+
60
+ # Run a script for side effects (no return value). Wrapped in an IIFE so
61
+ # statements are allowed and the completion value is voided — otherwise a
62
+ # trailing Promise expression would trip the gem's "unawaited Promise"
63
+ # guard. Drains microtasks so queued .then work lands before returning.
64
+ def execute(js)
65
+ @backend.eval("(function () {\n#{js}\n})();")
66
+ drain_microtasks
67
+ nil
68
+ end
69
+
70
+ # Evaluate JS and return its value, with DOM nodes decoded to Dommy
71
+ # objects (rather than the empty Hash a raw proxy becomes crossing to
72
+ # Ruby). Accepts either an expression (`document.title`) or a statement
73
+ # body that uses `return` (`const x = ...; return x;`): the expression
74
+ # form is tried first and, on a syntax error, retried as an async
75
+ # function body. Syntax errors are compile-time so the failed first
76
+ # attempt runs nothing. The result is awaited, so a Promise resolves
77
+ # before returning.
78
+ def evaluate(js)
79
+ @bridge.decode(eval_tagged("await (#{js.strip.sub(/;\s*\z/, "")})"))
80
+ rescue ::Quickjs::SyntaxError
81
+ @bridge.decode(eval_tagged("await (async () => {\n#{js}\n})()"))
82
+ end
83
+
84
+ def drain_microtasks
85
+ @backend.drain_microtasks
86
+ end
87
+
88
+ # Handle-oriented JS access for a wasm guest (see WasmBridge). Memoized
89
+ # so the guest's `__rbWasmInvoke` dispatcher (installed via #on_invoke)
90
+ # stays registered for the VM's lifetime.
91
+ def wasm_bridge
92
+ @wasm_bridge ||= WasmBridge.new(@backend)
93
+ end
94
+
95
+ # Drive the event loop to quiescence: drain the native microtask queue,
96
+ # then advance the deterministic scheduler to its next due timer and drain
97
+ # again, repeating until no timer is pending. This is the single
98
+ # deterministic "settle everything" entry point a host uses after an eval
99
+ # (mirroring a `drain_async!`): every queued microtask runs and every
100
+ # scheduled timer fires, in WHATWG order (microtasks before each timer).
101
+ # `max_iterations` bounds runaway timer loops (e.g. a self-rescheduling
102
+ # setInterval).
103
+ def run_until_idle(max_iterations: 1000)
104
+ sched = @window&.scheduler
105
+ max_iterations.times do
106
+ drain_microtasks
107
+ break unless sched
108
+
109
+ next_at = sched.next_due_timer_at
110
+ break unless next_at
111
+
112
+ sched.advance_time(next_at - sched.now_ms)
113
+ drain_microtasks
114
+ end
115
+ self
116
+ end
117
+
118
+ # Surface otherwise-swallowed JS promise rejections (see Backend).
119
+ def on_unhandled_rejection(&block)
120
+ @backend.on_unhandled_rejection(&block)
121
+ self
122
+ end
123
+
124
+ # Observe console.* output (see Backend).
125
+ def on_log(&block)
126
+ @backend.on_log(&block)
127
+ self
128
+ end
129
+
130
+ # Wire the bare browser globals frameworks reach for, aliased onto the
131
+ # installed window: self / location / history / navigator / storages /
132
+ # CSS / fetch / addEventListener / .... Call after install_window. This
133
+ # is what lets real frontend bundles (Turbo, …) run unmodified.
134
+ def install_browser_globals
135
+ @backend.eval(<<~JS)
136
+ globalThis.self = globalThis;
137
+ // Top-level window: parent/top are the window itself (spec), so
138
+ // frame-walking loops terminate instead of dereferencing undefined.
139
+ globalThis.parent = globalThis;
140
+ globalThis.top = globalThis;
141
+ globalThis.location = window.location;
142
+ globalThis.history = window.history;
143
+ globalThis.navigator = window.navigator;
144
+ globalThis.sessionStorage = window.sessionStorage;
145
+ globalThis.localStorage = window.localStorage;
146
+ globalThis.CSS = window.CSS;
147
+ globalThis.fetch = (...args) => window.fetch(...args);
148
+ globalThis.addEventListener = (...args) => window.addEventListener(...args);
149
+ globalThis.removeEventListener = (...args) => window.removeEventListener(...args);
150
+ globalThis.dispatchEvent = (event) => window.dispatchEvent(event);
151
+ // The window IS the global object, so JS built-in constructors and
152
+ // namespaces are also `window` properties (`window.String`,
153
+ // `window.Number`, …). Mirror them as own props on the window proxy
154
+ // so code that reads constructors off `window` (e.g. the WPT
155
+ // reflection harness's `window[type]` casts) resolves them.
156
+ for (const __n of [
157
+ "String", "Boolean", "Number", "BigInt", "Symbol", "Object", "Array",
158
+ "Function", "Date", "RegExp", "Promise", "Map", "Set", "WeakMap",
159
+ "WeakSet", "Math", "JSON", "Reflect", "Proxy", "Error", "TypeError",
160
+ "RangeError", "SyntaxError", "Infinity", "NaN", "undefined",
161
+ "parseInt", "parseFloat", "isNaN", "isFinite", "globalThis",
162
+ ]) {
163
+ try { window[__n] = globalThis[__n]; } catch (__e) {}
164
+ }
165
+ // Minimal WebAssembly.Memory: the engine provides a real
166
+ // SharedArrayBuffer but no WebAssembly, and WPT's `common/sab.js`
167
+ // derives the SAB constructor from
168
+ // `new WebAssembly.Memory({shared:true}).buffer.constructor`. A
169
+ // Memory whose `.buffer` is a SharedArrayBuffer is enough to let
170
+ // those tests (encodeInto, TextDecoder copy, …) exercise shared
171
+ // buffers with the real codec logic.
172
+ if (typeof globalThis.WebAssembly === "undefined" && typeof globalThis.SharedArrayBuffer === "function") {
173
+ globalThis.WebAssembly = {
174
+ Memory: function (opts) {
175
+ const bytes = ((opts && opts.initial) || 0) * 65536;
176
+ this.buffer = (opts && opts.shared)
177
+ ? new SharedArrayBuffer(bytes)
178
+ : new ArrayBuffer(bytes);
179
+ },
180
+ };
181
+ }
182
+ JS
183
+ self
184
+ end
185
+
186
+ # Run JS GC then drain, so FinalizationRegistry cleanup callbacks fire and
187
+ # release handles for proxies that are no longer referenced.
188
+ def collect_garbage
189
+ @backend.run_gc
190
+ @backend.drain_microtasks
191
+ end
192
+
193
+ # Live handle count (introspection for lifetime tests).
194
+ def registered_count
195
+ @bridge.registered_count
196
+ end
197
+
198
+ def dispose
199
+ @backend.dispose
200
+ end
201
+
202
+ private
203
+
204
+ def eval_tagged(inner_expr)
205
+ @backend.eval_awaited("__rbHost.tag(#{inner_expr});")
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dommy
4
+ module Js
5
+ module Quickjs
6
+ VERSION = "0.1.0"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dommy
4
+ module Js
5
+ module Quickjs
6
+ # Handle-oriented JS access for a wasm guest (e.g. mruby-in-wasm under
7
+ # wasmtime-rb). Distinct from the Proxy-based HostBridge: instead of
8
+ # exposing Ruby DOM objects to JS as proxies, this lets a guest treat any
9
+ # JS value as an opaque integer ref it can get/set/call/new on — the shape
10
+ # the guest's `js_*` bridge imports need.
11
+ #
12
+ # The JS half lives in host_runtime.js (the `wasm*` functions on
13
+ # `__rbHost`); this is the thin Ruby facade over them. Every non-primitive
14
+ # JS value crosses as a `JSValue` (an opaque ref into the VM); primitives
15
+ # cross as plain Ruby values. Callbacks the guest registers become JS
16
+ # functions (also refs) that route back through `__rbWasmInvoke`.
17
+ class WasmBridge
18
+ # An opaque handle to a JS value living in the VM. `ref` is the integer
19
+ # id into the JS-side jsRefs table.
20
+ JSValue = Struct.new(:ref) do
21
+ def to_s
22
+ "#<JSValue ref=#{ref}>"
23
+ end
24
+ end
25
+
26
+ def initialize(backend)
27
+ @backend = backend
28
+ end
29
+
30
+ # Install the dispatcher JS callbacks route back through. The block
31
+ # receives (invoke_id, packed_args) and must return a packed result
32
+ # (the same tagged shape #pack produces). Called once by the embedder.
33
+ def on_invoke(&block)
34
+ @backend.define_host_function("__rbWasmInvoke") do |invoke_id, args|
35
+ block.call(invoke_id.to_i, args)
36
+ end
37
+ self
38
+ end
39
+
40
+ # A ref to the VM's globalThis — the guest's `js_global`.
41
+ def global_ref
42
+ unpack(@backend.call_js("__rbHost.wasmGlobalRef"))
43
+ end
44
+
45
+ # Evaluate real JS source in global scope; returns the (packed) result.
46
+ def eval_js(src)
47
+ unpack(@backend.call_js("__rbHost.wasmEval", src.to_s))
48
+ end
49
+
50
+ def get(recv, prop)
51
+ unpack(@backend.call_js("__rbHost.wasmGet", ref_of(recv), prop.to_s))
52
+ end
53
+
54
+ def set(recv, prop, value)
55
+ @backend.call_js("__rbHost.wasmSet", ref_of(recv), prop.to_s, pack(value))
56
+ nil
57
+ end
58
+
59
+ def call(recv, method, args)
60
+ unpack(@backend.call_js("__rbHost.wasmCall", ref_of(recv), method.to_s, args.map { |a| pack(a) }))
61
+ end
62
+
63
+ # Apply a function ref directly (optionally with an explicit `this`).
64
+ def apply(fn, this_arg, args)
65
+ this_ref = this_arg.nil? ? nil : ref_of(this_arg)
66
+ unpack(@backend.call_js("__rbHost.wasmApply", ref_of(fn), this_ref, args.map { |a| pack(a) }))
67
+ end
68
+
69
+ def construct(ctor, args)
70
+ unpack(@backend.call_js("__rbHost.wasmNew", ref_of(ctor), args.map { |a| pack(a) }))
71
+ end
72
+
73
+ def typeof(value)
74
+ @backend.call_js("__rbHost.wasmTypeof", ref_of(value))
75
+ end
76
+
77
+ def to_string(value)
78
+ @backend.call_js("__rbHost.wasmToString", ref_of(value))
79
+ end
80
+
81
+ def strict_equal(a, b)
82
+ @backend.call_js("__rbHost.wasmStrictEqual", ref_of(a), ref_of(b))
83
+ end
84
+
85
+ def js_null?(value)
86
+ return value.nil? unless value.is_a?(JSValue)
87
+
88
+ @backend.call_js("__rbHost.wasmIsNull", value.ref)
89
+ end
90
+
91
+ def instance_of?(value, ctor)
92
+ @backend.call_js("__rbHost.wasmInstanceof", ref_of(value), ref_of(ctor))
93
+ end
94
+
95
+ # Make a JS function (returned as a ref) that calls back into the guest
96
+ # with the given invoke-id when invoked.
97
+ def make_callback(invoke_id)
98
+ unpack(@backend.call_js("__rbHost.wasmMakeCallback", invoke_id.to_i))
99
+ end
100
+
101
+ def release(value)
102
+ @backend.call_js("__rbHost.wasmReleaseRef", value.ref) if value.is_a?(JSValue)
103
+ nil
104
+ end
105
+
106
+ # Ruby value -> wasm-tagged JS value. Public so the embedder's
107
+ # #on_invoke dispatcher can pack the values it hands back into JS.
108
+ def pack(value)
109
+ case value
110
+ when JSValue then {"__rb_js_ref" => value.ref}
111
+ when nil, true, false, Integer, Float, String then value
112
+ when Symbol then value.to_s
113
+ when Array then value.map { |e| pack(e) }
114
+ when Hash then value.each_with_object({}) { |(k, v), h| h[k.to_s] = pack(v) }
115
+ else
116
+ raise ArgumentError, "cannot pack #{value.class} for the wasm JS bridge"
117
+ end
118
+ end
119
+
120
+ # wasm-tagged JS value -> Ruby value (JSValue for refs). Public for the
121
+ # same reason as #pack.
122
+ def unpack(value)
123
+ case value
124
+ when Hash
125
+ if value.key?("__rb_js_ref")
126
+ JSValue.new(value["__rb_js_ref"])
127
+ elsif value.key?("__rb_undefined")
128
+ nil
129
+ elsif value.key?("__rb_bytes")
130
+ value["__rb_bytes"]
131
+ elsif value.key?("__rb_arraybuffer")
132
+ value["__rb_arraybuffer"]
133
+ else
134
+ value.each_with_object({}) { |(k, v), h| h[k] = unpack(v) }
135
+ end
136
+ when Array then value.map { |e| unpack(e) }
137
+ else value
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ def ref_of(value)
144
+ return value.ref if value.is_a?(JSValue)
145
+
146
+ raise ArgumentError, "expected a JSValue receiver, got #{value.inspect}"
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "quickjs/version"
4
+
5
+ module Dommy
6
+ module Js
7
+ module Quickjs
8
+ class Error < StandardError; end
9
+ end
10
+ end
11
+ end
12
+
13
+ require_relative "handle_table"
14
+ require_relative "dom_interfaces"
15
+ require_relative "constructor_registry"
16
+ require_relative "custom_elements"
17
+ require_relative "host_bridge"
18
+ require_relative "quickjs/backend"
19
+ require_relative "quickjs/wasm_bridge"
20
+ require_relative "quickjs/runtime"
@@ -0,0 +1,8 @@
1
+ module Dommy
2
+ module Js
3
+ module Quickjs
4
+ VERSION: String
5
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
6
+ end
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dommy-js-quickjs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - takahashim
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2026-05-31 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: quickjs
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.18.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.18.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: dommy
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.8.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.8.1
40
+ description: |
41
+ dommy-js-quickjs lets JavaScript drive a Dommy DOM by embedding QuickJS (via
42
+ the quickjs gem) and bridging DOM nodes to JS through an ES Proxy that routes
43
+ property/method access into Dommy's __js_get__ / __js_set__ / __js_call__ ABI.
44
+ email:
45
+ - takahashimm@gmail.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - CHANGELOG.md
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - docs/bridge-redesign.md
55
+ - docs/wpt-conformance.md
56
+ - lib/dommy/js/constructor_registry.rb
57
+ - lib/dommy/js/custom_elements.rb
58
+ - lib/dommy/js/dom_interfaces.rb
59
+ - lib/dommy/js/handle_table.rb
60
+ - lib/dommy/js/host_bridge.rb
61
+ - lib/dommy/js/host_runtime.js
62
+ - lib/dommy/js/observable_runtime.js
63
+ - lib/dommy/js/quickjs.rb
64
+ - lib/dommy/js/quickjs/backend.rb
65
+ - lib/dommy/js/quickjs/capybara.rb
66
+ - lib/dommy/js/quickjs/runtime.rb
67
+ - lib/dommy/js/quickjs/version.rb
68
+ - lib/dommy/js/quickjs/wasm_bridge.rb
69
+ - sig/dommy/js/quickjs.rbs
70
+ homepage: https://github.com/takahashim/dommy-js-quickjs
71
+ licenses:
72
+ - MIT
73
+ metadata:
74
+ homepage_uri: https://github.com/takahashim/dommy-js-quickjs
75
+ source_code_uri: https://github.com/takahashim/dommy-js-quickjs
76
+ bug_tracker_uri: https://github.com/takahashim/dommy-js-quickjs/issues
77
+ rubygems_mfa_required: 'true'
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 3.2.0
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubygems_version: 3.6.2
93
+ specification_version: 4
94
+ summary: QuickJS backend for running JavaScript against a Dommy DOM.
95
+ test_files: []