Class: Dommy::Window

Inherits:
Object
  • Object
show all
Includes:
Bridge::Methods, EventTarget
Defined in:
lib/dommy/window.rb

Overview

The browser global. ‘JS.global` from inside wasm resolves to this. Property access (`JS.global`, `JS.global`) is routed through `#js_get`. Method calls (`JS.global.call(:foo)`) are routed through `#js_call`.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Bridge::Methods

included

Methods included from EventTarget

#__internal_deliver_event__, #add_event_listener, capture_flag, #deliver_at, #dispatch_event, js_truthy?, #remove_event_listener

Constructor Details

#initialize(host = nil, nokogiri_doc: nil) ⇒ Window

Returns a new instance of Window.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/dommy/window.rb', line 23

def initialize(host = nil, nokogiri_doc: nil)
  @host = host
  @scheduler = Scheduler.new
  @crypto = Crypto.new(self)
  @css_namespace = CSSNamespace.new
  @cookie_store = CookieStore.new(self)
  @local_storage = Storage.new
  @session_storage = Storage.new
  @location = Location.new(self)
  @history = History.new(self, @location)
  # `JS.global[:__some_key__] = ...` from user code lands here. Test code
  # uses this for stub installation (e.g. a custom `__fetch_stub__`);
  # production code stays on the typed accessors. Kept last in the read
  # fallback so it can't shadow intentional getters.
  @globals = {}
  @document = Document.new(host, nokogiri_doc: nokogiri_doc)
  @document.default_view = self
  @custom_elements = CustomElementRegistry.new(self)
  @navigator = Navigator.new(self)
  # All JS global constructors (`new Event()`, `new URL()`, ...) live in a
  # single name→Constructor registry rather than one ivar + one __js_get__
  # arm each.
  @constructors = Bridge::ConstructorRegistry.new(build_constructors)
end

Instance Attribute Details

#custom_elementsObject (readonly)

Returns the value of attribute custom_elements.



21
22
23
# File 'lib/dommy/window.rb', line 21

def custom_elements
  @custom_elements
end

#documentObject (readonly)

Returns the value of attribute document.



21
22
23
# File 'lib/dommy/window.rb', line 21

def document
  @document
end

#globalsObject (readonly)

Returns the value of attribute globals.



21
22
23
# File 'lib/dommy/window.rb', line 21

def globals
  @globals
end

#locationObject (readonly)

Returns the value of attribute location.



21
22
23
# File 'lib/dommy/window.rb', line 21

def location
  @location
end

Returns the value of attribute navigator.



21
22
23
# File 'lib/dommy/window.rb', line 21

def navigator
  @navigator
end

#schedulerObject (readonly)

Returns the value of attribute scheduler.



21
22
23
# File 'lib/dommy/window.rb', line 21

def scheduler
  @scheduler
end

Instance Method Details

#__internal_event_parent__Object



183
184
185
# File 'lib/dommy/window.rb', line 183

def __internal_event_parent__
  nil
end

#__js_call__(method, args) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/dommy/window.rb', line 130

def __js_call__(method, args)
  case method
  when "fetch"
    FetchFn.new(self).__js_call__("call", args)
  when "encodeURIComponent"
    Internal::GlobalFunctions.encode_uri_component(args[0])
  when "decodeURIComponent"
    Internal::GlobalFunctions.decode_uri_component(args[0])
  when "addEventListener"
    add_event_listener(args[0], args[1], args[2])
  when "removeEventListener"
    remove_event_listener(args[0], args[1], args[2])
  when "dispatchEvent"
    dispatch_event(args[0])
  when "setTimeout"
    @scheduler.set_timeout(args[0], timer_delay(args[1]))
  when "clearTimeout"
    @scheduler.clear_timeout(args[0])
  when "setInterval"
    @scheduler.set_interval(args[0], timer_delay(args[1]))
  when "clearInterval"
    @scheduler.clear_interval(args[0])
  when "requestAnimationFrame"
    @scheduler.request_animation_frame(args[0])
  when "cancelAnimationFrame"
    @scheduler.cancel_animation_frame(args[0])
  when "queueMicrotask"
    @scheduler.queue_microtask(args[0])
  when "requestIdleCallback"
    @scheduler.request_idle_callback(args[0], (args[1].is_a?(Hash) && args[1]["timeout"]) || 0)
  when "cancelIdleCallback"
    @scheduler.cancel_idle_callback(args[0])
  when "structuredClone"
    Dommy.structured_clone(args[0])
  when "matchMedia"
    MediaQueryList.new(self, args[0].to_s)
  when "getComputedStyle"
    # No CSS engine — return the element's inline style. That
    # covers `getComputedStyle(el).getPropertyValue("color")` for
    # values the test set inline via `el.style.color = "..."`.
    target = args[0]
    target.respond_to?(:style) ? target.style : nil
  when "scroll", "scrollTo"
    scroll_to(*args)
  when "scrollBy"
    scroll_by(*args)
  else
    # Additional window-level methods (fetch, location, history,
    # Promise, MutationObserver, etc.) arrive in later sessions.
    nil
  end
