capybara-simulated 0.8.0 → 0.9.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.
@@ -138,6 +138,14 @@ module Capybara
138
138
  self
139
139
  end
140
140
 
141
+ # Capybara's `Element#scroll_to(:current, offset: [dx, dy])` calls this after the position
142
+ # handling above — a relative scroll from wherever the element is now.
143
+ def scroll_by(dx, dy)
144
+ check_stale
145
+ browser.scroll_by(handle_id, dx, dy)
146
+ self
147
+ end
148
+
141
149
  # Capybara's standard rect API. No layout engine — but
142
150
  # The element's coarse border-box (viewport-relative), from the layout engine — backs the
143
151
  # spatial selectors (`:above`/`:below`/`:near`) and coordinate drag. Deterministic per layout
@@ -246,14 +246,27 @@ module Capybara
246
246
  # already the inter-test reset point.
247
247
  def reset_page = rebuild_ctx
248
248
 
249
- # NOTE: intentionally NO `dispose` Browser#dispose gates its
250
- # `@runtime.dispose` call on `respond_to?(:dispose)`, so QuickJS skips it.
251
- # QuickJS VMs aren't pinned by a process-wide registry the way V8 isolates
252
- # are in V8Runtime's `@@live`; an aux window's Browser becomes unreferenced
253
- # on close and Ruby GC's dfree frees its `@vm` (the same lifecycle
254
- # `rebuild_ctx` already relies on). Adding a `@vm = nil` dispose would only
255
- # introduce a NoMethodError window for any stray post-close call (eval/call
256
- # don't all nil-guard `@vm`) with no leak benefit.
249
+ # PERMANENTLY drop this runtime. Distinct from `rebuild_ctx` above, which
250
+ # deliberately does NOT `dispose!` that runs per visit, on every example,
251
+ # and `dispose!` blocks on the quickjs GC with the GVL held. Here it runs
252
+ # once, when a session is dropped for good, and the cost is the point.
253
+ #
254
+ # This used to be intentionally absent, on the reasoning that Ruby GC's
255
+ # dfree would reach an unreferenced `@vm` on its own. Measured, it does not
256
+ # in time to matter: 30 sessions created and dropped in a loop held 1979 MB
257
+ # with no dispose and 1971 MB with one, because `Browser#dispose` gates on
258
+ # `respond_to?(:dispose)` and so did nothing at all here. The suite's peak
259
+ # RSS is what pays for it.
260
+ #
261
+ # The old note's hazard — a stray post-close `eval`/`call` finding `@vm`
262
+ # nil — is real, so this leaves `@vm` in place and lets the gem's own
263
+ # freed-VM handling answer; `Driver#disposed?` keeps the live-driver walk
264
+ # from stepping a dropped runtime in the first place.
265
+ def dispose
266
+ @vm&.dispose!
267
+ rescue StandardError
268
+ nil
269
+ end
257
270
 
258
271
  # bridge.js patches `Intl.DateTimeFormat`; rusty_racer ships ICU built-in but
259
272
  # QuickJS gates it behind a polyfill flag (other surfaces bridge.js touches —
@@ -395,6 +408,7 @@ module Capybara
395
408
  vm.define_function('__csimBroadcast') {|name, data, _rid, origin| broadcast_out&.call(name, data, origin); nil } if broadcast_out
396
409
  # Service-worker → main-thread signals via the outbox (see v8_runtime#build_worker).
397
410
  vm.define_function('__csim_swPostToClient') {|client_id, data| sw_hooks[:post_to_client]&.call(client_id, data); nil } if sw_hooks[:post_to_client]
411
+ vm.define_function('__csim_swFocusClient') {|client_id| sw_hooks[:focus_client]&.call(client_id); nil } if sw_hooks[:focus_client]
398
412
  vm.define_function('__csim_swClaim') { sw_hooks[:claim]&.call; nil } if sw_hooks[:claim]
399
413
  vm.define_function('__csim_swFetchRespond') {|fetch_id, resp, realm_id| sw_hooks[:fetch_respond]&.call(fetch_id, resp, realm_id); nil } if sw_hooks[:fetch_respond]
