Class: Dommy::Window
- Inherits:
-
Object
- Object
- Dommy::Window
- Includes:
- EventTarget
- Defined in:
- lib/dommy/window.rb
Overview
Instance Attribute Summary collapse
-
#custom_elements ⇒ Object
readonly
Returns the value of attribute custom_elements.
-
#document ⇒ Object
readonly
Returns the value of attribute document.
-
#globals ⇒ Object
readonly
Returns the value of attribute globals.
-
#location ⇒ Object
readonly
Returns the value of attribute location.
-
#navigator ⇒ Object
readonly
Returns the value of attribute navigator.
-
#scheduler ⇒ Object
readonly
Returns the value of attribute scheduler.
Instance Method Summary collapse
- #__event_parent__ ⇒ Object
- #__js_call__(method, args) ⇒ Object
-
#__js_get__(key) ⇒ Object
Bridge protocol: respond to a JS-style property read by name.
- #__js_set__(key, value) ⇒ Object
- #fire_hashchange(old_hash, new_hash) ⇒ Object
-
#fire_popstate(state) ⇒ Object
Called by History#go and Location.href= to fire popstate / hashchange events.
-
#initialize(host = nil, nokogiri_doc: nil) ⇒ Window
constructor
A new instance of Window.
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 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 109 110 111 112 113 114 |
# File 'lib/dommy/window.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]) } @input_event_ctor = Bridge::Constructor.new { |args| InputEvent.new(args[0], args[1]) } @pointer_event_ctor = Bridge::Constructor.new { |args| PointerEvent.new(args[0], args[1]) } @progress_event_ctor = Bridge::Constructor.new { |args| ProgressEvent.new(args[0], args[1]) } @touch_ctor = Bridge::Constructor.new { |args| Touch.new(args[0] || {}) } @touch_event_ctor = Bridge::Constructor.new { |args| TouchEvent.new(args[0], args[1]) } @clipboard_event_ctor = Bridge::Constructor.new { |args| ClipboardEvent.new(args[0], args[1]) } @composition_event_ctor = Bridge::Constructor.new { |args| CompositionEvent.new(args[0], args[1]) } @wheel_event_ctor = Bridge::Constructor.new { |args| WheelEvent.new(args[0], args[1]) } @focus_event_ctor = Bridge::Constructor.new { |args| FocusEvent.new(args[0], args[1]) } @before_unload_event_ctor = Bridge::Constructor.new { |args| BeforeUnloadEvent.new(args[0] || "beforeunload", args[1]) } win_ref = self @animation_ctor = Bridge::Constructor.new { |args| Animation.new(args[0], args[1], window: win_ref) } @keyframe_effect_ctor = Bridge::Constructor.new { |args| KeyframeEffect.new(args[0], args[1] || [], args[2]) } @crypto = Crypto.new(self) @text_encoder_ctor = Bridge::Constructor.new { |_args| TextEncoder.new } @text_decoder_ctor = Bridge::Constructor.new { |args| TextDecoder.new(args[0] || "utf-8", args[1]) } @intersection_observer_ctor = Bridge::Constructor.new { |args| IntersectionObserver.new(args[0], args[1]) } @resize_observer_ctor = Bridge::Constructor.new { |args| ResizeObserver.new(args[0]) } @performance_observer_ctor = Bridge::Constructor.new { |args| PerformanceObserver.new(args[0]) } @request_ctor = Bridge::Constructor.new { |args| Request.new(args[0], args[1]) } xhr_win_ref = self @xhr_ctor = Bridge::Constructor.new { |_args| XMLHttpRequest.new(xhr_win_ref) } @file_reader_ctor = Bridge::Constructor.new { |_args| FileReader.new(xhr_win_ref) } @message_channel_ctor = Bridge::Constructor.new { |_args| MessageChannel.new(xhr_win_ref) } @broadcast_channel_ctor = Bridge::Constructor.new { |args| BroadcastChannel.new(xhr_win_ref, args[0]) } @web_socket_ctor = Bridge::Constructor.new { |args| WebSocket.new(xhr_win_ref, args[0], args[1]) } @event_source_ctor = Bridge::Constructor.new { |args| EventSource.new(xhr_win_ref, args[0], args[1]) } @notification_ctor = Bridge::Constructor.new { |args| Notification.new(xhr_win_ref, args[0], args[1]) } @notification_ctor.define_class_method("requestPermission") do |args| Notification.(xhr_win_ref, args[0]) end @worker_ctor = Bridge::Constructor.new { |args| Worker.new(xhr_win_ref, args[0], args[1]) } @readable_stream_ctor = Bridge::Constructor.new { |args| ReadableStream.new(xhr_win_ref, args[0]) } @writable_stream_ctor = Bridge::Constructor.new { |args| WritableStream.new(xhr_win_ref, args[0]) } @transform_stream_ctor = Bridge::Constructor.new { |args| TransformStream.new(xhr_win_ref, args[0]) } @text_encoder_stream_ctor = Bridge::Constructor.new { |_args| TextEncoderStream.new(xhr_win_ref) } @text_decoder_stream_ctor = Bridge::Constructor.new { |args| TextDecoderStream.new(xhr_win_ref, args[0] || "utf-8", args[1]) } @compression_stream_ctor = Bridge::Constructor.new { |args| CompressionStream.new(xhr_win_ref, args[0]) } @decompression_stream_ctor = Bridge::Constructor.new { |args| DecompressionStream.new(xhr_win_ref, args[0]) } @url_pattern_ctor = Bridge::Constructor.new { |args| URLPattern.new(args[0], args[1]) } @cookie_store = CookieStore.new(xhr_win_ref) @range_ctor = Bridge::Constructor.new { |_args| Range.new(@document) } @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_elements ⇒ Object (readonly)
Returns the value of attribute custom_elements.
24 25 26 |
# File 'lib/dommy/window.rb', line 24 def custom_elements @custom_elements end |
#document ⇒ Object (readonly)
Returns the value of attribute document.
24 25 26 |
# File 'lib/dommy/window.rb', line 24 def document @document end |
#globals ⇒ Object (readonly)
Returns the value of attribute globals.
24 25 26 |
# File 'lib/dommy/window.rb', line 24 def globals @globals end |
#location ⇒ Object (readonly)
Returns the value of attribute location.
24 25 26 |
# File 'lib/dommy/window.rb', line 24 def location @location end |
#navigator ⇒ Object (readonly)
Returns the value of attribute navigator.
24 25 26 |
# File 'lib/dommy/window.rb', line 24 def navigator @navigator end |
#scheduler ⇒ Object (readonly)
Returns the value of attribute scheduler.
24 25 26 |
# File 'lib/dommy/window.rb', line 24 def scheduler @scheduler end |
Instance Method Details
#__event_parent__ ⇒ Object
339 340 341 |
# File 'lib/dommy/window.rb', line 339 def __event_parent__ nil end |
#__js_call__(method, args) ⇒ Object
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/dommy/window.rb', line 276 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]) when "requestIdleCallback" # WHATWG `requestIdleCallback` — no real idle period in # dommy, so we model it as a deferred setTimeout. The # callback receives an `IdleDeadline`-shaped Hash. @scheduler.set_timeout( proc { args[0].respond_to?(:__js_call__) ? args[0].__js_call__( "call", [{"timeRemaining" => 50.0, "didTimeout" => false}] ) : args[0].call({"timeRemaining" => 50.0, "didTimeout" => false}) }, (args[1].is_a?(Hash) && args[1]["timeout"]) || 0 ) when "cancelIdleCallback" @scheduler.clear_timeout(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 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.
124 125 126 127 128 129 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 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/dommy/window.rb', line 124 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 when "InputEvent" @input_event_ctor when "PointerEvent" @pointer_event_ctor when "ProgressEvent" @progress_event_ctor when "Touch" @touch_ctor when "TouchEvent" @touch_event_ctor when "ClipboardEvent" @clipboard_event_ctor when "CompositionEvent" @composition_event_ctor when "WheelEvent" @wheel_event_ctor when "FocusEvent" @focus_event_ctor when "BeforeUnloadEvent" @before_unload_event_ctor when "Animation" @animation_ctor when "KeyframeEffect" @keyframe_effect_ctor when "crypto" @crypto when "TextEncoder" @text_encoder_ctor when "TextDecoder" @text_decoder_ctor when "IntersectionObserver" @intersection_observer_ctor when "ResizeObserver" @resize_observer_ctor when "PerformanceObserver" @performance_observer_ctor when "Request" @request_ctor when "XMLHttpRequest" @xhr_ctor when "FileReader" @file_reader_ctor when "MessageChannel" @message_channel_ctor when "BroadcastChannel" @broadcast_channel_ctor when "WebSocket" @web_socket_ctor when "EventSource" @event_source_ctor when "Notification" @notification_ctor when "Worker" @worker_ctor when "ReadableStream" @readable_stream_ctor when "WritableStream" @writable_stream_ctor when "TransformStream" @transform_stream_ctor when "TextEncoderStream" @text_encoder_stream_ctor when "TextDecoderStream" @text_decoder_stream_ctor when "CompressionStream" @compression_stream_ctor when "DecompressionStream" @decompression_stream_ctor when "URLPattern" @url_pattern_ctor when "cookieStore" @cookie_store when "Range" @range_ctor # handled by Symbol sentinel when "console" :console # likewise 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 "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
263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/dommy/window.rb', line 263 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
351 352 353 354 |
# File 'lib/dommy/window.rb', line 351 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.
346 347 348 349 |
# File 'lib/dommy/window.rb', line 346 def fire_popstate(state) event = CustomEvent.new("popstate", "detail" => state) dispatch_event(event) end |