Module: Dommy::EventTarget
- Included in:
- AbortSignal, Animation, Attr, BatteryManager, BroadcastChannel, CharacterDataNode, Clipboard, CookieStore, Document, DocumentType, Element, EventSource, FileReader, Fragment, MediaQueryList, MessagePort, Notification, PermissionStatus, ProcessingInstruction, ShadowRoot, StandaloneEventTarget, WakeLockSentinel, WebSocket, Window, Worker, XMLHttpRequest, XMLHttpRequestUpload
- Defined in:
- lib/dommy/event.rb
Overview
Note: ‘Callback` and `Constructor` live in `Dommy::Bridge::*` —they’re bridge-adapter classes, not part of the public DOM surface.
Defined Under Namespace
Classes: Listener
Class Method Summary collapse
-
.capture_flag(options) ⇒ Object
The capture flag for an addEventListener/removeEventListener options argument, using JS — not Ruby — truthiness: a boolean useCapture, or the ‘capture` member of an options dictionary, where 0 / “” / NaN / null / undefined are falsy (in Ruby 0 and “” are truthy, so a naive `!!` is wrong).
-
.js_truthy?(value) ⇒ Boolean
JS ToBoolean: false for false/null/undefined, +0/-0, NaN, and “”.
Instance Method Summary collapse
-
#__internal_deliver_event__(event, phase = :both) ⇒ Object
‘phase` is :capture (capture listeners), :bubble (non-capture), or :both (at the target).
-
#__internal_event_parent__ ⇒ Object
The next target up the propagation path.
- #add_event_listener(type, listener = nil, options = nil, &block) ⇒ Object
-
#deliver_at(node, event, phase) ⇒ Object
Deliver ‘event` to one node’s listeners for the current phase, then honor stopPropagation (throws to end the whole walk after this node finishes).
- #dispatch_event(event) ⇒ Object
- #remove_event_listener(type, listener, options = nil) ⇒ Object
Class Method Details
.capture_flag(options) ⇒ Object
The capture flag for an addEventListener/removeEventListener options argument, using JS — not Ruby — truthiness: a boolean useCapture, or the ‘capture` member of an options dictionary, where 0 / “” / NaN / null / undefined are falsy (in Ruby 0 and “” are truthy, so a naive `!!` is wrong).
172 173 174 175 176 177 178 179 180 |
# File 'lib/dommy/event.rb', line 172 def self.capture_flag() raw = if .is_a?(Hash) .key?("capture") ? ["capture"] : [:capture] else end js_truthy?(raw) end |
.js_truthy?(value) ⇒ Boolean
JS ToBoolean: false for false/null/undefined, +0/-0, NaN, and “”.
183 184 185 186 187 188 189 190 |
# File 'lib/dommy/event.rb', line 183 def self.js_truthy?(value) return false if value.nil? || value == false return false if defined?(Bridge::UNDEFINED) && value.equal?(Bridge::UNDEFINED) return false if value.is_a?(Numeric) && (value.zero? || (value.respond_to?(:nan?) && value.nan?)) return false if value == "" true end |
Instance Method Details
#__internal_deliver_event__(event, phase = :both) ⇒ Object
‘phase` is :capture (capture listeners), :bubble (non-capture), or :both (at the target). stopImmediatePropagation ends delivery within this node.
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/dommy/event.rb', line 120 def __internal_deliver_event__(event, phase = :both) listeners = listeners_for(event.type).dup listeners.each do |entry| next unless phase == :both || (phase == :capture ? entry.capture? : !entry.capture?) # Spec: a `once` listener is removed BEFORE its callback runs, so a nested # dispatch from within the callback can't invoke it a second time. if entry.once? listeners_for(event.type).reject! do |candidate| candidate.listener.equal?(entry.listener) && candidate.capture? == entry.capture? end end CallableInvoker.invoke_listener(entry.listener, event) break if event.immediate_propagation_stopped? end nil end |
#__internal_event_parent__ ⇒ Object
The next target up the propagation path. The default (no parent) suits EventTargets that aren’t tree nodes (AbortSignal, XHR, …); Element / Document / ShadowRoot override it to walk the node tree.
144 145 146 |
# File 'lib/dommy/event.rb', line 144 def __internal_event_parent__ nil end |
#add_event_listener(type, listener = nil, options = nil, &block) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/dommy/event.rb', line 9 def add_event_listener(type, listener = nil, = nil, &block) cb = listener || block return nil if type.nil? || cb.nil? list = listeners_for(type.to_s) entry = Listener.new(cb, ) # Per spec, a listener is deduplicated by (type, callback, capture) — so # the same function may be registered once as a capture and once as a # bubble listener. return nil if list.any? { |e| e.listener.equal?(cb) && e.capture? == entry.capture? } list << entry # `{ signal: AbortSignal }` — when the signal aborts, auto- # remove the listener. Per spec, if the signal is already aborted # the listener must not be registered at all. signal = .is_a?(Hash) ? (["signal"] || [:signal]) : nil if signal.respond_to?(:__js_get__) if signal.__js_get__("aborted") remove_event_listener(type, cb, ) else target = self signal.__js_call__( "addEventListener", [ "abort", proc { target.remove_event_listener(type, cb, ) } ] ) end end nil end |
#deliver_at(node, event, phase) ⇒ Object
Deliver ‘event` to one node’s listeners for the current phase, then honor stopPropagation (throws to end the whole walk after this node finishes).
107 108 109 110 111 112 113 114 115 116 |
# File 'lib/dommy/event.rb', line 107 def deliver_at(node, event, phase) # Honor a stop-propagation flag set before reaching this node (including # one set before dispatch began) — the spec checks it before invoking a # node's listeners, not only after. throw :stop_propagation if event.propagation_stopped? event.__internal_set_current_target__(node) node.__internal_deliver_event__(event, phase) throw :stop_propagation if event.propagation_stopped? end |
#dispatch_event(event) ⇒ Object
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 |
# File 'lib/dommy/event.rb', line 59 def dispatch_event(event) return true if event.nil? # Per spec, dispatchEvent must receive an Event instance. raise TypeError, "dispatchEvent requires an Event, got #{event.class}" unless event.is_a?(Event) event.__internal_prepare_for_dispatch__(self) event.__internal_set_dispatch_flag__(true) # The full propagation path: the target plus its ancestors (root last). # Capturing always traverses the ancestors regardless of `bubbles`. path = event.__js_get__("composed") ? composed_bubble_path(event) : event_bubble_path event.__internal_record_path__(path) if event.respond_to?(:__internal_record_path__) ancestors = path[1..] || [] catch(:stop_propagation) do # Capturing phase: root → … → parent, capture listeners only. event.__internal_set_event_phase__(Event::CAPTURING_PHASE) ancestors.reverse_each do |node| deliver_at(node, event, :capture) end # At the target: both capture and bubble listeners. event.__internal_set_event_phase__(Event::AT_TARGET) deliver_at(self, event, :both) # Bubbling phase: parent → … → root, bubble listeners only (only when # the event bubbles). if event.bubbles? event.__internal_set_event_phase__(Event::BUBBLING_PHASE) ancestors.each do |node| deliver_at(node, event, :bubble) end end end # After dispatch, currentTarget reverts to null and eventPhase to NONE, and # the propagation flags are unset so the event can be dispatched again. event.__internal_set_current_target__(nil) event.__internal_set_event_phase__(Event::NONE) event.__internal_clear_propagation_flags__ event.__internal_set_dispatch_flag__(false) !event.default_prevented? end |
#remove_event_listener(type, listener, options = nil) ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/dommy/event.rb', line 46 def remove_event_listener(type, listener, = nil) return nil if type.nil? || listener.nil? # Per spec, a listener is identified by (type, callback, capture) — so # removing must match the capture flag, not just the callback (a function # registered as both a capture and a bubble listener is two listeners). capture = EventTarget.capture_flag() listeners_for(type.to_s).reject! do |entry| entry.listener.equal?(listener) && entry.capture? == capture end nil end |