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.
- checksums.yaml +4 -4
- data/README.md +8 -7
- data/lib/capybara/simulated/browser.rb +537 -33
- data/lib/capybara/simulated/driver.rb +42 -13
- data/lib/capybara/simulated/js/bridge.bundle.js +6606 -3888
- data/lib/capybara/simulated/node.rb +8 -0
- data/lib/capybara/simulated/quickjs_runtime.rb +22 -8
- data/lib/capybara/simulated/runtime_shared.rb +29 -28
- data/lib/capybara/simulated/v8_runtime.rb +69 -20
- data/lib/capybara/simulated/version.rb +1 -1
- metadata +1 -1
|
@@ -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
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
#
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
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(
|
|
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')
|
|
885
|
-
# cross-origin to
|
|
886
|
-
# "sandboxed srcdoc should not inherit")
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
@browser.
|
|
896
|
-
@browser.
|
|
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
|
-
|
|
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).
|