400
414
  vm.define_function('__csim_swFetchStream') {|fetch_id, kind, payload, realm_id| sw_hooks[:fetch_stream]&.call(fetch_id, kind, payload, realm_id); nil } if sw_hooks[:fetch_stream]
@@ -67,8 +67,10 @@ module Capybara
67
67
  # isHistoryNavigation. Must run while the outgoing realm (a[0]) is still alive.
68
68
  '__csim_recordFrameNav' => ->(b, *a) { b.record_frame_nav(a[0].to_i, a[1]); nil },
69
69
  '__setTimersActive' => ->(b, *a) { b.timers_active = !!a[0]; nil },
70
- '__setCurrentUrl' => ->(b, *a) { b.history_state(a[0], a[1]); nil },
71
- '__pushHistoryEntry' => ->(b, *a) { b.history_push(a[0], a[1]); nil },
70
+ # a[2] is the realm that navigated: a nested browsing context keeps its OWN session
71
+ # history, so a frame's pushState must not be mirrored onto the top document's.
72
+ '__setCurrentUrl' => ->(b, *a) { b.history_state(a[0], a[1], a[2]); nil },
73
+ '__pushHistoryEntry' => ->(b, *a) { b.history_push(a[0], a[1], a[2]); nil },
72
74
  '__historyGo' => ->(b, *a) { b.history_go(a[0]); nil },
73
75
  '__historyLength' => ->(b, *_) { b.history_length },
74
76
  '__csimReadFilePick' => ->(b, *a) { b.read_file_pick(a[0], a[1], a[2], a[3]) },
@@ -107,8 +109,9 @@ module Capybara
107
109
  '__csim_rackFetchAsyncAbort' => ->(b, *a) { b.rack_fetch_async_abort(a[0]); nil },
108
110
  # Cross-window references (window.open / opener / postMessage). A separate-VM
109
111
  # aux window forwards to the Driver; a same-origin window realm lives in this
110
- # isolate. a[2] is the opener's realm id (for wiring window.opener).
111
- '__csimWindowOpen' => ->(b, *a) { b.open_child_window(a[0], a[1], a[2]) },
112
+ # isolate. a[2] is the opener's realm id (for wiring window.opener); a[3]/a[4] the opener
113
+ # document's base URL and origin, which an about:blank popup inherits.
114
+ '__csimWindowOpen' => ->(b, *a) { b.open_child_window(a[0], a[1], a[2], a[3], a[4]) },
112
115
  # A `target=_blank`/named link/area activation from a frame or window realm:
113
116
  # open a new auxiliary window (the realm's VM isn't rebuilt — a fresh window
114
117
  # is). `opener` = rel=opener (target=_blank defaults to noopener); the Driver
@@ -140,7 +143,10 @@ module Capybara
140
143
  # Fire an aux window's OWN `load` event (in its VM) — deferred by the
141
144
  # opener so a child `window.onload` runs after the opener's current task.
142
145
  '__csimFireAuxWindowLoad' => ->(b, *a) { b.fire_aux_window_load(a[0]); nil },
143
- '__csim_workerSpawn' => ->(b, *a) { b.worker_spawn(a[0], shared: !!a[1], creator_key: a[2]) },
146
+ # a[3] is the CREATING realm (the worker dies when it is discarded); a[4] that context's
147
+ # CURRENT controller handle, which a DEDICATED worker inherits rather than scope-matching
148
+ # its own — often opaque (blob:/data:) — script URL. 0 when the creator is uncontrolled.
149
+ '__csim_workerSpawn' => ->(b, *a) { b.worker_spawn(a[0], shared: !!a[1], creator_key: a[2], realm_id: a[3].to_i, controller_handle: a[4].to_i) },
144
150
  # navigator.serviceWorker.register (universal-server only) — spawn a worker
145
151
  # running the SW script as an executor context. Returns its handle.
146
152
  '__csim_serviceWorkerRegister' => ->(b, *a) { b.worker_spawn(a[0], service: true, creator_key: a[1]) },
