capybara-simulated 0.4.0 → 0.5.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.
@@ -264,10 +264,10 @@ module Capybara
264
264
  # matches an existing window navigates that window instead of opening a
265
265
  # new one (HTML window-name targeting); `opener_handle` records the
266
266
  # opener so the new window's `window.opener` resolves back to it.
267
- def open_aux_window(url = nil, name: nil, opener_handle: nil)
267
+ def open_aux_window(url = nil, name: nil, opener_handle: nil, source: nil, blob_snapshot: nil)
268
268
  name = name.to_s
269
269
  if !name.empty? && (existing = @aux_windows.find {|w| w[:name] == name })
270
- navigate_window(existing[:browser], url)
270
+ navigate_window(existing[:browser], url, source: source)
271
271
  return existing[:handle]
272
272
  end
273
273
  @next_window_seq += 1
@@ -278,7 +278,15 @@ module Capybara
278
278
  # `window.opener`, which resolves through this entry — so the entry
279
279
  # (with its opener) must exist before `visit` runs those scripts.
280
280
  @aux_windows << {handle: handle, browser: aux, name: name, opener: opener_handle}
281
- aux.visit(url) if url && !url.empty?
281
+ if url && !url.empty?
282
+ # A blob: URL isn't rack-navigable and its bytes live in the OPENER's
283
+ # isolate — load the document directly from a click-time snapshot (a
284
+ # deferred target=_blank nav may revoke the URL first) or, failing that,
285
+ # the opener's blob store.
286
+ unless url.to_s.start_with?('blob:') && load_blob_into_window(aux, url, source, snapshot: blob_snapshot)
287
+ aux.visit(url)
288
+ end
289
+ end
282
290
  handle
283
291
  rescue StandardError => e
284
292
  # Aux window URL-load failure (binary content, network error, …)
@@ -298,7 +306,23 @@ module Capybara
298
306
  # against the opener's document and records the opener relationship.
299
307
  def open_window_from_js(opener_browser, url, name)
300
308
  resolved = url.to_s.empty? ? nil : opener_browser.resolve_document_url(url)
301
- open_aux_window(resolved, name: name, opener_handle: handle_for(opener_browser))
309
+ open_aux_window(resolved, name: name, opener_handle: handle_for(opener_browser), source: opener_browser)
310
+ end
311
+
312
+ # Load a blob: document into aux window `aux` from `source`'s (the opener's)
313
+ # local blob store — the bytes live in the opener's isolate, not the aux's.
314
+ # Returns false if `source`/bytes are unavailable (caller falls back).
315
+ private def load_blob_into_window(aux, url, source, snapshot: nil)
316
+ data = if snapshot.is_a?(Hash) && snapshot['b64']
317
+ { bytes: Base64.decode64(snapshot['b64'].to_s), type: snapshot['type'].to_s }
318
+ elsif source.respond_to?(:read_blob_for_window)
319
+ source.read_blob_for_window(url)
320
+ end
321
+ return false unless data
322
+ aux.boot_blob_document(url, data[:bytes], data[:type])
323
+ true
324
+ rescue StandardError
325
+ false
302
326
  end
303
327
 
304
328
  # `targetWindow.postMessage(data, origin)` — queue on the target window's
@@ -308,10 +332,25 @@ module Capybara
308
332
  target.enqueue_window_message(data, _origin, handle_for(source_browser))
309
333
  end
310
334
 
335
+ # `BroadcastChannel.postMessage` — deliver to every OTHER window's channels
336
+ # with the same name (same-window delivery is handled in-VM by the sender).
337
+ def broadcast_channel(source_browser, name, data)
338
+ window_entries.each do |w|
339
+ next if w[:browser].equal?(source_browser)
340
+ w[:browser].enqueue_broadcast(name, data)
341
+ end
342
+ end
343
+
311
344
  def window_location(handle) = (window_browser(handle)&.current_url).to_s
345
+ # A cross-window property read (`win.foo` / `win.document.foo`) — read the
346
+ # primitive off the target window's VM.
347
+ def window_read(handle, prop, doc: false)
348
+ b = window_browser(handle) or return nil
349
+ b.read_property(prop, doc: doc)
350
+ end
312
351
  def window_set_location(handle, url)
313
352
  b = window_browser(handle) or return
314
- navigate_window(b, b.resolve_document_url(url))
353
+ navigate_window(b, b.resolve_document_url(url), source: current_browser)
315
354
  end
316
355
  def window_closed?(handle) = window_browser(handle).nil?
317
356
  def opener_handle_of(browser)
@@ -326,8 +365,15 @@ module Capybara
326
365
  # navigating it synchronously (`visit` → `rebuild_ctx`) would dispose the
327
366
  # V8 context mid-call and abort the running handler. A non-active window
328
367
  # can navigate immediately (its VM isn't on the stack).
329
- private def navigate_window(browser, url)
368
+ private def navigate_window(browser, url, source: nil)
330
369
  return if url.nil? || url.to_s.empty?
370
+ # A blob: navigation loads directly from the opener's blob bytes (not
371
+ # rack-navigable, cross-isolate). Only when the target isn't the active
372
+ # window (boot rebuilds its ctx — unsafe mid-call on the running window).
373
+ if url.to_s.start_with?('blob:') && !browser.equal?(current_browser) &&
374
+ load_blob_into_window(browser, url, source)
375
+ return
376
+ end
331
377
  if browser.equal?(current_browser)
332
378
  browser.location_assign(url)
333
379
  else