Class: Dommy::AbortSignal

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

Overview

‘AbortController` + `AbortSignal` subset. Signal fires an “abort” event and flips `[:aborted]` to true when the controller’s ‘abort()` is called; otherwise it stays inert.

Constant Summary collapse

NO_REASON =

Sentinel meaning “no reason argument was supplied” — distinct from an explicit ‘null` reason (`abort(null)` keeps reason null, but `abort()` / `abort(undefined)` default to a fresh AbortError).

Object.new

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Bridge::Methods

included

Methods included from EventTarget

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

Constructor Details

#initializeAbortSignal

Returns a new instance of AbortSignal.



1038
1039
1040
1041
# File 'lib/dommy/event.rb', line 1038

def initialize
  @aborted = false
  @reason = nil
end

Class Method Details

.abort(*reason) ⇒ Object

Spec: ‘AbortSignal.abort(reason?)` returns a fresh, pre-aborted signal. Convenient for APIs that need an already-cancelled token.



995
996
997
998
999
# File 'lib/dommy/event.rb', line 995

def self.abort(*reason)
  signal = new
  signal.__internal_mark_aborted__(*reason)
  signal
end

.any(signals) ⇒ Object

Spec: ‘AbortSignal.any([sig, …])` returns a composite signal that aborts as soon as any of the inputs aborts. If any input is already aborted, the returned signal is pre-aborted with that input’s reason.



1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
# File 'lib/dommy/event.rb', line 1022

def self.any(signals)
  composite = new
  list = Array(signals).select { |s| s.is_a?(AbortSignal) }
  already = list.find(&:aborted?)
  if already
    composite.__internal_mark_aborted__(already.reason)
    return composite
  end

  list.each do |sig|
    sig.add_event_listener("abort", proc { composite.__internal_mark_aborted__(sig.reason) })
  end

  composite
end

.timeout(ms, scheduler: nil) ⇒ Object

Spec: ‘AbortSignal.timeout(ms)` returns a signal that aborts itself after `ms` milliseconds with a `TimeoutError` reason. Without a Window/scheduler we fall back to a Thread-based timer so the signal works in vanilla CRuby; embedders that want microtask integration can pass a window via `schedule_via`.



1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
# File 'lib/dommy/event.rb', line 1006

def self.timeout(ms, scheduler: nil)
  signal = new
  reason = DOMException::TimeoutError.new("operation timed out")
  if scheduler
    scheduler.set_timeout(proc { signal.__internal_mark_aborted__(reason) }, ms.to_i)
  else
    signal.__internal_schedule_thread_timeout__(ms.to_i, reason)
  end

  signal
end

Instance Method Details

#__internal_mark_aborted__(reason = NO_REASON) ⇒ Object



1123
1124
1125
1126
1127
1128
1129
1130
# File 'lib/dommy/event.rb', line 1123

def __internal_mark_aborted__(reason = NO_REASON)
  return if @aborted

  @aborted = true
  no_reason = reason.equal?(NO_REASON) || (defined?(Bridge::UNDEFINED) && reason.equal?(Bridge::UNDEFINED))
  @reason = no_reason ? DOMException::AbortError.new("signal is aborted without reason") : reason
  dispatch_event(Event.new("abort", "bubbles" => false, "cancelable" => false).__internal_mark_trusted__)
end

#__internal_schedule_thread_timeout__(ms, reason) ⇒ Object

Background-thread timeout used by ‘AbortSignal.timeout` when no scheduler is provided. Kept package-private; tests can also drive the abort manually via `internal_mark_aborted`.



1046
1047
1048
1049
1050
1051
1052
1053
# File 'lib/dommy/event.rb', line 1046

def __internal_schedule_thread_timeout__(ms, reason)
  Thread.new do
    sleep(ms.to_f / 1000.0)
    __internal_mark_aborted__(reason)
  end

  nil
end

#__js_call__(method, args) ⇒ Object



1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
# File 'lib/dommy/event.rb', line 1105

def __js_call__(method, args)
  case method
  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 "throwIfAborted"
    throw_if_aborted
  end
end

#__js_get__(key) ⇒ Object



1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
# File 'lib/dommy/event.rb', line 1077

def __js_get__(key)
  case key
  when "aborted"
    @aborted
  when "reason"
    # A non-aborted signal's reason is `undefined` (not null); once aborted
    # it is the abort reason (an explicit value or the default AbortError).
    @aborted ? @reason : Bridge::UNDEFINED
  when "onabort"
    @onabort_handler
  end
end

#__js_set__(key, value) ⇒ Object

‘signal.onabort = fn` is an event-handler IDL attribute: it registers a single “abort” listener (replacing any previous one); null/undefined clears it. (Setting it after the signal is already aborted never fires.)



1093
1094
1095
1096
1097
1098
1099
1100
1101
# File 'lib/dommy/event.rb', line 1093

def __js_set__(key, value)
  return Bridge::UNHANDLED unless key == "onabort"

  remove_event_listener("abort", @onabort_handler) if @onabort_handler
  cleared = value.nil? || (defined?(Bridge::UNDEFINED) && value.equal?(Bridge::UNDEFINED))
  @onabort_handler = cleared ? nil : value
  add_event_listener("abort", @onabort_handler) if @onabort_handler
  nil
end

#aborted?Boolean

Returns:

  • (Boolean)


1055
1056
1057
# File 'lib/dommy/event.rb', line 1055

def aborted?
  @aborted
end

#reasonObject



1059
1060
1061
# File 'lib/dommy/event.rb', line 1059

def reason
  @reason
end

#throw_if_abortedObject Also known as: throwIfAborted

Spec: throws ‘signal.reason` if aborted, otherwise no-op. Used by consumer code that polls before doing async work.

Raises:



1065
1066
1067
1068
1069
1070
1071
1072
1073
# File 'lib/dommy/event.rb', line 1065

def throw_if_aborted
  return unless @aborted
  # An Exception reason (the default AbortError, or an explicit DOMException)
  # is raised so the bridge tags it as a real JS error; any other reason — a
  # string, number, or opaque JSValue — is thrown verbatim, identity kept.
  raise @reason if @reason.is_a?(Exception)

  raise Bridge::ThrowValue.new(@reason)
end