@@ -151,6 +157,13 @@ module Capybara
151
157
  # relay a client-realm port's postMessage to its remote (worker/SW) peer.
152
158
  '__csimClientPortEndpoint' => ->(b, *a) { b.port_channel_endpoint_realm(a[0], a[1]); nil },
153
159
  '__csimClientPortPost' => ->(b, *a) { b.client_port_post(a[0], a[1]); nil },
160
+ # The focus chain moved into this realm's browsing context (a focus() commit, or an
161
+ # <iframe> focused in its container, which hands focus to the nested context).
162
+ '__csimNoteFocusedRealm' => ->(b, *a) { b.note_focused_realm(a[0]); nil },
163
+ # This realm is a service-worker client: reported at document load and again whenever
164
+ # control is installed, by the realm, because only it knows its own URL, frame type and
165
+ # controller. `a[3]` is the controlling worker's handle, 0 when uncontrolled.
166
+ '__csimNoteClient' => ->(b, *a) { b.sw_note_client(a[0], a[1], a[2], a[3]); nil },
154
167
  # A controlled client's fetch → the controlling SW's `fetch` event. Returns false if the SW
155
168
  # is gone (client falls back to the network).
156
169
  '__csim_serviceWorkerControllerFetch' => ->(b, *a) { b.service_worker_controller_fetch(a[0], a[1], a[2], a[3]) },
@@ -165,6 +178,10 @@ module Capybara
165
178
  # local registration (a different iframe registering an already-active scope) can synthesize a
166
179
  # registration reflecting the shared active worker instead of installing a duplicate.
167
180
  '__csim_swActiveHandleForScope' => ->(b, *a) { b.sw_active_handle_for_scope(a[0]) },
181
+ # Does this worker still control any client? An incoming worker may only activate once the
182
+ # outgoing one controls nothing (or skipWaiting is called) — see _scheduleLifecycle.
183
+ '__csim_swControlsClients' => ->(b, *a) { b.sw_worker_controls_clients?(a[0]) },
184
+ '__csim_swNoteActivationParked' => ->(b, *_) { b.sw_note_activation_parked; nil },
168
185
  # Navigation Preload state (NavigationPreloadManager), keyed by the registration's active
169
186
  # worker handle — reached identically from the client (registration.active._handle) and the
170
187
  # worker (__csimWorkerHandle). Get returns {enabled, headerValue}; set leaves a nil field as-is.
@@ -227,34 +244,18 @@ module Capybara
227
244
  # Host fns that route to pure stdlib — no Browser surface,
228
245
  # nothing to safe_call, no allocation needed for the wrap. Skip
229
246
  # the rescue overhead on every per-find / per-event invocation.
230
- # Process-wide cascade-rule cache (mirrors the script bytecode cache). The
231
- # built {hide, layout} rules are deterministic per (stylesheet-set,
232
- # viewport), so the JS side caches the serialized rules keyed by a digest of
233
- # the sheet sources and skips the ~12-15 ms css-tree parse + per-rule
234
- # specificity + terminalKey rebuild on every per-visit VM rebuild. Lives in
235
- # Ruby (not the VM) so it survives `rebuild_ctx`. Key space is tiny (one app
236
- # ships one stylesheet set), so the map stays small; no eviction needed.
237
- CASCADE_RULE_CACHE = {}
238
- CASCADE_RULE_CACHE_MUTEX = Mutex.new
239
-
240
- # Process-wide PER-SHEET parse cache (companion to CASCADE_RULE_CACHE). The
241
- # built whole-cascade is cached above, but it misses whenever a page's inline
242
- # `<style>` changes (Avo injects per-page styles), forcing a rebuild that
243
- # re-parses every sheet — including unchanged linked bundles (avo.base.css).
244
- # `parseSheet` is pure, so the JS side caches its serialized `{hide,layout}`
245
- # keyed by (cssText hash, viewport) here, surviving the per-visit VM rebuild
246
- # that wipes the in-VM `__sheetCache` — the CSS analogue of the JS bytecode
247
- # cache. Keyed by content, so a content change yields a new key. Capped.
247
+ # Process-wide PER-SHEET parse cache (the CSS analogue of the JS bytecode
248
+ # cache). `parseSheet` is pure, so the JS side caches its serialized
249
+ # `{hide,layout}` here keyed by (cssText hash, viewport), surviving the
250
+ # per-visit VM rebuild that wipes the in-VM `__sheetCache`. A cascade
251
+ # rebuild then re-parses only sheets it has never seen (content change =
252
+ # new key). Content-keyed ONLY never url-keyed so freshness stays the
253
+ # asset cache's call. Capped.
248
254
  SHEET_PARSE_CACHE = {}
