dommy-js-quickjs 0.1.0 → 0.10.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,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dommy
4
+ module Js
5
+ module Quickjs
6
+ # A process-global cache of compiled external-script bytecode, keyed by
7
+ # URL. Vendored bundles (turbo.umd.js, stimulus.umd.js, application.js, …)
8
+ # are identical across page loads but otherwise re-parsed on every fresh
9
+ # VM; compiling each once and running the bytecode per VM removes that
10
+ # repeated parse cost (the bundles are hundreds of KB).
11
+ #
12
+ # Keyed by URL: an asset URL maps 1:1 to its content (Propshaft / Sprockets
13
+ # digest-stamp it; test fixtures are stable within a process).
14
+ #
15
+ # The bridge's built-in runtime bundles (host_runtime.js,
16
+ # observable_runtime.js) use a separate cache in Backend#run_bundle, so an
17
+ # external-script count here stays uncontaminated by engine internals.
18
+ module ScriptCache
19
+ @cache = {}
20
+ @mutex = Mutex.new
21
+
22
+ class << self
23
+ # The compiled bytecode for `url`, compiling `source` on first use.
24
+ def compiled(url, source)
25
+ @mutex.synchronize { @cache[url] ||= Backend.compile(source, filename: url.to_s) }
26
+ end
27
+
28
+ def clear
29
+ @mutex.synchronize { @cache.clear }
30
+ end
31
+
32
+ # Under the mutex like the others: one lock guards this cache, and a
33
+ # reader that steps outside it is the kind of inconsistency that only
34
+ # shows up under load.
35
+ def size = @mutex.synchronize { @cache.size }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -3,7 +3,7 @@
3
3
  module Dommy
4
4
  module Js
5
5
  module Quickjs
6
- VERSION = "0.1.0"
6
+ VERSION = "0.10.0"
7
7
  end
8
8
  end
9
9
  end
@@ -15,13 +15,13 @@ module Dommy
15
15
  # cross as plain Ruby values. Callbacks the guest registers become JS
16
16
  # functions (also refs) that route back through `__rbWasmInvoke`.
17
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
18
+ # An opaque handle to a JS value living in the VM, identified by its id
19
+ # into the JS-side `jsRefs` table. This is the SAME table and the SAME
20
+ # `__rb_js_ref` tag the Proxy bridge's dehydrate/rehydrate use (see
21
+ # host_runtime.js), so a ref is one concept across both bridges — hence a
22
+ # single shared type. Aliased here so existing `WasmBridge::JSValue`
23
+ # callers keep resolving.
24
+ JSValue = ::Dommy::Bridge::JSValue
25
25
 
26
26
  def initialize(backend)
27
27
  @backend = backend
@@ -105,9 +105,15 @@ module Dommy
105
105
 
106
106
  # Ruby value -> wasm-tagged JS value. Public so the embedder's
107
107
  # #on_invoke dispatcher can pack the values it hands back into JS.
108
+ #
109
+ # Deliberately not core's Marshaller, which shares the same WireTags but
110
+ # answers a bigger question: it wraps host objects as DOM proxies, binds
111
+ # callbacks and resolves interfaces against a handle table. A wasm guest
112
+ # has none of that — every non-primitive is an opaque ref it asks about
113
+ # through #get / #call — so what it needs is the tags and nothing else.
108
114
  def pack(value)
109
115
  case value
110
- when JSValue then {"__rb_js_ref" => value.ref}
116
+ when JSValue then {Dommy::Bridge::WireTags::JS_REF => value.ref}
111
117
  when nil, true, false, Integer, Float, String then value
112
118
  when Symbol then value.to_s
113
119
  when Array then value.map { |e| pack(e) }
@@ -122,14 +128,14 @@ module Dommy
122
128
  def unpack(value)
123
129
  case value
124
130
  when Hash
125
- if value.key?("__rb_js_ref")
126
- JSValue.new(value["__rb_js_ref"])
127
- elsif value.key?("__rb_undefined")
131
+ if value.key?(Dommy::Bridge::WireTags::JS_REF)
132
+ JSValue.new(value[Dommy::Bridge::WireTags::JS_REF])
133
+ elsif value.key?(Dommy::Bridge::WireTags::UNDEFINED)
128
134
  nil
129
- elsif value.key?("__rb_bytes")
130
- value["__rb_bytes"]
131
- elsif value.key?("__rb_arraybuffer")
132
- value["__rb_arraybuffer"]
135
+ elsif value.key?(Dommy::Bridge::WireTags::BYTES)
136
+ value[Dommy::Bridge::WireTags::BYTES]
137
+ elsif value.key?(Dommy::Bridge::WireTags::ARRAY_BUFFER)
138
+ value[Dommy::Bridge::WireTags::ARRAY_BUFFER]
133
139
  else
134
140
  value.each_with_object({}) { |(k, v), h| h[k] = unpack(v) }
135
141
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dommy"
3
4
  require_relative "quickjs/version"
4
5
 
5
6
  module Dommy
@@ -10,11 +11,22 @@ module Dommy
10
11
  end
11
12
  end
12
13
 
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"
14
+ # The engine-agnostic host layer lives in the `dommy` gem (loaded above via
15
+ # `require "dommy"`): the Runtime port + registry, ScriptBoot/ImportMap/
16
+ # ModuleLoader, Dommy::Browser, AND the JS<->Ruby DOM bridge (HostBridge +
17
+ # Bridge::WireTags / Js::HandleTable / DomInterfaces / ConstructorResolver /
18
+ # CustomElementBridge, with host_runtime.js / observable_runtime.js). This gem
19
+ # provides only the QuickJS backend that plugs in underneath.
20
+ require_relative "quickjs/config"
18
21
  require_relative "quickjs/backend"
