capybara-simulated 0.10.0 → 0.11.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.
@@ -253,24 +253,24 @@ module Capybara
253
253
  # precompiled bytecode. Partial in-VM resets carry the same
254
254
  # library-init-leak hazards V8Runtime documents.
255
255
  #
256
- # We don't `@vm&.dispose!` before swapping: per-visit rebuilds
257
- # happen on every spec example, and `dispose!` blocks on the
258
- # quickjs GC running with the GVL held. Ruby GC will eventually
259
- # reach the unreferenced VM and the gem's dfree handler frees
260
- # the JSRuntime. The transient C-heap growth between GCs is the
261
- # tradeoff for not paying ~hundreds of ms per spec.
256
+ # The OLD VM is disposed here, not left to Ruby's GC: a VM holding the bridge is ~40 MB of
257
+ # C heap behind a Ruby object a few hundred bytes big, so nothing prompts a collection and
258
+ # the dead VMs pile up a flatware worker climbed monotonically from 300 MB to 4.6 GB over
259
+ # one QuickJS gate, and 30 sessions of two visits each held 1945 MB (measured 2026-09-03).
260
+ # `dispose!` on a bridge-sized VM costs ~4 ms (the same thirty sessions: 3.69 s → 3.92 s,
261
+ # 515 MB), not the hundreds of milliseconds this used to fear.
262
262
  def rebuild_ctx
263
+ old = @vm
263
264
  @vm = build_vm
265
+ old&.dispose!
264
266
  end
265
267
 
266
268
  # Same operation as `rebuild_ctx` since per-visit rebuilds are
267
269
  # already the inter-test reset point.
268
270
  def reset_page = rebuild_ctx
269
271
 
270
- # PERMANENTLY drop this runtime. Distinct from `rebuild_ctx` above, which
271
- # deliberately does NOT `dispose!` that runs per visit, on every example,
272
- # and `dispose!` blocks on the quickjs GC with the GVL held. Here it runs
273
- # once, when a session is dropped for good, and the cost is the point.
272
+ # PERMANENTLY drop this runtime: the current VM goes the way every superseded one goes in
273
+ # `rebuild_ctx`, disposed rather than left to a GC that has no reason to run.
274
274
  #
275
275
  # This used to be intentionally absent, on the reasoning that Ruby GC's
276
276
  # dfree would reach an unreferenced `@vm` on its own. Measured, it does not
@@ -480,6 +480,7 @@ module Capybara
480
480
  resolved = browser.resolve_module_specifier(specifier, importer)
481
481
  body = browser.rack_fetch_body(resolved)
482
482
  return nil unless body
483
+ browser.note_module_fetch(resolved.to_s) # its Resource Timing entry ('script')
483
484
  # `.json` (and `?import` JSON) imports come from Vite's
484
485
  # `import.meta.glob` and `import x from './data.json'`
485
486
  # patterns. quickjs.rb's loader passes the source through
@@ -43,6 +43,7 @@ module Capybara
43
43
  BROWSER_HOST_FNS = {
44
44
  '__rackFetch' => ->(b, *a) { b.rack_fetch(a[0], a[1], a[2], a[3], a[4], a[5], credentials: a[6] || 'same-origin', referrer_policy: a[7], referrer: a[8], cache_mode: a[9] || 'default', initiator: a[10], site_seed: a[11], origin_null: a[12], client_url: a[13], cookie_cross_site: a[14] == true, nav_dest: a[15]) },
45
45
  '__csimExternalAsset' => ->(b, *a) { b.external_asset_source(a[0]) },
46
+ '__csimExternalAssetMeta' => ->(b, *a) { b.external_asset_meta(a[0]) },
46
47
  # fetch(…, {keepalive}) — eager detached-thread dispatch + one-shot result poll.
47
48
  '__csim_keepaliveStart' => ->(b, *a) { b.keepalive_start(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10]) },
48
49
  '__csim_keepaliveTake' => ->(b, *a) { b.keepalive_take(a[0]) },
@@ -229,6 +230,12 @@ module Capybara
229
230
  '__csim_decodeImage' => ->(b, *a) { b.decode_image(a[0], a[1], a[2]) },
230
231
  '__csim_renderText' => ->(b, *a) { b.render_text(a[0], a[1], a[2], a[3], a[4]) },
231
232
  '__csim_fontAdvances' => ->(b, *a) { b.font_advance_table(a[0], a[1]) },
233
+ '__csim_fontAdvancesFromUrl' => ->(b, *a) { b.font_advance_table_from_url(a[0]) },
234
+ '__csim_localFontTable' => ->(b, *a) { b.local_font_table(a[0], a[1] || '') },
235
+ '__csim_resourceTimingFetch' => ->(b, *a) { b.resource_timing_fetch(a[0], a[1] == true, a[2] || 'same-origin') },
236
+ '__csim_takeModuleRt' => ->(b, *a) { b.take_module_rt },
237
+ '__csim_takeWorkerRt' => ->(b, *a) { b.take_worker_rt },
238
+ '__csim_fontAdvancesFromBytes' => ->(b, *a) { b.font_advance_table_from_bytes(a[0]) },
232
239
  '__csim_loadImage' => ->(b, *a) { b.load_image(a[0], !!a[1], a[2] || 'same-origin') },
233
240
  '__csim_imageLoadStart' => ->(b, *a) { b.image_load_start(a[0], !!a[1], a[2] || 'same-origin') },
234
241
  '__csim_blobRegister' => ->(b, *a) { b.blob_register(a[0], a[1], a[2]); nil },
@@ -1169,7 +1169,10 @@ module Capybara
1169
1169
  return handles[url] = nil if r && r['blocked'] # respondWith failed the load
1170
1170
  src = r && r['body']
1171
1171
  end
1172
- src ||= @browser.rack_fetch_body(url_s)
1172
+ if src.nil?
1173
+ src = @browser.rack_fetch_body(url_s)
1174
+ @browser.note_module_fetch(url_s) if src # its Resource Timing entry ('script')
1175
+ end
1173
1176
  return handles[url] = nil unless src
1174
1177
  body = module_body(url_s, src)
1175
1178
  # No-cd warm path: once this isolate has compiled a URL, its in-memory
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Simulated
5
- VERSION = '0.10.0'
5
+ VERSION = '0.11.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara-simulated
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keita Urashima
@@ -65,6 +65,20 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '2.2'
68
+ - !ruby/object:Gem::Dependency
69
+ name: brotli
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.5'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.5'
68
82
  description: A Capybara driver that runs JavaScript against an in-process JS-resident
69
83
  DOM — V8 via rusty_racer or QuickJS via quickjs.rb, whichever is installed. No Chrome,
70
84
  no Node toolchain. Forms submit through Rack::MockRequest, inline <script> + event