capybara-simulated 0.6.0 → 0.8.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.
@@ -71,7 +71,12 @@ module Capybara
71
71
  # level and inject them into every per-window Browser. Each
72
72
  # Browser still has its own sessionStorage + DOM + JS VM.
73
73
  @cookies = {}
74
+ @auth_cache = {}
74
75
  @local_storage = {}
76
+ # Cache Storage (caches/Cache) is origin-shared like localStorage — owned at the
77
+ # Driver level and injected, so a service worker and every same-origin window see
78
+ # the same caches (partitioned by origin key within the store).
79
+ @cache_storage = {}
75
80
  # Capture the universal-server flag ONCE, at session construction — the WPT
76
81
  # runner sets CSIM_LOCAL_ALL_HOSTS only while building the session, then
77
82
  # restores it. Every window (incl. aux windows opened later) inherits this so
@@ -100,7 +105,9 @@ module Capybara
100
105
  driver: self,
101
106
  js_engine: @js_engine,
102
107
  cookies: @cookies,
108
+ auth_cache: @auth_cache,
103
109
  local_storage: @local_storage,
110
+ cache_storage: @cache_storage,
104
111
  all_hosts_local: @all_hosts_local)
105
112
  end
106
113
 
@@ -274,11 +281,37 @@ module Capybara
274
281
  def visit(path) = current_browser.visit(path)
275
282
  def refresh = current_browser.refresh
276
283
  def reset!
284
+ reset_windows!
285
+ browser.reset!
286
+ end
287
+
288
+ # Dispose every auxiliary window and return focus to the primary — a fresh
289
+ # browsing context has no sibling windows. Disposing each aux Browser tears
290
+ # down its worker / SSE / websocket threads and its V8 isolate eagerly; left
291
+ # alone they pile into V8Runtime's process-wide `@@live` set and are only
292
+ # reclaimed by the at_exit hook, which on a long-lived multi-file session
293
+ # (the WPT runner) means a slow — sometimes minutes-long — process exit. Split
294
+ # out of `reset!` so a caller can drop windows WITHOUT resetting the primary's
295
+ # page state (the WPT runner rebuilds the primary itself, per file, via visit).
296
+ def reset_windows!
277
297
  @aux_windows.each {|w| w[:browser].dispose rescue nil }
278
298
  @aux_windows.clear
279
299
  @active_handle = nil
280
300
  @blob_partitions_lock.synchronize { @blob_partitions.clear }
281
- browser.reset!
301
+ end
302
+
303
+ # Full teardown of the whole driver: aux windows AND the primary browser's
304
+ # V8 isolate. `reset_windows!` deliberately keeps the primary alive (the
305
+ # per-test reset path rebuilds only its page); this is for permanently
306
+ # DROPPING a session. A caller that nils its session without this leaks the
307
+ # primary isolate — with its heap, canvas pixel buffers, and worker threads —
308
+ # into V8Runtime's process-wide `@@live` until at_exit. The WPT runner recycles
309
+ # the cross-origin session per `.sub.`/`.https.` file, so that leak is ~one
310
+ # isolate per cross-origin file (hundreds over the suite); disposing here
311
+ # incrementally is what reset_windows! already does for aux windows.
312
+ def dispose
313
+ reset_windows!
314
+ @browser.dispose rescue nil
282
315
  end
283
316
  def go_back = current_browser.go_back
284
317
  def go_forward = current_browser.go_forward
@@ -494,10 +527,30 @@ module Capybara
494
527
 
495
528
  # `BroadcastChannel.postMessage` — deliver to every OTHER window's channels
496
529
  # with the same name (same-window delivery is handled in-VM by the sender).
497
- def broadcast_channel(source_browser, name, data)
530
+ def broadcast_channel(source_browser, name, data, origin = nil)
531
+ # An opaque origin is unique to its own agent cluster; its key is a token ('opaque:…') minted
532
+ # per-realm and therefore only unique WITHIN one isolate — two unrelated opaque contexts in
533
+ # DIFFERENT windows could mint the same token. A BroadcastChannel never bridges two distinct
534
+ # opaque origins, and no opaque origin spans separate top-level windows here, so a cross-
535
+ # WINDOW post from an opaque origin reaches no one: drop it rather than risk a cross-isolate
536
+ # token collision. (Same-isolate opaque peers are reached in-VM / via enqueue_broadcast; an
537
+ # inherited-origin blob worker via its own inbox — neither goes through this cross-window path.)
538
+ return if origin.to_s.start_with?('opaque:')
539
+ window_entries.each do |w|
540
+ next if w[:browser].equal?(source_browser)
541
+ w[:browser].enqueue_broadcast(name, data, nil, origin)
542
+ end
543
+ end
544
+
545
+ # A localStorage change fans out to every OTHER window (localStorage spans same-origin
546
+ # browsing contexts). Every window shares the Driver's one `@local_storage` jar, so all
547
+ # windows are same-origin peers here (cross-origin storage partitioning is a separate
548
+ # backlog item); a nil source realm reaches each target's every realm. sessionStorage is
549
+ # per-context and never reaches this path.
550
+ def storage_broadcast(source_browser, kind, key, old, new, url)
498
551
  window_entries.each do |w|
499
552
  next if w[:browser].equal?(source_browser)
500
- w[:browser].enqueue_broadcast(name, data)
553
+ w[:browser].enqueue_storage_event(kind, key, old, new, url, nil)
501
554
  end
502
555
  end
503
556