249
255
  SHEET_PARSE_CACHE_MUTEX = Mutex.new
250
256
  SHEET_PARSE_CACHE_MAX = 2048
251
257
 
252
258
  STDLIB_HOST_FNS = {
253
- '__csimCascadeCacheGet' => ->(*a) { CASCADE_RULE_CACHE_MUTEX.synchronize { CASCADE_RULE_CACHE[a[0].to_s] } },
254
- '__csimCascadeCachePut' => lambda {|*a|
255
- CASCADE_RULE_CACHE_MUTEX.synchronize { CASCADE_RULE_CACHE[a[0].to_s] = a[1].to_s }
256
- nil
257
- },
258
259
  '__csimSheetCacheGet' => ->(*a) { SHEET_PARSE_CACHE_MUTEX.synchronize { SHEET_PARSE_CACHE[a[0].to_s] } },
259
260
  '__csimSheetCachePut' => lambda {|*a|
260
261
  SHEET_PARSE_CACHE_MUTEX.synchronize {
@@ -426,6 +426,12 @@ module Capybara
426
426
  # through its own navigation.
427
427
  def window_realm_meta = (@window_realm_meta ||= {})
428
428
 
429
+ # Is this realm a TOP-LEVEL browsing context? The main realm is, and so is an auxiliary
430
+ # window realm — `frame_realm_parents` records 0 for one because it has no parent FRAME,
431
+ # which is not the same as being nested in the main window. An ancestor walk (the focus
432
+ # chain, most of all) has to stop here rather than step into the opener.
433
+ def top_level_realm?(realm_id) = realm_id.to_i.zero? || window_realm_meta.key?(realm_id.to_i)
434
+
429
435
  def dispose_frame_realms
430
436
  @realm_module_handles&.clear
431
437
  @frame_realm_depths&.clear
@@ -471,7 +477,11 @@ module Capybara
471
477
  # Like reload_frame_realm, a re-navigated window discards its child browsing contexts — dispose
472
478
  # the descendant frame realms too, not just old_id, so a popup's iframes don't linger.
473
479
  dispose_frame_realm_tree(old_id)
474
- create_window_realm(url, body, content_type, opener_id: meta[:opener_id], window_name: meta[:window_name])
480
+ create_window_realm(
481
+ url, body, content_type,
482
+ opener_id: meta[:opener_id], window_name: meta[:window_name],
483
+ about_base: meta[:about_base], about_origin: meta[:about_origin]
484
+ )
475
485
  end
476
486
 
477
487
  # Tear down a single frame realm (e.g. a descendant frame destroyed when an
@@ -483,6 +493,11 @@ module Capybara
483
493
  @browser.revoke_realm_blobs(id) rescue nil
484
494
  # Drop it from the SW Client registry so matchAll stops returning a dead client.
485
495
  @browser.sw_unregister_client(id) rescue nil
496
+ # A dedicated worker is owned by the context that created it — discarding the context
497
+ # terminates it (and unregisters its own client record).
498
+ @browser.terminate_realm_workers(id) rescue nil
499
+ # If it held the focus chain, focus returns to the top-level browsing context.
500
+ @browser.note_realm_discarded(id) rescue nil
486
501
  @realm_module_handles&.delete(id)
487
502
  @frame_realm_depths&.delete(id)
488
503
  @frame_realm_parents&.delete(id)
@@ -753,8 +768,8 @@ module Capybara
753
768
  # id (or nil on failure — then the bridge keeps its same-realm fallback).
754
769
  # The bridge maps `iframe.contentWindow` to `RustyRacer.contextGlobal(id)`.
755
770
  def attach_frame_realm_loader(c)
756
- c.attach('__csim_createFrameRealm', ->(url, body, content_type, parent_id = 0, frame_name = nil, frame_doc_origin = nil, frame_location_origin = nil, js_url_source = nil, frame_about_base = nil) {
757
- RuntimeShared.safe_call { create_frame_realm(c, url, body, content_type, parent_id, frame_name, frame_doc_origin, frame_location_origin, js_url_source, frame_about_base) }
771
+ c.attach('__csim_createFrameRealm', ->(url, body, content_type, parent_id = 0, frame_name = nil, frame_doc_origin = nil, frame_location_origin = nil, js_url_source = nil, frame_about_base = nil, frame_viewport = nil) {
772
+ RuntimeShared.safe_call { create_frame_realm(c, url, body, content_type, parent_id, frame_name, frame_doc_origin, frame_location_origin, js_url_source, frame_about_base, frame_viewport) }
758
773
  })
759
774
  # Re-navigating an iframe (src/srcdoc reassigned) builds a fresh realm;
760
775
  # the bridge calls this to tear down the superseded one so it doesn't
@@ -806,7 +821,7 @@ module Capybara
806
821
  realm
807
822
  end
808
823
 
809
- def create_frame_realm(parent_ctx, url, body, content_type, parent_id = 0, frame_name = nil, frame_doc_origin = nil, frame_location_origin = nil, js_url_source = nil, frame_about_base = nil)
824
+ def create_frame_realm(parent_ctx, url, body, content_type, parent_id = 0, frame_name = nil, frame_doc_origin = nil, frame_location_origin = nil, js_url_source = nil, frame_about_base = nil, frame_viewport = nil)
810
825
  depth = (frame_realm_depths[parent_id] || 0) + 1
811
826
  if depth > MAX_FRAME_DEPTH
812
827
  @browser.log_console('warn', "iframe nesting depth #{depth} exceeds #{MAX_FRAME_DEPTH}; not building #{url}")
@@ -858,6 +873,11 @@ module Capybara
858
873
  # loads, so a frame whose load handler reads window.name to identify itself
859
874
  # (declarative-shadow declarative-child-frame) sees it.
860
875
  realm.call('__csimSetWindowName', frame_name.to_s) unless frame_name.nil?
876
+ # The frame's viewport is its container's content box, measured by the parent realm. Seed it
877
+ # BEFORE the document loads so load-time `innerWidth` reads and `@media` evaluation see the
878
+ # frame's own size rather than the top window's. `nil` = an unrendered container, which is a
879
+ # 0x0 window — pass it through rather than skipping, or the frame keeps the top-level size.
880
+ realm.call('__csimFrameViewportChanged', frame_viewport)
861
881
  # Seed the frame's document origin (opaque/inherited) BEFORE the document
862
882
  # loads, so its load-time scripts read the right self.origin. nil → a
863
883
  # real-URL frame whose origin is its own location origin.
@@ -881,21 +901,31 @@ module Capybara
881
901
  # (sw_note_realm_controller); recording it BEFORE the load also lets a NESTED frame built during
882
902
  # this frame's load inherit the controller.
883
903
  opaque = opaque_frame_url?(url)
884
- # A sandboxed frame WITHOUT allow-same-origin has an OPAQUE origin (doc origin 'null') and is
885
- # cross-origin to its creator, so it does NOT inherit the creator's controller (srcdoc-iframe:
886
- # "sandboxed srcdoc should not inherit"). allow-same-origin restores the parent origin → inherits.
887
- inheritable = opaque && frame_doc_origin.to_s != 'null'
888
- ctrl = @browser.sw_client_controller_for(url.to_s)
889
- ctrl ||= @browser.sw_inherited_controller_for(parent_id) if inheritable
890
- if ctrl
891
- realm.call('__csim_swSetControllerDirect', *ctrl)
892
- # Only an OPAQUE (about:blank / srcdoc) frame is mirrored into the SW's clients.matchAll():
893
- # a real-URL frame's registration would fire during its SYNCHRONOUS SW nav-fetch (main thread
894
- # blocked on @sw_nav_outbox), perturbing worker timing — the http-src client model is deferred.
895
- @browser.sw_note_realm_controller(realm.id, ctrl)
896
- @browser.sw_register_client(realm.id, url.to_s, 'window', 'nested', ctrl[0]) if opaque
904
+ # A sandboxed frame WITHOUT allow-same-origin has an OPAQUE origin (doc origin 'null'), which
905
+ # is cross-origin to everything so it is NOT CONTROLLED AT ALL. It inherits no controller
906
+ # from its creator (srcdoc-iframe: "sandboxed srcdoc should not inherit"), its subresource
907
+ # fetches never reach the SW, and it is absent from `clients.matchAll()`. Excluding it from
908
+ # only the client registry would not hold: a controlled frame's first subresource fetch
909
+ # re-registers it lazily from the fetch event (js/src/workers.js clientFor). The SW does still
910
+ # answer its NAVIGATION request, because a CSP-header sandbox is knowable only FROM that
911
+ # response (sandboxed-iframe-fetch-event, "Service worker should NOT control the sandboxed
912
+ # page"). allow-same-origin restores the parent origin, and a real-URL frame carries no
913
+ # explicit origin at all (nil = its own location origin) both are controlled normally.
914
+ if frame_doc_origin.to_s != 'null'
915
+ ctrl = @browser.sw_client_controller_for(url.to_s)
916
+ ctrl ||= @browser.sw_inherited_controller_for(parent_id) if opaque
917
+ if ctrl
918
+ # Wiring the controller is what registers this frame as a client — the realm reports
919
+ # itself from installController (js/src/sw-client.js), before the document below loads.
920
+ realm.call('__csim_swSetControllerDirect', *ctrl)
921
+ @browser.sw_note_realm_controller(realm.id, ctrl)
922
+ end
897
923
  end
898
924
  realm.call('__csimLoadDocument', body.to_s, content_type.to_s)
925
+ # This document now has a URL and any host-wired controller, so it can announce itself
926
+ # as a service-worker client — an UNCONTROLLED context is still a client of its origin
927
+ # and must show up in `matchAll({includeUncontrolled: true})`.
928
+ realm.call('__csim_swReportClient') rescue nil
899
929
  # A `javascript:` URL frame: the initial empty document is now loaded and
900
930
  # parent/top are wired, so evaluate the URL's script in the realm (global
901
931
  # scope). Per HTML, only a STRING result navigates the frame to a new
@@ -944,7 +974,7 @@ module Capybara
944
974
  # a native `__csimFrameWindowProxyFor`, so `popup.document` is a real
945
975
  # same-isolate Document (cross-window adoptNode works). The single isolate also
946
976
  # makes a same-origin window far cheaper than today's isolate-per-window.
947
- def create_window_realm(url, body, content_type, opener_id: nil, window_name: nil, doc_origin: nil, location_origin: nil)
977
+ def create_window_realm(url, body, content_type, opener_id: nil, window_name: nil, doc_origin: nil, location_origin: nil, about_base: nil, about_origin: nil)
948
978
  realm = seed_realm_bridge(ctx.create_context)
949
979
  # Mark it a top-level window realm so its location setter routes to
950
980
  # __csimWindowRealmNavigate (reload THIS realm) rather than the frame-nav or
@@ -976,7 +1006,21 @@ module Capybara
976
1006
  }
977
1007
  JS
978
1008
  end
979
- realm.call('__csimUpdateLocation', url.to_s) unless url.to_s.empty?
1009
+ # `window.open()` with no URL opens about:blank — that IS the new document's URL, and a
1010
+ # realm built from the snapshot would otherwise keep reporting the OPENER's (making the
1011
+ # popup a phantom duplicate of its opener everywhere a document URL is read, the service-
1012
+ # worker client set included). Its ORIGIN and its BASE url are inherited from the opener,
1013
+ # exactly as an empty <iframe>'s are (create_frame_realm's frame_about_base): the origin
1014
+ # so the popup isn't cross-origin to the window that opened it, the base so relative
1015
+ # resolution inside it still targets the opener's document.
1016
+ if url.to_s.empty?
1017
+ realm.call('__csimUpdateLocation', 'about:blank')
1018
+ realm.call('__csimSetAboutBaseURL', about_base.to_s) unless about_base.to_s.empty?
1019
+ doc_origin ||= about_origin unless about_origin.to_s.empty?
1020
+ location_origin ||= about_origin unless about_origin.to_s.empty?
1021
+ else
1022
+ realm.call('__csimUpdateLocation', url.to_s)
1023
+ end
980
1024
  realm.call('__csimSetWindowName', window_name.to_s) unless window_name.nil?
981
1025
  realm.call('__csimSetDocumentOrigin', doc_origin.to_s) unless doc_origin.nil?
982
1026
  realm.call('__csimSetLocationOrigin', location_origin.to_s) unless location_origin.nil?
@@ -995,8 +1039,10 @@ module Capybara
995
1039
  # Remember the window's opener / name so a self-navigation (reload_window_realm
996
1040
  # builds a FRESH realm) can carry them across — a real popup keeps window.opener
997
1041
  # and window.name through its own navigation.
998
- window_realm_meta[realm.id] = {opener_id: opener_id, window_name: window_name}
1042
+ window_realm_meta[realm.id] = {opener_id: opener_id, window_name: window_name, about_base: about_base, about_origin: about_origin}
999
1043
  realm.call('__csimLoadDocument', body.to_s, content_type.to_s)
1044
+ # As in create_frame_realm: an auxiliary window is a client of its origin too.
1045
+ realm.call('__csim_swReportClient') rescue nil
1000
1046
  realm.call('__csimFireWindowLoad') rescue nil
1001
1047
  realm.id
1002
1048
  rescue StandardError => e
@@ -1375,7 +1421,10 @@ module Capybara
1375
1421
  # deliver_worker_messages): client.postMessage, clients.claim (set the client's controller),
1376
1422
  # and a controlled fetch's respondWith result. See run_worker for the closures.
1377
1423
  c.attach('__csim_swPostToClient', ->(client_id, data) { sw_hooks[:post_to_client]&.call(client_id, data); nil }) if sw_hooks[:post_to_client]
1424
+ c.attach('__csim_swFocusClient', ->(client_id) { sw_hooks[:focus_client]&.call(client_id); nil }) if sw_hooks[:focus_client]
1425
+ c.attach('__csim_swNavigateClient', ->(client_id, url, nav_id) { sw_hooks[:navigate_client]&.call(client_id, url, nav_id); nil }) if sw_hooks[:navigate_client]
1378
1426
  c.attach('__csim_swClaim', -> { sw_hooks[:claim]&.call; nil }) if sw_hooks[:claim]
1427
+ c.attach('__csim_swSkipWaitingRequest', -> { sw_hooks[:skip_waiting]&.call; nil }) if sw_hooks[:skip_waiting]
1379
1428
  c.attach('__csim_swFetchRespond', ->(fetch_id, resp, realm_id) { sw_hooks[:fetch_respond]&.call(fetch_id, resp, realm_id); nil }) if sw_hooks[:fetch_respond]
1380
1429
  c.attach('__csim_swFetchStream', ->(fetch_id, kind, payload, realm_id) { sw_hooks[:fetch_stream]&.call(fetch_id, kind, payload, realm_id); nil }) if sw_hooks[:fetch_stream]
1381
1430
  # Cross-isolate MessagePort channel signals (a worker/SW port endpoint + its outbound messages).
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Simulated
5
- VERSION = '0.8.0'
5
+ VERSION = '0.9.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.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keita Urashima