Class: Dommy::Window

Inherits:
Object
  • Object
show all
Includes:
EventTarget
Defined in:
lib/dommy/world.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 EventTarget

#__deliver_event__, #add_event_listener, #dispatch_event, #invoke_listener, #remove_event_listener

Constructor Details

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

Returns a new instance of Window.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/dommy/world.rb', line 26

def initialize(host = nil, nokogiri_doc: nil)
  @host = host
  @scheduler = Scheduler.new
  @event_ctor = Bridge::Constructor.new { |args| Event.new(args[0], args[1]) }
  @custom_event_ctor = Bridge::Constructor.new { |args| CustomEvent.new(args[0], args[1]) }
  @mouse_event_ctor = Bridge::Constructor.new { |args| MouseEvent.new(args[0], args[1]) }
  @keyboard_event_ctor = Bridge::Constructor.new { |args| KeyboardEvent.new(args[0], args[1]) }
  @event_target_ctor = Bridge::Constructor.new { |_args| StandaloneEventTarget.new }
  @error_ctor = Bridge::Constructor.new { |args| ErrorValue.new(args[0]) }
  @promise_ctor = Bridge::PromiseConstructor.new(self)
  @mutation_observer_ctor = Bridge::Constructor.new { |args| MutationObserver.new(self, args[0]) }
  @abort_controller_ctor = Bridge::Constructor.new { |_args| AbortController.new }
  @blob_ctor = Bridge::Constructor.new { |args| Blob.new(args[0] || [], args[1] || {}) }
  @file_ctor = Bridge::Constructor.new { |args| File.new(args[0] || [], args[1].to_s, args[2] || {}) }
  @file_list_ctor = Bridge::Constructor.new { |args| FileList.new(args[0] || []) }
  @data_transfer_ctor = Bridge::Constructor.new { |args|
    opts = args[0] || {}
    DataTransfer.new(
      files: opts["files"] || opts[:files] || [],
      data: opts["data"] || opts[:data] || {}
    )
  }
  @drag_event_ctor = Bridge::Constructor.new { |args| DragEvent.new(args[0], args[1]) }
  @local_storage = Storage.new
  @session_storage = Storage.new
  @location = Location.new(self)
  @history = History.new(self, @location)
  @url_ctor = Bridge::Constructor.new { |args| Url.new(args[0], args[1]) }
  @url_ctor.define_class_method("createObjectURL") { |args| URL.create_object_url(args[0]) }
  @url_ctor.define_class_method("revokeObjectURL") { |args| URL.revoke_object_url(args[0]) }
  # `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 above. We keep it last in the read fallback to
  # avoid shadowing intentional getters.
  @globals = {}
  @document = Document.new(host, nokogiri_doc: nokogiri_doc)
  @document.default_view = self
  @custom_elements = CustomElementRegistry.new(self)
  @navigator = Navigator.new(self)
end

Instance Attribute Details

#custom_elementsObject (readonly)

Returns the value of attribute custom_elements.



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

def custom_elements
  @custom_elements
end

#documentObject (readonly)

Returns the value of attribute document.



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

def document
  @document
end

#globalsObject (readonly)

Returns the value of attribute globals.



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

def globals
  @globals
end

#locationObject (readonly)

Returns the value of attribute location.



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

def location
  @location
end

Returns the value of attribute navigator.



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

def navigator
  @navigator
end

#schedulerObject (readonly)

Returns the value of attribute scheduler.



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

def scheduler
  @scheduler
end

Instance Method Details

#__event_parent__Object



192
193
194
# File 'lib/dommy/world.rb', line 192

def __event_parent__
  nil
end

#__js_call__(method, args) ⇒ Object



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
182
183
184
185
186
187
188
189
190
# File 'lib/dommy/world.rb', line 154

def __js_call__(method, args)
  case method
  when "fetch"
    FetchFn.new(self).__js_call__("call", args)
  when "encodeURIComponent"
    # JS spec encoding: percent-encode anything except
    # `A-Za-z0-9 - _ . ! ~ * ' ( )`. Ruby's `CGI.escape` uses
    # `+` for space; ERB::Util.url_encode matches JS behavior.
    ERB::Util.url_encode(args[0].to_s)
  when "decodeURIComponent"
    CGI.unescape(args[0].to_s)
  when "addEventListener"
    add_event_listener(args[0], args[1], args[2])
  when "removeEventListener"
    remove_event_listener(args[0], args[1])
  when "dispatchEvent"
    dispatch_event(args[0])
  when "setTimeout"
    @scheduler.set_timeout(args[0], args[1] || 0)
  when "clearTimeout"
    @scheduler.clear_timeout(args[0])
  when "setInterval"
    @scheduler.set_interval(args[0], args[1] || 0)
  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])
  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.



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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/dommy/world.rb', line 76

def __js_get__(key)
  case key
  when "document"
    @document
  when "Event"
    @event_ctor
  when "CustomEvent"
    @custom_event_ctor
  when "MouseEvent"
    @mouse_event_ctor
  when "KeyboardEvent"
    @keyboard_event_ctor
  when "EventTarget"
    @event_target_ctor
  when "Error"
    @error_ctor
  when "Promise"
    @promise_ctor
  when "MutationObserver"
    @mutation_observer_ctor
  when "AbortController"
    @abort_controller_ctor
  when "Blob"
    @blob_ctor
  when "File"
    @file_ctor
  when "FileList"
    @file_list_ctor
  when "DataTransfer"
    @data_transfer_ctor
  when "DragEvent"
    @drag_event_ctor
    # handled by Symbol sentinel
  when "console"
    :console
    # likewise
  when "Object"
    :object_ctor
  when "Array"
    :array_ctor
  when "JSON"
    :json_ctor
  when "performance"
    {"now" => @scheduler.now_ms.to_f}
  when "localStorage"
    @local_storage
  when "sessionStorage"
    @session_storage
  when "location"
    @location
  when "history"
    @history
  when "URL"
    @url_ctor
  when "fetch"
    FetchFn.new(self)
  when "customElements"
    @custom_elements
  when "navigator"
    @navigator
  else
    @globals[key]
  end
end

#__js_set__(key, value) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/dommy/world.rb', line 141

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



204
205
206
207
# File 'lib/dommy/world.rb', line 204

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.



199
200
201
202
# File 'lib/dommy/world.rb', line 199

def fire_popstate(state)
  event = CustomEvent.new("popstate", "detail" => state)
  dispatch_event(event)
end