22
+ require_relative "quickjs/browser_environment"
23
+ require_relative "quickjs/event_loop"
24
+ require_relative "quickjs/error_translator"
19
25
  require_relative "quickjs/wasm_bridge"
20
26
  require_relative "quickjs/runtime"
27
+ require_relative "quickjs/script_cache"
28
+
29
+ # Register QuickJS as a pluggable JS runtime backend (the default). The host
30
+ # layer builds runtimes through `Dommy::Js.build_runtime` rather than naming
31
+ # this class directly.
32
+ Dommy::Js.register_runtime(:quickjs) { |**opts| Dommy::Js::Quickjs::Runtime.new(**opts) }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dommy-js-quickjs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - takahashim
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-05-31 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: quickjs
@@ -15,28 +15,34 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.18.0
18
+ version: 0.21.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.18.0
25
+ version: 0.21.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: dommy
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - "~>"
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.14.0
33
+ - - "<"
31
34
  - !ruby/object:Gem::Version
32
- version: 0.8.1
35
+ version: '0.15'
33
36
  type: :runtime
34
37
  prerelease: false
35
38
  version_requirements: !ruby/object:Gem::Requirement
36
39
  requirements:
37
- - - "~>"
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.14.0
43
+ - - "<"
38
44
  - !ruby/object:Gem::Version
39
- version: 0.8.1
45
+ version: '0.15'
40
46
  description: |
41
47
  dommy-js-quickjs lets JavaScript drive a Dommy DOM by embedding QuickJS (via
42
48
  the quickjs gem) and bridging DOM nodes to JS through an ES Proxy that routes
@@ -50,20 +56,22 @@ files:
50
56
  - CHANGELOG.md
51
57
  - LICENSE.txt
52
58
  - 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
59
  - lib/dommy/js/quickjs.rb
64
60
  - lib/dommy/js/quickjs/backend.rb
61
+ - lib/dommy/js/quickjs/browser_environment.rb
65
62
  - lib/dommy/js/quickjs/capybara.rb
63
+ - lib/dommy/js/quickjs/config.rb
64
+ - lib/dommy/js/quickjs/error_translator.rb
65
+ - lib/dommy/js/quickjs/event_loop.rb
66
+ - lib/dommy/js/quickjs/js/browser_globals.js
67
+ - lib/dommy/js/quickjs/js/error_rebuild.js
68
+ - lib/dommy/js/quickjs/js/intl_polyfill.js
69
+ - lib/dommy/js/quickjs/js/timer_globals.js
70
+ - lib/dommy/js/quickjs/js/wasm_stub.js
71
+ - lib/dommy/js/quickjs/js/window_builtins.js
72
+ - lib/dommy/js/quickjs/rack.rb
66
73
  - lib/dommy/js/quickjs/runtime.rb
74
+ - lib/dommy/js/quickjs/script_cache.rb
67
75
  - lib/dommy/js/quickjs/version.rb
68
76
  - lib/dommy/js/quickjs/wasm_bridge.rb
69
77
  - sig/dommy/js/quickjs.rbs
@@ -89,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
97
  - !ruby/object:Gem::Version
90
98
  version: '0'
91
99
  requirements: []
92
- rubygems_version: 3.6.2
100
+ rubygems_version: 4.0.20
93
101
  specification_version: 4
94
102
  summary: QuickJS backend for running JavaScript against a Dommy DOM.
95
103
  test_files: []
data/Rakefile DELETED
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "minitest/test_task"
5
-
6
- Minitest::TestTask.create
7
-
8
- task default: :test
9
-
10
- namespace :wpt do
11
- desc "Run the vendored WPT corpus against the bridge and report a conformance rate"
12
- task :conformance, [:filter] do |_t, args|
13
- $LOAD_PATH.unshift File.expand_path("test", __dir__)
14
- require "test_helper"
15
- require "support/wpt_runner"
16
-
17
- runner = Dommy::Js::WptRunner
18
- files = runner.manifest
19
- files = files.grep(/#{args[:filter]}/) if args[:filter]
20
-
21
- total_pass = total = file_ok = 0
22
- blocked = []
23
- by_dir = Hash.new { |h, k| h[k] = [0, 0] } # dir => [pass, total]
24
- files.each do |rel|
25
- results = runner.run(rel)
26
- pass = results.count(&:pass?)
27
- n = results.size
28
- total_pass += pass
29
- total += n
30
- dir = rel.split("/").first
31
- by_dir[dir][0] += pass
32
- by_dir[dir][1] += n
33
- file_ok += 1 if n.positive? && pass == n
34
- flag = n.zero? ? "—" : (pass == n ? "✓" : " ")
35
- printf(" %s %-46s %3d/%-3d\n", flag, rel, pass, n)
36
- results.reject(&:pass?).first(3).each { |r| puts " #{r.to_s[0, 110]}" }
37
- rescue => e
38
- blocked << rel
39
- puts " ✗ #{rel} (errored: #{e.class}: #{e.message[0, 80]})"
40
- end
41
-
42
- pct = ->(p, t) { t.zero? ? 0 : (100.0 * p / t).round(1) }
43
- puts
44
- by_dir.sort.each { |dir, (p, t)| printf(" %-8s %4d/%-5d (%.1f%%)\n", dir, p, t, pct.call(p, t)) }
45
- puts
46
- puts "WPT conformance: #{total_pass}/#{total} subtests (#{pct.call(total_pass, total)}%) across #{files.size} files; " \
47
- "#{file_ok} files fully green#{blocked.empty? ? "" : ", #{blocked.size} errored"}"
48
- end
49
- end