Class: Dommy::XMLHttpRequest

Inherits:
Object
  • Object
show all
Includes:
EventTarget
Defined in:
lib/dommy/xml_http_request.rb

Overview

‘XMLHttpRequest` polyfill. Consults the same stub maps that `FetchFn` reads (`fetchy_stub` / `resource_fetch_stub` / `inject_fetch_stub`) so a single set of fixtures drives both `fetch(…)` and `new XMLHttpRequest()` style code.

State transitions match the spec:

UNSENT(0) → OPENED(1) → HEADERS_RECEIVED(2) → LOADING(3) → DONE(4)

Each transition fires ‘readystatechange`. `load` / `loadend` fire on completion; `error` / `timeout` / `abort` fire on the respective failure paths.

Async requests resolve via the scheduler (a microtask, or a ‘setTimeout` for stubs with `delay:`); sync requests (`open(…, false)`) deliver inline so tests can read `xhr.responseText` immediately.

Spec: xhr.spec.whatwg.org/

Defined Under Namespace

Classes: Error

Constant Summary collapse

UNSENT =
0
OPENED =
1
HEADERS_RECEIVED =
2
LOADING =
3
DONE =
4
INLINE_HANDLERS =
%w[
  readystatechange
  loadstart
  load
  loadend
  progress
  error
  timeout
  abort
].freeze

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(window) ⇒ XMLHttpRequest

Returns a new instance of XMLHttpRequest.



56
57
58
59
60
61
62
63
64
65
# File 'lib/dommy/xml_http_request.rb', line 56

def initialize(window)
  @window = window
  @timeout = 0
  @with_credentials = false
  @response_type = ""
  @generation = 0
  reset_state
  @inline_handlers = {}
  @upload = XMLHttpRequestUpload.new
end

Instance Attribute Details

#ready_stateObject (readonly)

Returns the value of attribute ready_state.



43
44
45
# File 'lib/dommy/xml_http_request.rb', line 43

def ready_state
  @ready_state
end

#responseObject (readonly)

Returns the value of attribute response.



43
44
45
# File 'lib/dommy/xml_http_request.rb', line 43

def response
  @response
end

#response_textObject (readonly)

Returns the value of attribute response_text.



43
44
45
# File 'lib/dommy/xml_http_request.rb', line 43

def response_text
  @response_text
end

#response_typeObject

Returns the value of attribute response_type.



54
55
56
# File 'lib/dommy/xml_http_request.rb', line 54

def response_type
  @response_type
end

#response_urlObject (readonly)

Returns the value of attribute response_url.



43
44
45
# File 'lib/dommy/xml_http_request.rb', line 43

def response_url
  @response_url
end

#response_xmlObject (readonly)

Returns the value of attribute response_xml.



43
44
45
# File 'lib/dommy/xml_http_request.rb', line 43

def response_xml
  @response_xml
end

#statusObject (readonly)

Returns the value of attribute status.



43
44
45
# File 'lib/dommy/xml_http_request.rb', line 43

def status
  @status
end

#status_textObject (readonly)

Returns the value of attribute status_text.



43
44
45
# File 'lib/dommy/xml_http_request.rb', line 43

def status_text
  @status_text
end

#timeoutObject

Returns the value of attribute timeout.



54
55
56
# File 'lib/dommy/xml_http_request.rb', line 54

def timeout
  @timeout
end

#uploadObject (readonly)

Returns the value of attribute upload.



43
44
45
# File 'lib/dommy/xml_http_request.rb', line 43

def upload
  @upload
end

#with_credentialsObject

Returns the value of attribute with_credentials.



54
55
56
# File 'lib/dommy/xml_http_request.rb', line 54

def with_credentials
  @with_credentials
end

Instance Method Details

#__event_parent__Object



239
240
241
# File 'lib/dommy/xml_http_request.rb', line 239

def __event_parent__
  nil
end

#__js_call__(method, args) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/dommy/xml_http_request.rb', line 214

def __js_call__(method, args)
  case method
  when "open"
    open(args[0], args[1], args[2].nil? ? true : args[2], args[3], args[4])
  when "send"
    send(args[0])
  when "setRequestHeader"
    set_request_header(args[0], args[1])
  when "abort"
    abort
  when "getResponseHeader"
    get_response_header(args[0])
  when "getAllResponseHeaders"
    get_all_response_headers
  when "overrideMimeType"
    override_mime_type(args[0])
  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])
  end
end

#__js_get__(key) ⇒ Object

— JS bridge —————————————————



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
# File 'lib/dommy/xml_http_request.rb', line 159

def __js_get__(key)
  case key
  when "readyState"
    @ready_state
  when "status"
    @status
  when "statusText"
    @status_text
  when "responseURL"
    @response_url
  when "response"
    @response
  when "responseText"
    @response_text
  when "responseXML"
    @response_xml
  when "responseType"
    @response_type
  when "timeout"
    @timeout
  when "withCredentials"
    @with_credentials
  when "upload"
    @upload
  when "UNSENT"
    UNSENT
  when "OPENED"
    OPENED
  when "HEADERS_RECEIVED"
    HEADERS_RECEIVED
  when "LOADING"
    LOADING
  when "DONE"
    DONE
  else
    @inline_handlers[inline_event_for(key)]
  end
end

#__js_set__(key, value) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/dommy/xml_http_request.rb', line 198

def __js_set__(key, value)
  case key
  when "responseType"
    @response_type = value.to_s
  when "timeout"
    @timeout = value.to_i
  when "withCredentials"
    @with_credentials = !!value
  else
    event = inline_event_for(key)
    set_inline_handler(event, value) if event
  end

  nil
end

#abortObject



118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/dommy/xml_http_request.rb', line 118

def abort
  return if @ready_state == UNSENT || @ready_state == DONE

  @aborted = true
  @generation += 1
  @status = 0
  @status_text = ""
  transition(DONE)
  dispatch_event(ProgressEvent.new("abort"))
  dispatch_event(ProgressEvent.new("loadend"))
  reset_state(keep_handlers: true, keep_generation: true)
  nil
end

#get_all_response_headersObject Also known as: getAllResponseHeaders



142
143
144
145
146
# File 'lib/dommy/xml_http_request.rb', line 142

def get_all_response_headers
  return "" if @ready_state < HEADERS_RECEIVED

  @response_headers.map { |k, v| "#{k}: #{v}\r\n" }.join
end

#get_response_header(name) ⇒ Object Also known as: getResponseHeader



132
133
134
135
136
137
138
# File 'lib/dommy/xml_http_request.rb', line 132

def get_response_header(name)
  return nil if @ready_state < HEADERS_RECEIVED

  key = name.to_s.downcase
  hit = @response_headers.find { |k, _| k.to_s.downcase == key }
  hit ? hit.last : nil
end

#open(method, url, async = true, _user = nil, _password = nil) ⇒ Object

XHR §open. ‘method` is uppercased; `async` defaults to true.



68
69
70
71
72
73
74
75
76
# File 'lib/dommy/xml_http_request.rb', line 68

def open(method, url, async = true, _user = nil, _password = nil)
  reset_state
  @method = method.to_s.upcase
  @url = url.to_s
  @async = async.nil? ? true : !!async
  @request_headers = {}
  transition(OPENED)
  nil
end

#override_mime_type(mime) ⇒ Object Also known as: overrideMimeType



150
151
152
153
# File 'lib/dommy/xml_http_request.rb', line 150

def override_mime_type(mime)
  @override_mime = mime.to_s
  nil
end

#send(body = nil) ⇒ Object

Raises:



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
# File 'lib/dommy/xml_http_request.rb', line 89

def send(body = nil)
  raise Error, "send called before open" if @ready_state != OPENED

  @request_body = body
  @sent = true
  @generation += 1
  gen = @generation
  dispatch_event(ProgressEvent.new("loadstart"))

  entry = lookup_stub
  track_globals

  if entry.nil?
    deliver(body: "not found", status: 404, status_text: "Not Found", headers: {})
    return nil
  end

  delay = entry["delay"]
  if delay && @async
    schedule_delivery_with_delay(entry, delay.to_i, gen)
  elsif @async
    @window.scheduler.queue_microtask(proc { deliver_entry(entry) if active?(gen) })
  else
    deliver_entry(entry)
  end

  nil
end

#set_request_header(name, value) ⇒ Object Also known as: setRequestHeader

Raises:



78
79
80
81
82
83
84
85
# File 'lib/dommy/xml_http_request.rb', line 78

def set_request_header(name, value)
  raise Error, "setRequestHeader called before open" if @ready_state != OPENED

  key = name.to_s
  existing = @request_headers[key]
  @request_headers[key] = existing ? "#{existing}, #{value}" : value.to_s
  nil
end