Class: Dommy::Rack::SessionRuntime

Inherits:
Object
  • Object
show all
Defined in:
lib/dommy/rack/session_runtime.rb

Overview

Binds a JS runtime to a Session: each HTML document the session loads gets its own JS realm (window globals, listeners, timers), its <script> tags boot, and window.fetch / external scripts resolve through the session's Rack app (shared cookie jar). Subscribes to the session's on_document_loaded seam so VM lifetime follows page loads.

The JS engine is pluggable: realms are built through Dommy::Js.build_runtime, so any registered backend (QuickJS via dommy-js-quickjs, others later) drives the session. This is the engine behind Dommy::Rack::Session.new(app, javascript: true) and the Capybara driver's JS support — one realm manager, two front ends.

Constant Summary collapse

PUMP_SLICE_MS =
50

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(session, &current_document) ⇒ SessionRuntime

Returns a new instance of SessionRuntime.



26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/dommy/rack/session_runtime.rb', line 26

def initialize(session, &current_document)
  @session = session
  @current_document = current_document || -> { session.document }
  @runtimes = {}.compare_by_identity
  @js_errors = []
  @console = []
  @console_listeners = []
  @js_error_listeners = []
  @script_listeners = []
  @document_listeners = []
  session.on_document_loaded { |window| on_page_load(window) }
end

Instance Attribute Details

#consoleObject (readonly)

current_document yields the document execute/evaluate should target (the session's current document by default; the Capybara driver passes its own frame-aware accessor). Uncaught JS errors and unhandled promise rejections collected across every realm (a host can fail a test when non-empty), and console output.



24
25
26
# File 'lib/dommy/rack/session_runtime.rb', line 24

def console
  @console
end

#js_errorsObject (readonly)

current_document yields the document execute/evaluate should target (the session's current document by default; the Capybara driver passes its own frame-aware accessor). Uncaught JS errors and unhandled promise rejections collected across every realm (a host can fail a test when non-empty), and console output.



24
25
26
# File 'lib/dommy/rack/session_runtime.rb', line 24

def js_errors
  @js_errors
end

Instance Method Details

#advance_time(ms) ⇒ Object

Advance the current realm's virtual clock, running timers that come due, then drain.



61
62
63
64
65
# File 'lib/dommy/rack/session_runtime.rb', line 61

def advance_time(ms)
  scheduler_of(@current_document.call)&.advance_time(ms)
  current_runtime.drain_microtasks
  self
end

#current_runtimeObject



90
91
92
# File 'lib/dommy/rack/session_runtime.rb', line 90

def current_runtime
  runtime_for(@current_document.call)
end

#disposeObject



94
95
96
# File 'lib/dommy/rack/session_runtime.rb', line 94

def dispose
  dispose_all
end

#drainObject

Drain the current realm's microtasks (used as an interaction's settle point: a Ruby-dispatched event ran JS handlers; flush their promises).



69
70
71
72
# File 'lib/dommy/rack/session_runtime.rb', line 69

def drain
  current_runtime.drain_microtasks
  self
end

#evaluate(js) ⇒ Object



50
# File 'lib/dommy/rack/session_runtime.rb', line 50

def evaluate(js) = current_runtime.evaluate(js)

#execute(js) ⇒ Object



49
# File 'lib/dommy/rack/session_runtime.rb', line 49

def execute(js) = current_runtime.execute(js)

#on_console(&block) ⇒ Object

Observation seams a Trace (or other host) subscribes to. console output, JS errors, and script-boot results are realm-internal — they surface here, not on the Session — so the runtime fans them out. on_document fires before a freshly loaded page's scripts boot (see on_page_load), so a :document marker is ordered ahead of that page's :script entries.



44
# File 'lib/dommy/rack/session_runtime.rb', line 44

def on_console(&block) = @console_listeners << block

#on_document(&block) ⇒ Object



47
# File 'lib/dommy/rack/session_runtime.rb', line 47

def on_document(&block) = @document_listeners << block

#on_js_error(&block) ⇒ Object



45
# File 'lib/dommy/rack/session_runtime.rb', line 45

def on_js_error(&block) = @js_error_listeners << block

#on_script(&block) ⇒ Object



46
# File 'lib/dommy/rack/session_runtime.rb', line 46

def on_script(&block) = @script_listeners << block

#pumpObject

Advance virtual time a slice and drain across EVERY live realm, so a timer in any window (top or frame) progresses while a poller waits. Snapshot iteration: a fired timer may navigate and replace the map.



77
78
79
80
81
82
# File 'lib/dommy/rack/session_runtime.rb', line 77

def pump
  @runtimes.to_a.each do |doc, runtime|
    scheduler_of(doc)&.advance_time(PUMP_SLICE_MS)
    runtime.drain_microtasks
  end
end

#runtime_for(doc) ⇒ Object

The realm VM for one document, built lazily and cached by identity so a frame switch keeps each realm's JS state instead of rebuilding it.



86
87
88
# File 'lib/dommy/rack/session_runtime.rb', line 86

def runtime_for(doc)
  @runtimes[doc] ||= build_runtime(doc)
end

#settleObject

Settle work ready at the current virtual time (microtasks + due-now timers + rAF) for the current document's realm.



54
55
56
57
# File 'lib/dommy/rack/session_runtime.rb', line 54

def settle
  current_runtime.settle
  self
end