end

#__js_get__(key) ⇒ Object

Bridge protocol: respond to a JS-style property read by name. Returns either a Ruby primitive (Integer / String / true / false / nil), a Hash/Array (for JS object/array literals), or a Dom::* instance for live DOM/BOM objects.

Anything outside the surface we’ve explicitly polyfilled returns nil (= JS undefined). Spec failures here are the signal to widen the surface in a future session.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/dommy/window.rb', line 56

def __js_get__(key)
  ctor = @constructors[key]
  return ctor if ctor

  case key
  when "document"
    @document
  when "window", "self", "parent", "top", "frames"
    # A top-level browsing context refers to itself for these. Returning the
    # window (not nil) lets `window === window.parent` and frame-walking
    # loops (e.g. testharness.js's `while (w != w.parent)`) terminate.
    self
  when "crypto"
    @crypto
  when "cookieStore"
    @cookie_store
  when "console"
    :console
  when "Object"
    :object_ctor
  when "Array"
    :array_ctor
  when "JSON"
    :json_ctor
  when "performance"
    @performance ||= Performance.new(self)
  when "localStorage"
    @local_storage
  when "sessionStorage"
    @session_storage
  when "location"
    @location
  when "history"
    @history
  when "CSS"
    @css_namespace
  when "fetch"
    FetchFn.new(self)
  when "customElements"
    @custom_elements
  when "navigator"
    @navigator
  when "scrollX", "pageXOffset"
    @scroll_x || 0
  when "scrollY", "pageYOffset"
    @scroll_y || 0
  when "scrollMaxX", "scrollMaxY"
    # No real content box to scroll past, so the max offset is 0.
    0
  else
    @globals[key]
  end
end

#__js_set__(key, value) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/dommy/window.rb', line 110

def __js_set__(key, value)
  # Stash arbitrary keys for later reads (e.g.
  # `JS.global[:__fetchy_stub__] = map`).
  @globals[key] = value
  # The Fetchy spec's `install_fetch_stub` resets `__fetch_count__`
  # to 0 inside its JS installer (`globalThis.__fetch_count__ = 0;
  # globalThis.fetch = ...`). Our polyfill ignores raw JS, so we
  # piggy-back on the stub assignment to perform the same reset
  # — without it the count accumulates across tests in one VM run.
  @globals["__fetch_count__"] = 0 if %w[__fetchy_stub__ __resource_fetch_stub__ __inject_fetch_stub__].include?(key)
  nil
end

#fire_hashchange(old_hash, new_hash) ⇒ Object



197
198
199
200
# File 'lib/dommy/window.rb', line 197

def fire_hashchange(old_hash, new_hash)
  event = CustomEvent.new("hashchange", "detail" => {"oldURL" => old_hash, "newURL" => new_hash})
  dispatch_event(event)
end

#fire_popstate(state) ⇒ Object

Called by History#go and Location.href= to fire popstate / hashchange events. Listeners registered on the Window via ‘addEventListener(“popstate”|“hashchange”, cb)` receive them.



190
191
192
193
194
195
# File 'lib/dommy/window.rb', line 190

def fire_popstate(state)
  # PopStateEvent exposes the entry's state as `event.state` (the spec
  # property). Routers (Turbo) branch on `event.state`.
  event = PopStateEvent.new("popstate", "state" => state)
  dispatch_event(event)
end