dommy 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +213 -0
- data/lib/dommy/attr.rb +200 -0
- data/lib/dommy/blob.rb +182 -0
- data/lib/dommy/bridge.rb +141 -0
- data/lib/dommy/css.rb +283 -0
- data/lib/dommy/custom_elements.rb +125 -0
- data/lib/dommy/data_transfer.rb +98 -0
- data/lib/dommy/document.rb +674 -0
- data/lib/dommy/dom_exception.rb +258 -0
- data/lib/dommy/dom_parser.rb +88 -0
- data/lib/dommy/element.rb +1975 -0
- data/lib/dommy/event.rb +589 -0
- data/lib/dommy/fetch.rb +241 -0
- data/lib/dommy/form_data.rb +208 -0
- data/lib/dommy/html_collection.rb +207 -0
- data/lib/dommy/html_elements.rb +4455 -0
- data/lib/dommy/internal/cookie_jar.rb +27 -0
- data/lib/dommy/internal/dom_matching.rb +141 -0
- data/lib/dommy/internal/mutation_coordinator.rb +172 -0
- data/lib/dommy/internal/node_traversal.rb +36 -0
- data/lib/dommy/internal/node_wrapper_cache.rb +179 -0
- data/lib/dommy/internal/observer_manager.rb +31 -0
- data/lib/dommy/internal/observer_matcher.rb +31 -0
- data/lib/dommy/internal/scope_resolution.rb +27 -0
- data/lib/dommy/internal/shadow_root_registry.rb +35 -0
- data/lib/dommy/internal/template_content_registry.rb +97 -0
- data/lib/dommy/minitest/assertions.rb +105 -0
- data/lib/dommy/minitest.rb +17 -0
- data/lib/dommy/navigator.rb +271 -0
- data/lib/dommy/node.rb +218 -0
- data/lib/dommy/observer.rb +199 -0
- data/lib/dommy/parser.rb +29 -0
- data/lib/dommy/promise.rb +199 -0
- data/lib/dommy/router.rb +275 -0
- data/lib/dommy/rspec/capy_style_matchers.rb +356 -0
- data/lib/dommy/rspec/matchers.rb +230 -0
- data/lib/dommy/rspec.rb +18 -0
- data/lib/dommy/scheduler.rb +135 -0
- data/lib/dommy/shadow_root.rb +255 -0
- data/lib/dommy/storage.rb +112 -0
- data/lib/dommy/test_helpers.rb +78 -0
- data/lib/dommy/tree_walker.rb +425 -0
- data/lib/dommy/url.rb +479 -0
- data/lib/dommy/version.rb +5 -0
- data/lib/dommy/world.rb +209 -0
- data/lib/dommy.rb +119 -0
- metadata +110 -0
|
@@ -0,0 +1,4455 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dommy
|
|
4
|
+
# Base for specialized HTMLElement subclasses. Adds the reflected
|
|
5
|
+
# IDL boolean / string attribute helpers each subclass uses.
|
|
6
|
+
class HTMLElement < Element
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def reflected_boolean(name)
|
|
10
|
+
@__node__.key?(name.to_s.downcase)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def set_reflected_boolean(name, value)
|
|
14
|
+
key = name.to_s.downcase
|
|
15
|
+
if value
|
|
16
|
+
set_attribute(key, "")
|
|
17
|
+
elsif @__node__.key?(key)
|
|
18
|
+
remove_attribute(key)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def reflected_string(name)
|
|
23
|
+
@__node__[name.to_s.downcase].to_s
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def set_reflected_string(name, value)
|
|
27
|
+
set_attribute(name.to_s.downcase, value.to_s)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# `<a>` — exposes URL-component getters/setters via the `href`
|
|
32
|
+
# attribute, plus reflected `target` / `download` / `rel` / `type`.
|
|
33
|
+
class HTMLAnchorElement < HTMLElement
|
|
34
|
+
def target
|
|
35
|
+
reflected_string("target")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def target=(v)
|
|
39
|
+
set_reflected_string("target", v)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def download
|
|
43
|
+
reflected_string("download")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def download=(v)
|
|
47
|
+
set_reflected_string("download", v)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def rel
|
|
51
|
+
reflected_string("rel")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def rel=(v)
|
|
55
|
+
set_reflected_string("rel", v)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def hreflang
|
|
59
|
+
reflected_string("hreflang")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def type
|
|
63
|
+
reflected_string("type")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# URL-decomposition helpers. The anchor's `href` is resolved to
|
|
67
|
+
# an absolute URL (inherited from Element#anchor_href); break it
|
|
68
|
+
# into the standard components on demand.
|
|
69
|
+
def hash
|
|
70
|
+
uri_part(:fragment) ? "##{uri_part(:fragment)}" : ""
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def host
|
|
74
|
+
uri.host ? "#{uri.host}#{port_suffix}" : ""
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def hostname
|
|
78
|
+
uri.host || ""
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def pathname
|
|
82
|
+
uri.path || "/"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def protocol
|
|
86
|
+
uri.scheme ? "#{uri.scheme}:" : ""
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def search
|
|
90
|
+
uri.query ? "?#{uri.query}" : ""
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def port
|
|
94
|
+
uri.port ? uri.port.to_s : ""
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def origin
|
|
98
|
+
uri.scheme && uri.host ? "#{uri.scheme}://#{uri.host}#{port_suffix}" : ""
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def __js_get__(key)
|
|
102
|
+
case key
|
|
103
|
+
when "target"
|
|
104
|
+
target
|
|
105
|
+
when "download"
|
|
106
|
+
download
|
|
107
|
+
when "rel"
|
|
108
|
+
rel
|
|
109
|
+
when "hreflang"
|
|
110
|
+
hreflang
|
|
111
|
+
when "type"
|
|
112
|
+
type
|
|
113
|
+
when "hash"
|
|
114
|
+
self.hash
|
|
115
|
+
when "host"
|
|
116
|
+
host
|
|
117
|
+
when "hostname"
|
|
118
|
+
hostname
|
|
119
|
+
when "pathname"
|
|
120
|
+
pathname
|
|
121
|
+
when "protocol"
|
|
122
|
+
protocol
|
|
123
|
+
when "search"
|
|
124
|
+
search
|
|
125
|
+
when "port"
|
|
126
|
+
port
|
|
127
|
+
when "origin"
|
|
128
|
+
origin
|
|
129
|
+
else
|
|
130
|
+
super
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def __js_set__(key, value)
|
|
135
|
+
case key
|
|
136
|
+
when "target", "download", "rel", "hreflang"
|
|
137
|
+
set_reflected_string(key, value)
|
|
138
|
+
else
|
|
139
|
+
super
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
def uri
|
|
146
|
+
require "uri"
|
|
147
|
+
|
|
148
|
+
URI(anchor_href)
|
|
149
|
+
rescue URI::InvalidURIError, ArgumentError
|
|
150
|
+
URI("")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def uri_part(part)
|
|
154
|
+
uri.send(part)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def port_suffix
|
|
158
|
+
return "" unless uri.port
|
|
159
|
+
|
|
160
|
+
default = uri.scheme == "https" ? 443 : 80
|
|
161
|
+
uri.port == default ? "" : ":#{uri.port}"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# `<form>` — element collection, submit/reset, and a stubbed
|
|
166
|
+
# validation surface.
|
|
167
|
+
class HTMLFormElement < HTMLElement
|
|
168
|
+
def name
|
|
169
|
+
reflected_string("name")
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def name=(v)
|
|
173
|
+
set_reflected_string("name", v)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def action
|
|
177
|
+
reflected_string("action")
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def action=(v)
|
|
181
|
+
set_reflected_string("action", v)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def method_attr
|
|
185
|
+
reflected_string("method")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def method_attr=(v)
|
|
189
|
+
set_reflected_string("method", v)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def enctype
|
|
193
|
+
reflected_string("enctype")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def target
|
|
197
|
+
reflected_string("target")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def autocomplete
|
|
201
|
+
reflected_string("autocomplete")
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def accept_charset
|
|
205
|
+
reflected_string("accept-charset")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def no_validate
|
|
209
|
+
reflected_boolean("novalidate")
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def no_validate=(v)
|
|
213
|
+
set_reflected_boolean("novalidate", v)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# `form.elements` — listed elements inside the form (excludes
|
|
217
|
+
# nested forms per spec; we approximate by walking
|
|
218
|
+
# input/select/textarea/button/output/fieldset). Returned as a
|
|
219
|
+
# live HTMLCollection so listening to `submit`/`reset` and
|
|
220
|
+
# adding fields between accesses works as expected.
|
|
221
|
+
def elements
|
|
222
|
+
el = self
|
|
223
|
+
HTMLCollection.new do
|
|
224
|
+
el
|
|
225
|
+
.__node__
|
|
226
|
+
.css("input, select, textarea, button, output, fieldset")
|
|
227
|
+
.map do |n|
|
|
228
|
+
el.document.wrap_node(n)
|
|
229
|
+
end
|
|
230
|
+
.compact
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def length
|
|
235
|
+
elements.size
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Spec: `submit()` performs form submission directly WITHOUT
|
|
239
|
+
# firing a `submit` event. This is the JS-only entry point —
|
|
240
|
+
# browsers don't run constraint validation either. Dommy has no
|
|
241
|
+
# navigation engine, so this is effectively a no-op (returns nil).
|
|
242
|
+
def submit
|
|
243
|
+
nil
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Spec: `reset()` does fire a `reset` event; if the event is
|
|
247
|
+
# default-prevented, no reset happens. Dommy has no built-in
|
|
248
|
+
# control re-init logic, so we just dispatch the event.
|
|
249
|
+
def reset
|
|
250
|
+
dispatch_event(Event.new("reset", "bubbles" => true, "cancelable" => true))
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Spec: `requestSubmit(submitter?)` is the JS counterpart that
|
|
254
|
+
# MIRRORS user-initiated submission — it runs constraint validation
|
|
255
|
+
# and fires a `submit` event. Returns true if not default-prevented.
|
|
256
|
+
# `submitter` (if given) must be a button inside this form.
|
|
257
|
+
def request_submit(submitter = nil)
|
|
258
|
+
if submitter
|
|
259
|
+
unless submitter.respond_to?(:__node__) && submitter.__node__.ancestors.include?(@__node__)
|
|
260
|
+
raise DOMException::NotFoundError, "submitter is not a descendant of this form"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
type = submitter.respond_to?(:type) ? submitter.type.to_s.downcase : ""
|
|
264
|
+
unless %w[submit image].include?(type)
|
|
265
|
+
raise TypeError, "submitter must be a submit button"
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
dispatch_event(Event.new("submit", "bubbles" => true, "cancelable" => true))
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Walk all listed elements; the form is "valid" iff every
|
|
273
|
+
# candidate control passes its own checkValidity. Dispatches a
|
|
274
|
+
# non-bubbling `invalid` event on each failing control.
|
|
275
|
+
def check_validity
|
|
276
|
+
ok = true
|
|
277
|
+
elements.each do |el|
|
|
278
|
+
next unless el.respond_to?(:will_validate)
|
|
279
|
+
next unless el.will_validate
|
|
280
|
+
next if el.validity.valid && (el.instance_variable_get(:@custom_validity_message) || "").empty?
|
|
281
|
+
|
|
282
|
+
# Fire invalid event on this control (matches spec).
|
|
283
|
+
el.dispatch_event(Event.new("invalid", "bubbles" => false, "cancelable" => true))
|
|
284
|
+
ok = false
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
ok
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def report_validity
|
|
291
|
+
check_validity
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def __js_get__(key)
|
|
295
|
+
case key
|
|
296
|
+
when "elements"
|
|
297
|
+
elements
|
|
298
|
+
when "length"
|
|
299
|
+
length
|
|
300
|
+
when "name"
|
|
301
|
+
name
|
|
302
|
+
when "action"
|
|
303
|
+
action
|
|
304
|
+
when "method"
|
|
305
|
+
method_attr
|
|
306
|
+
when "enctype"
|
|
307
|
+
enctype
|
|
308
|
+
when "target"
|
|
309
|
+
target
|
|
310
|
+
when "autocomplete"
|
|
311
|
+
autocomplete
|
|
312
|
+
when "acceptCharset"
|
|
313
|
+
accept_charset
|
|
314
|
+
when "noValidate"
|
|
315
|
+
no_validate
|
|
316
|
+
else
|
|
317
|
+
super
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def __js_set__(key, value)
|
|
322
|
+
case key
|
|
323
|
+
when "name"
|
|
324
|
+
set_reflected_string("name", value)
|
|
325
|
+
when "action"
|
|
326
|
+
set_reflected_string("action", value)
|
|
327
|
+
when "method"
|
|
328
|
+
set_reflected_string("method", value)
|
|
329
|
+
when "enctype"
|
|
330
|
+
set_reflected_string("enctype", value)
|
|
331
|
+
when "target"
|
|
332
|
+
set_reflected_string("target", value)
|
|
333
|
+
when "noValidate"
|
|
334
|
+
set_reflected_boolean("novalidate", value)
|
|
335
|
+
else
|
|
336
|
+
super
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def __js_call__(method, args)
|
|
341
|
+
case method
|
|
342
|
+
when "submit"
|
|
343
|
+
submit
|
|
344
|
+
when "reset"
|
|
345
|
+
reset
|
|
346
|
+
when "requestSubmit"
|
|
347
|
+
request_submit(args[0])
|
|
348
|
+
when "checkValidity"
|
|
349
|
+
check_validity
|
|
350
|
+
when "reportValidity"
|
|
351
|
+
report_validity
|
|
352
|
+
else
|
|
353
|
+
super
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# `<input>` — covers the most-used form control surface.
|
|
359
|
+
class HTMLInputElement < HTMLElement
|
|
360
|
+
def type
|
|
361
|
+
raw = @__node__["type"].to_s
|
|
362
|
+
raw.empty? ? "text" : raw.downcase
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def type=(v)
|
|
366
|
+
set_reflected_string("type", v)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def name
|
|
370
|
+
reflected_string("name")
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def name=(v)
|
|
374
|
+
set_reflected_string("name", v)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def placeholder
|
|
378
|
+
reflected_string("placeholder")
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def placeholder=(v)
|
|
382
|
+
set_reflected_string("placeholder", v)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def min
|
|
386
|
+
reflected_string("min")
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def max
|
|
390
|
+
reflected_string("max")
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def step
|
|
394
|
+
reflected_string("step")
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def pattern
|
|
398
|
+
reflected_string("pattern")
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def autocomplete
|
|
402
|
+
reflected_string("autocomplete")
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def autofocus
|
|
406
|
+
reflected_boolean("autofocus")
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def autofocus=(v)
|
|
410
|
+
set_reflected_boolean("autofocus", v)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def default_value
|
|
414
|
+
reflected_string("value")
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def default_checked
|
|
418
|
+
reflected_boolean("checked")
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Runtime value/checked. Dommy has no UI, so the runtime state is
|
|
422
|
+
# initialized from the attribute on first access and tracked
|
|
423
|
+
# separately thereafter — matching browser semantics where the
|
|
424
|
+
# `value` IDL attribute can drift from the `value` content attr.
|
|
425
|
+
def value
|
|
426
|
+
sanitize_value(@__value.nil? ? reflected_string("value") : @__value)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def value=(v)
|
|
430
|
+
raw = v.to_s
|
|
431
|
+
@__raw_value = raw
|
|
432
|
+
@__value = raw
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# `files` — for `<input type="file">`. Browsers populate this via
|
|
436
|
+
# user interaction; in tests, code uses `__set_files__` to seed it.
|
|
437
|
+
def files
|
|
438
|
+
@__files ||= FileList.new
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Test-only seam: set the input's file list directly.
|
|
442
|
+
# Accepts an array (wrapped in a FileList) or a FileList itself.
|
|
443
|
+
def __set_files__(files_input)
|
|
444
|
+
@__files = files_input.is_a?(FileList) ? files_input : FileList.new(Array(files_input))
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# Spec: the "value sanitization algorithm" runs lazily on read.
|
|
448
|
+
# type=email/url trim leading/trailing ASCII whitespace; type=number
|
|
449
|
+
# rejects non-finite floats by returning "" (badInput stays true
|
|
450
|
+
# so validity surfaces the original raw value).
|
|
451
|
+
def sanitize_value(raw)
|
|
452
|
+
case type
|
|
453
|
+
when "email"
|
|
454
|
+
if @__node__.key?("multiple")
|
|
455
|
+
raw.to_s.split(",").map(&:strip).join(",")
|
|
456
|
+
else
|
|
457
|
+
raw.to_s.strip
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
when "url"
|
|
461
|
+
raw.to_s.strip
|
|
462
|
+
when "number", "range"
|
|
463
|
+
sanitize_number(raw)
|
|
464
|
+
when "color"
|
|
465
|
+
s = raw.to_s.strip.downcase
|
|
466
|
+
s.match?(/\A#[0-9a-f]{6}\z/) ? s : "#000000"
|
|
467
|
+
else
|
|
468
|
+
raw.to_s
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def sanitize_number(raw)
|
|
473
|
+
s = raw.to_s
|
|
474
|
+
Float(s)
|
|
475
|
+
s
|
|
476
|
+
rescue ArgumentError, TypeError
|
|
477
|
+
""
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Underlying string the user supplied to `value=`, before any
|
|
481
|
+
# sanitization. Used by ValidityState.badInput so a non-parseable
|
|
482
|
+
# number still trips constraint validation.
|
|
483
|
+
def raw_value
|
|
484
|
+
@__raw_value || @__value || reflected_string("value")
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def checked
|
|
488
|
+
@__checked.nil? ? default_checked : @__checked
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def checked=(v)
|
|
492
|
+
@__checked = !!v
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def disabled
|
|
496
|
+
reflected_boolean("disabled")
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def disabled=(v)
|
|
500
|
+
set_reflected_boolean("disabled", v)
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def required
|
|
504
|
+
reflected_boolean("required")
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def required=(v)
|
|
508
|
+
set_reflected_boolean("required", v)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def readonly
|
|
512
|
+
reflected_boolean("readonly")
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def readonly=(v)
|
|
516
|
+
set_reflected_boolean("readonly", v)
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def labels
|
|
520
|
+
return [] if id.empty?
|
|
521
|
+
|
|
522
|
+
@document.query_selector_all("label[for='#{id}']")
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Closest enclosing form (or nil if detached / not in a form).
|
|
526
|
+
def form
|
|
527
|
+
closest("form")
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# No real text selection; method stubs let callers proceed.
|
|
531
|
+
def select
|
|
532
|
+
nil
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def set_selection_range(_start, _end, _direction = nil)
|
|
536
|
+
nil
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
def set_range_text(_replacement, *_)
|
|
540
|
+
nil
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def step_up(_n = 1)
|
|
544
|
+
nil
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def step_down(_n = 1)
|
|
548
|
+
nil
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def validity
|
|
552
|
+
@__validity ||= ValidityState.new(self)
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# Whether this control participates in constraint validation.
|
|
556
|
+
# Disabled / hidden / button-type inputs return false.
|
|
557
|
+
def will_validate
|
|
558
|
+
return false if reflected_boolean("disabled")
|
|
559
|
+
return false if reflected_boolean("readonly")
|
|
560
|
+
return false if %w[hidden button submit reset image].include?(type)
|
|
561
|
+
|
|
562
|
+
true
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def validation_message
|
|
566
|
+
return "" unless will_validate
|
|
567
|
+
|
|
568
|
+
msg = (@custom_validity_message || "").to_s
|
|
569
|
+
return msg unless msg.empty?
|
|
570
|
+
return "Please fill out this field." if validity.value_missing
|
|
571
|
+
return "Please match the requested format." if validity.pattern_mismatch
|
|
572
|
+
return "Please enter a valid email address." if validity.type_mismatch && type == "email"
|
|
573
|
+
return "Please enter a URL." if validity.type_mismatch && type == "url"
|
|
574
|
+
|
|
575
|
+
""
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def check_validity
|
|
579
|
+
ok = !will_validate || validity.valid
|
|
580
|
+
dispatch_event(Event.new("invalid", "bubbles" => false, "cancelable" => true)) unless ok
|
|
581
|
+
ok
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
def report_validity
|
|
585
|
+
check_validity
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def set_custom_validity(msg)
|
|
589
|
+
@custom_validity_message = msg.to_s
|
|
590
|
+
nil
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def __js_get__(key)
|
|
594
|
+
case key
|
|
595
|
+
when "type"
|
|
596
|
+
type
|
|
597
|
+
when "name"
|
|
598
|
+
name
|
|
599
|
+
when "placeholder"
|
|
600
|
+
placeholder
|
|
601
|
+
when "min"
|
|
602
|
+
min
|
|
603
|
+
when "max"
|
|
604
|
+
max
|
|
605
|
+
when "step"
|
|
606
|
+
step
|
|
607
|
+
when "pattern"
|
|
608
|
+
pattern
|
|
609
|
+
when "autocomplete"
|
|
610
|
+
autocomplete
|
|
611
|
+
when "autofocus"
|
|
612
|
+
autofocus
|
|
613
|
+
when "defaultValue"
|
|
614
|
+
default_value
|
|
615
|
+
when "defaultChecked"
|
|
616
|
+
default_checked
|
|
617
|
+
when "value"
|
|
618
|
+
value
|
|
619
|
+
when "checked"
|
|
620
|
+
checked
|
|
621
|
+
when "disabled"
|
|
622
|
+
disabled
|
|
623
|
+
when "required"
|
|
624
|
+
required
|
|
625
|
+
when "readonly", "readOnly"
|
|
626
|
+
readonly
|
|
627
|
+
when "labels"
|
|
628
|
+
labels
|
|
629
|
+
when "form"
|
|
630
|
+
form
|
|
631
|
+
when "validity"
|
|
632
|
+
validity
|
|
633
|
+
when "willValidate"
|
|
634
|
+
will_validate
|
|
635
|
+
when "validationMessage"
|
|
636
|
+
validation_message
|
|
637
|
+
when "files"
|
|
638
|
+
files
|
|
639
|
+
else
|
|
640
|
+
super
|
|
641
|
+
end
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
def __js_set__(key, value)
|
|
645
|
+
case key
|
|
646
|
+
when "type"
|
|
647
|
+
set_reflected_string("type", value)
|
|
648
|
+
when "name"
|
|
649
|
+
set_reflected_string("name", value)
|
|
650
|
+
when "placeholder"
|
|
651
|
+
set_reflected_string("placeholder", value)
|
|
652
|
+
when "min", "max", "step", "pattern", "autocomplete"
|
|
653
|
+
set_reflected_string(key, value)
|
|
654
|
+
when "autofocus"
|
|
655
|
+
set_reflected_boolean("autofocus", value)
|
|
656
|
+
when "value"
|
|
657
|
+
self.value = value
|
|
658
|
+
when "checked"
|
|
659
|
+
self.checked = value
|
|
660
|
+
when "disabled"
|
|
661
|
+
self.disabled = value
|
|
662
|
+
when "required"
|
|
663
|
+
self.required = value
|
|
664
|
+
when "readonly", "readOnly"
|
|
665
|
+
self.readonly = value
|
|
666
|
+
else
|
|
667
|
+
super
|
|
668
|
+
end
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
def __js_call__(method, args)
|
|
672
|
+
case method
|
|
673
|
+
when "select"
|
|
674
|
+
select
|
|
675
|
+
when "setSelectionRange"
|
|
676
|
+
set_selection_range(args[0], args[1], args[2])
|
|
677
|
+
when "setRangeText"
|
|
678
|
+
set_range_text(args[0])
|
|
679
|
+
when "stepUp"
|
|
680
|
+
step_up(args[0])
|
|
681
|
+
when "stepDown"
|
|
682
|
+
step_down(args[0])
|
|
683
|
+
when "checkValidity"
|
|
684
|
+
check_validity
|
|
685
|
+
when "reportValidity"
|
|
686
|
+
report_validity
|
|
687
|
+
when "setCustomValidity"
|
|
688
|
+
set_custom_validity(args[0])
|
|
689
|
+
else
|
|
690
|
+
super
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# `<button>` — type defaults to "submit" per spec.
|
|
696
|
+
class HTMLButtonElement < HTMLElement
|
|
697
|
+
def type
|
|
698
|
+
raw = @__node__["type"].to_s.downcase
|
|
699
|
+
%w[submit reset button].include?(raw) ? raw : "submit"
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
def type=(v)
|
|
703
|
+
set_reflected_string("type", v)
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def name
|
|
707
|
+
reflected_string("name")
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
def name=(v)
|
|
711
|
+
set_reflected_string("name", v)
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
def form_action
|
|
715
|
+
reflected_string("formaction")
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
def form_enctype
|
|
719
|
+
reflected_string("formenctype")
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
def form_method
|
|
723
|
+
reflected_string("formmethod")
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
def form_target
|
|
727
|
+
reflected_string("formtarget")
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
def form_no_validate
|
|
731
|
+
reflected_boolean("formnovalidate")
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def form_no_validate=(v)
|
|
735
|
+
set_reflected_boolean("formnovalidate", v)
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
def form
|
|
739
|
+
closest("form")
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
def labels
|
|
743
|
+
return [] if id.empty?
|
|
744
|
+
|
|
745
|
+
@document.query_selector_all("label[for='#{id}']")
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
def validity
|
|
749
|
+
@__validity ||= ValidityState.new(self)
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
# Buttons don't participate in constraint validation (per spec).
|
|
753
|
+
def will_validate
|
|
754
|
+
false
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
def validation_message
|
|
758
|
+
""
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
def check_validity
|
|
762
|
+
true
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
def report_validity
|
|
766
|
+
true
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
def set_custom_validity(msg)
|
|
770
|
+
@custom_validity_message = msg.to_s
|
|
771
|
+
nil
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
def __js_get__(key)
|
|
775
|
+
case key
|
|
776
|
+
when "type"
|
|
777
|
+
type
|
|
778
|
+
when "name"
|
|
779
|
+
name
|
|
780
|
+
when "formAction"
|
|
781
|
+
form_action
|
|
782
|
+
when "formEnctype"
|
|
783
|
+
form_enctype
|
|
784
|
+
when "formMethod"
|
|
785
|
+
form_method
|
|
786
|
+
when "formTarget"
|
|
787
|
+
form_target
|
|
788
|
+
when "formNoValidate"
|
|
789
|
+
form_no_validate
|
|
790
|
+
when "form"
|
|
791
|
+
form
|
|
792
|
+
when "labels"
|
|
793
|
+
labels
|
|
794
|
+
when "validity"
|
|
795
|
+
validity
|
|
796
|
+
when "willValidate"
|
|
797
|
+
will_validate
|
|
798
|
+
when "validationMessage"
|
|
799
|
+
validation_message
|
|
800
|
+
else
|
|
801
|
+
super
|
|
802
|
+
end
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
def __js_set__(key, value)
|
|
806
|
+
case key
|
|
807
|
+
when "type"
|
|
808
|
+
set_reflected_string("type", value)
|
|
809
|
+
when "name"
|
|
810
|
+
set_reflected_string("name", value)
|
|
811
|
+
when "formAction"
|
|
812
|
+
set_reflected_string("formaction", value)
|
|
813
|
+
when "formEnctype"
|
|
814
|
+
set_reflected_string("formenctype", value)
|
|
815
|
+
when "formMethod"
|
|
816
|
+
set_reflected_string("formmethod", value)
|
|
817
|
+
when "formTarget"
|
|
818
|
+
set_reflected_string("formtarget", value)
|
|
819
|
+
when "formNoValidate"
|
|
820
|
+
set_reflected_boolean("formnovalidate", value)
|
|
821
|
+
else
|
|
822
|
+
super
|
|
823
|
+
end
|
|
824
|
+
end
|
|
825
|
+
end
|
|
826
|
+
|
|
827
|
+
# `<img>` — reflected URL/dimension attributes. Dommy has no real
|
|
828
|
+
# image loading, so `complete`/`naturalWidth`/`naturalHeight` are
|
|
829
|
+
# static (complete=true, dimensions=0).
|
|
830
|
+
class HTMLImageElement < HTMLElement
|
|
831
|
+
def src
|
|
832
|
+
reflected_string("src")
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
def src=(v)
|
|
836
|
+
set_reflected_string("src", v)
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
def alt
|
|
840
|
+
reflected_string("alt")
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
def alt=(v)
|
|
844
|
+
set_reflected_string("alt", v)
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
def width
|
|
848
|
+
@__node__["width"].to_s.to_i
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
def width=(v)
|
|
852
|
+
set_reflected_string("width", v.to_s)
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
def height
|
|
856
|
+
@__node__["height"].to_s.to_i
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
def height=(v)
|
|
860
|
+
set_reflected_string("height", v.to_s)
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
def crossorigin
|
|
864
|
+
reflected_string("crossorigin")
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
def decoding
|
|
868
|
+
reflected_string("decoding")
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
def loading
|
|
872
|
+
reflected_string("loading")
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
def referrer_policy
|
|
876
|
+
reflected_string("referrerpolicy")
|
|
877
|
+
end
|
|
878
|
+
|
|
879
|
+
def sizes
|
|
880
|
+
reflected_string("sizes")
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
def srcset
|
|
884
|
+
reflected_string("srcset")
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
# No real loader → these are constants.
|
|
888
|
+
def natural_width
|
|
889
|
+
0
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
def natural_height
|
|
893
|
+
0
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
def complete
|
|
897
|
+
true
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
def current_src
|
|
901
|
+
src
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
def __js_get__(key)
|
|
905
|
+
case key
|
|
906
|
+
when "src"
|
|
907
|
+
src
|
|
908
|
+
when "alt"
|
|
909
|
+
alt
|
|
910
|
+
when "width"
|
|
911
|
+
width
|
|
912
|
+
when "height"
|
|
913
|
+
height
|
|
914
|
+
when "naturalWidth"
|
|
915
|
+
natural_width
|
|
916
|
+
when "naturalHeight"
|
|
917
|
+
natural_height
|
|
918
|
+
when "complete"
|
|
919
|
+
complete
|
|
920
|
+
when "currentSrc"
|
|
921
|
+
current_src
|
|
922
|
+
when "crossOrigin"
|
|
923
|
+
crossorigin
|
|
924
|
+
when "decoding"
|
|
925
|
+
decoding
|
|
926
|
+
when "loading"
|
|
927
|
+
loading
|
|
928
|
+
when "referrerPolicy"
|
|
929
|
+
referrer_policy
|
|
930
|
+
when "sizes"
|
|
931
|
+
sizes
|
|
932
|
+
when "srcset"
|
|
933
|
+
srcset
|
|
934
|
+
else
|
|
935
|
+
super
|
|
936
|
+
end
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
def __js_set__(key, value)
|
|
940
|
+
case key
|
|
941
|
+
when "src", "alt", "decoding", "loading", "sizes", "srcset"
|
|
942
|
+
set_reflected_string(key, value)
|
|
943
|
+
when "width", "height"
|
|
944
|
+
set_reflected_string(key, value.to_s)
|
|
945
|
+
when "crossOrigin"
|
|
946
|
+
set_reflected_string("crossorigin", value)
|
|
947
|
+
when "referrerPolicy"
|
|
948
|
+
set_reflected_string("referrerpolicy", value)
|
|
949
|
+
else
|
|
950
|
+
super
|
|
951
|
+
end
|
|
952
|
+
end
|
|
953
|
+
end
|
|
954
|
+
|
|
955
|
+
# `<script>` — `src` / `type` / `async` / `defer` / `text`.
|
|
956
|
+
class HTMLScriptElement < HTMLElement
|
|
957
|
+
def src
|
|
958
|
+
reflected_string("src")
|
|
959
|
+
end
|
|
960
|
+
|
|
961
|
+
def src=(v)
|
|
962
|
+
set_reflected_string("src", v)
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
def type
|
|
966
|
+
reflected_string("type")
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
def type=(v)
|
|
970
|
+
set_reflected_string("type", v)
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
def integrity
|
|
974
|
+
reflected_string("integrity")
|
|
975
|
+
end
|
|
976
|
+
|
|
977
|
+
def nonce
|
|
978
|
+
reflected_string("nonce")
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
def referrer_policy
|
|
982
|
+
reflected_string("referrerpolicy")
|
|
983
|
+
end
|
|
984
|
+
|
|
985
|
+
def async
|
|
986
|
+
reflected_boolean("async")
|
|
987
|
+
end
|
|
988
|
+
|
|
989
|
+
def async=(v)
|
|
990
|
+
set_reflected_boolean("async", v)
|
|
991
|
+
end
|
|
992
|
+
|
|
993
|
+
def defer
|
|
994
|
+
reflected_boolean("defer")
|
|
995
|
+
end
|
|
996
|
+
|
|
997
|
+
def defer=(v)
|
|
998
|
+
set_reflected_boolean("defer", v)
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
def no_module
|
|
1002
|
+
reflected_boolean("nomodule")
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
def no_module=(v)
|
|
1006
|
+
set_reflected_boolean("nomodule", v)
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
# `text` is an alias for textContent on <script>.
|
|
1010
|
+
def text
|
|
1011
|
+
text_content
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
def text=(v)
|
|
1015
|
+
self.text_content = v
|
|
1016
|
+
end
|
|
1017
|
+
|
|
1018
|
+
def __js_get__(key)
|
|
1019
|
+
case key
|
|
1020
|
+
when "src"
|
|
1021
|
+
src
|
|
1022
|
+
when "type"
|
|
1023
|
+
type
|
|
1024
|
+
when "async"
|
|
1025
|
+
async
|
|
1026
|
+
when "defer"
|
|
1027
|
+
defer
|
|
1028
|
+
when "noModule"
|
|
1029
|
+
no_module
|
|
1030
|
+
when "integrity"
|
|
1031
|
+
integrity
|
|
1032
|
+
when "nonce"
|
|
1033
|
+
nonce
|
|
1034
|
+
when "referrerPolicy"
|
|
1035
|
+
referrer_policy
|
|
1036
|
+
when "text"
|
|
1037
|
+
text
|
|
1038
|
+
else
|
|
1039
|
+
super
|
|
1040
|
+
end
|
|
1041
|
+
end
|
|
1042
|
+
|
|
1043
|
+
def __js_set__(key, value)
|
|
1044
|
+
case key
|
|
1045
|
+
when "src", "type", "integrity", "nonce"
|
|
1046
|
+
set_reflected_string(key, value)
|
|
1047
|
+
when "async"
|
|
1048
|
+
set_reflected_boolean("async", value)
|
|
1049
|
+
when "defer"
|
|
1050
|
+
set_reflected_boolean("defer", value)
|
|
1051
|
+
when "noModule"
|
|
1052
|
+
set_reflected_boolean("nomodule", value)
|
|
1053
|
+
when "referrerPolicy"
|
|
1054
|
+
set_reflected_string("referrerpolicy", value)
|
|
1055
|
+
when "text"
|
|
1056
|
+
self.text_content = value
|
|
1057
|
+
else
|
|
1058
|
+
super
|
|
1059
|
+
end
|
|
1060
|
+
end
|
|
1061
|
+
end
|
|
1062
|
+
|
|
1063
|
+
# `<link>` — primarily for stylesheets, icons, preload, manifests.
|
|
1064
|
+
class HTMLLinkElement < HTMLElement
|
|
1065
|
+
def href
|
|
1066
|
+
reflected_string("href")
|
|
1067
|
+
end
|
|
1068
|
+
|
|
1069
|
+
def href=(v)
|
|
1070
|
+
set_reflected_string("href", v)
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1073
|
+
def rel
|
|
1074
|
+
reflected_string("rel")
|
|
1075
|
+
end
|
|
1076
|
+
|
|
1077
|
+
def rel=(v)
|
|
1078
|
+
set_reflected_string("rel", v)
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
def type
|
|
1082
|
+
reflected_string("type")
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1085
|
+
def type=(v)
|
|
1086
|
+
set_reflected_string("type", v)
|
|
1087
|
+
end
|
|
1088
|
+
|
|
1089
|
+
def media
|
|
1090
|
+
reflected_string("media")
|
|
1091
|
+
end
|
|
1092
|
+
|
|
1093
|
+
def sizes
|
|
1094
|
+
reflected_string("sizes")
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
def hreflang
|
|
1098
|
+
reflected_string("hreflang")
|
|
1099
|
+
end
|
|
1100
|
+
|
|
1101
|
+
def as_attr
|
|
1102
|
+
reflected_string("as")
|
|
1103
|
+
end
|
|
1104
|
+
|
|
1105
|
+
def crossorigin
|
|
1106
|
+
reflected_string("crossorigin")
|
|
1107
|
+
end
|
|
1108
|
+
|
|
1109
|
+
def integrity
|
|
1110
|
+
reflected_string("integrity")
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
def referrer_policy
|
|
1114
|
+
reflected_string("referrerpolicy")
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
# `link.sheet` — non-nil only when this link is a stylesheet
|
|
1118
|
+
# (`rel` contains "stylesheet"). The sheet itself is a stub:
|
|
1119
|
+
# Dommy doesn't fetch or parse CSS, but consumers can still
|
|
1120
|
+
# `insertRule` / `deleteRule` against the in-memory sheet.
|
|
1121
|
+
def sheet
|
|
1122
|
+
return nil unless rel.split(/\s+/).any? { |t| t.casecmp("stylesheet").zero? }
|
|
1123
|
+
|
|
1124
|
+
@__sheet ||= CSSStyleSheet.new(
|
|
1125
|
+
owner_node: self,
|
|
1126
|
+
href: href,
|
|
1127
|
+
media: media,
|
|
1128
|
+
title: @__node__["title"].to_s,
|
|
1129
|
+
type: (type.empty? ? "text/css" : type)
|
|
1130
|
+
)
|
|
1131
|
+
end
|
|
1132
|
+
|
|
1133
|
+
def __js_get__(key)
|
|
1134
|
+
case key
|
|
1135
|
+
when "href"
|
|
1136
|
+
href
|
|
1137
|
+
when "rel"
|
|
1138
|
+
rel
|
|
1139
|
+
when "type"
|
|
1140
|
+
type
|
|
1141
|
+
when "media"
|
|
1142
|
+
media
|
|
1143
|
+
when "sizes"
|
|
1144
|
+
sizes
|
|
1145
|
+
when "hreflang"
|
|
1146
|
+
hreflang
|
|
1147
|
+
when "as"
|
|
1148
|
+
as_attr
|
|
1149
|
+
when "crossOrigin"
|
|
1150
|
+
crossorigin
|
|
1151
|
+
when "integrity"
|
|
1152
|
+
integrity
|
|
1153
|
+
when "referrerPolicy"
|
|
1154
|
+
referrer_policy
|
|
1155
|
+
when "sheet"
|
|
1156
|
+
sheet
|
|
1157
|
+
else
|
|
1158
|
+
super
|
|
1159
|
+
end
|
|
1160
|
+
end
|
|
1161
|
+
|
|
1162
|
+
def __js_set__(key, value)
|
|
1163
|
+
case key
|
|
1164
|
+
when "href", "rel", "type", "media", "sizes", "hreflang", "as", "integrity"
|
|
1165
|
+
set_reflected_string(key, value)
|
|
1166
|
+
when "crossOrigin"
|
|
1167
|
+
set_reflected_string("crossorigin", value)
|
|
1168
|
+
when "referrerPolicy"
|
|
1169
|
+
set_reflected_string("referrerpolicy", value)
|
|
1170
|
+
else
|
|
1171
|
+
super
|
|
1172
|
+
end
|
|
1173
|
+
end
|
|
1174
|
+
end
|
|
1175
|
+
|
|
1176
|
+
# `ValidityState` — computes constraint-validation flags from the
|
|
1177
|
+
# host control's current attributes and value. Bound to a single
|
|
1178
|
+
# host control; reads dynamically on every access so attribute
|
|
1179
|
+
# changes between calls are reflected.
|
|
1180
|
+
#
|
|
1181
|
+
# Flags follow the HTML spec; `badInput` is always false (we'd need
|
|
1182
|
+
# the browser's number parser to detect "12abc" in a type=number).
|
|
1183
|
+
class ValidityState
|
|
1184
|
+
FLAGS = %w[
|
|
1185
|
+
valueMissing
|
|
1186
|
+
typeMismatch
|
|
1187
|
+
patternMismatch
|
|
1188
|
+
tooLong
|
|
1189
|
+
tooShort
|
|
1190
|
+
rangeUnderflow
|
|
1191
|
+
rangeOverflow
|
|
1192
|
+
stepMismatch
|
|
1193
|
+
badInput
|
|
1194
|
+
customError
|
|
1195
|
+
]
|
|
1196
|
+
.freeze
|
|
1197
|
+
|
|
1198
|
+
EMAIL_RE = /\A[^@\s]+@[^@\s]+\.[^@\s]+\z/
|
|
1199
|
+
URL_SCHEMES = %w[http:// https:// ftp://].freeze
|
|
1200
|
+
|
|
1201
|
+
def initialize(host = nil)
|
|
1202
|
+
@host = host
|
|
1203
|
+
end
|
|
1204
|
+
|
|
1205
|
+
# ---- Computed flags ----
|
|
1206
|
+
|
|
1207
|
+
def value_missing
|
|
1208
|
+
return false unless @host && host_attr_present?("required")
|
|
1209
|
+
|
|
1210
|
+
case host_type
|
|
1211
|
+
when "checkbox", "radio"
|
|
1212
|
+
!host_attr_present?("checked")
|
|
1213
|
+
else
|
|
1214
|
+
host_value.to_s.empty?
|
|
1215
|
+
end
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
def type_mismatch
|
|
1219
|
+
return false unless @host
|
|
1220
|
+
|
|
1221
|
+
v = host_value.to_s
|
|
1222
|
+
return false if v.empty?
|
|
1223
|
+
|
|
1224
|
+
case host_type
|
|
1225
|
+
when "email"
|
|
1226
|
+
!v.match?(EMAIL_RE)
|
|
1227
|
+
when "url"
|
|
1228
|
+
URL_SCHEMES.none? { |s| v.start_with?(s) }
|
|
1229
|
+
else
|
|
1230
|
+
false
|
|
1231
|
+
end
|
|
1232
|
+
end
|
|
1233
|
+
|
|
1234
|
+
def pattern_mismatch
|
|
1235
|
+
return false unless @host
|
|
1236
|
+
|
|
1237
|
+
pat = host_attr_value("pattern").to_s
|
|
1238
|
+
return false if pat.empty?
|
|
1239
|
+
|
|
1240
|
+
v = host_value.to_s
|
|
1241
|
+
return false if v.empty?
|
|
1242
|
+
|
|
1243
|
+
!Regexp.new("\\A(?:#{pat})\\z").match?(v)
|
|
1244
|
+
rescue RegexpError
|
|
1245
|
+
false
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1248
|
+
def too_long
|
|
1249
|
+
return false unless @host
|
|
1250
|
+
|
|
1251
|
+
max = host_attr_value("maxlength").to_s
|
|
1252
|
+
return false if max.empty?
|
|
1253
|
+
|
|
1254
|
+
max_n = max.to_i
|
|
1255
|
+
return false if max_n < 0
|
|
1256
|
+
|
|
1257
|
+
host_value.to_s.length > max_n
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1260
|
+
def too_short
|
|
1261
|
+
return false unless @host
|
|
1262
|
+
|
|
1263
|
+
min = host_attr_value("minlength").to_s
|
|
1264
|
+
return false if min.empty?
|
|
1265
|
+
|
|
1266
|
+
min_n = min.to_i
|
|
1267
|
+
return false if min_n < 0
|
|
1268
|
+
|
|
1269
|
+
v = host_value.to_s
|
|
1270
|
+
!v.empty? && v.length < min_n
|
|
1271
|
+
end
|
|
1272
|
+
|
|
1273
|
+
def range_underflow
|
|
1274
|
+
return false unless numeric_host?
|
|
1275
|
+
|
|
1276
|
+
min = host_attr_value("min").to_s
|
|
1277
|
+
return false if min.empty?
|
|
1278
|
+
|
|
1279
|
+
num = numeric_value
|
|
1280
|
+
num && num < min.to_f
|
|
1281
|
+
end
|
|
1282
|
+
|
|
1283
|
+
def range_overflow
|
|
1284
|
+
return false unless numeric_host?
|
|
1285
|
+
|
|
1286
|
+
max = host_attr_value("max").to_s
|
|
1287
|
+
return false if max.empty?
|
|
1288
|
+
|
|
1289
|
+
num = numeric_value
|
|
1290
|
+
num && num > max.to_f
|
|
1291
|
+
end
|
|
1292
|
+
|
|
1293
|
+
def step_mismatch
|
|
1294
|
+
return false unless numeric_host?
|
|
1295
|
+
|
|
1296
|
+
step = host_attr_value("step").to_s
|
|
1297
|
+
return false if step.empty? || step == "any"
|
|
1298
|
+
|
|
1299
|
+
step_n = step.to_f
|
|
1300
|
+
return false if step_n <= 0
|
|
1301
|
+
|
|
1302
|
+
num = numeric_value
|
|
1303
|
+
return false unless num
|
|
1304
|
+
|
|
1305
|
+
base = host_attr_value("min").to_s
|
|
1306
|
+
base_n = base.empty? ? 0.0 : base.to_f
|
|
1307
|
+
((num - base_n) / step_n - ((num - base_n) / step_n).round).abs > 1e-9
|
|
1308
|
+
end
|
|
1309
|
+
|
|
1310
|
+
# `badInput` flags input that the user agent couldn't convert to
|
|
1311
|
+
# the host control's expected type. For Dommy this is meaningful
|
|
1312
|
+
# for type=number/range (raw string not a finite float) and
|
|
1313
|
+
# type=color (not a #rrggbb literal).
|
|
1314
|
+
def bad_input
|
|
1315
|
+
return false unless @host
|
|
1316
|
+
|
|
1317
|
+
raw = @host.respond_to?(:raw_value) ? @host.raw_value : host_value
|
|
1318
|
+
raw = raw.to_s
|
|
1319
|
+
return false if raw.empty?
|
|
1320
|
+
|
|
1321
|
+
case host_type
|
|
1322
|
+
when "number", "range"
|
|
1323
|
+
!valid_float?(raw)
|
|
1324
|
+
when "color"
|
|
1325
|
+
!raw.strip.downcase.match?(/\A#[0-9a-f]{6}\z/)
|
|
1326
|
+
else
|
|
1327
|
+
false
|
|
1328
|
+
end
|
|
1329
|
+
end
|
|
1330
|
+
|
|
1331
|
+
def valid_float?(s)
|
|
1332
|
+
Float(s)
|
|
1333
|
+
true
|
|
1334
|
+
rescue ArgumentError, TypeError
|
|
1335
|
+
false
|
|
1336
|
+
end
|
|
1337
|
+
|
|
1338
|
+
def custom_error
|
|
1339
|
+
!custom_message.empty?
|
|
1340
|
+
end
|
|
1341
|
+
|
|
1342
|
+
def valid
|
|
1343
|
+
!(value_missing ||
|
|
1344
|
+
type_mismatch ||
|
|
1345
|
+
pattern_mismatch ||
|
|
1346
|
+
too_long ||
|
|
1347
|
+
too_short ||
|
|
1348
|
+
range_underflow ||
|
|
1349
|
+
range_overflow ||
|
|
1350
|
+
step_mismatch ||
|
|
1351
|
+
bad_input ||
|
|
1352
|
+
custom_error)
|
|
1353
|
+
end
|
|
1354
|
+
|
|
1355
|
+
# ---- Bridge protocol ----
|
|
1356
|
+
|
|
1357
|
+
def __js_get__(key)
|
|
1358
|
+
case key
|
|
1359
|
+
when "valueMissing"
|
|
1360
|
+
value_missing
|
|
1361
|
+
when "typeMismatch"
|
|
1362
|
+
type_mismatch
|
|
1363
|
+
when "patternMismatch"
|
|
1364
|
+
pattern_mismatch
|
|
1365
|
+
when "tooLong"
|
|
1366
|
+
too_long
|
|
1367
|
+
when "tooShort"
|
|
1368
|
+
too_short
|
|
1369
|
+
when "rangeUnderflow"
|
|
1370
|
+
range_underflow
|
|
1371
|
+
when "rangeOverflow"
|
|
1372
|
+
range_overflow
|
|
1373
|
+
when "stepMismatch"
|
|
1374
|
+
step_mismatch
|
|
1375
|
+
when "badInput"
|
|
1376
|
+
bad_input
|
|
1377
|
+
when "customError"
|
|
1378
|
+
custom_error
|
|
1379
|
+
when "valid"
|
|
1380
|
+
valid
|
|
1381
|
+
end
|
|
1382
|
+
end
|
|
1383
|
+
|
|
1384
|
+
private
|
|
1385
|
+
|
|
1386
|
+
def host_value
|
|
1387
|
+
return "" unless @host
|
|
1388
|
+
|
|
1389
|
+
@host.respond_to?(:value) ? @host.value : @host.__js_get__("value")
|
|
1390
|
+
end
|
|
1391
|
+
|
|
1392
|
+
def host_attr_value(name)
|
|
1393
|
+
return "" unless @host
|
|
1394
|
+
|
|
1395
|
+
@host.__node__[name].to_s
|
|
1396
|
+
end
|
|
1397
|
+
|
|
1398
|
+
def host_attr_present?(name)
|
|
1399
|
+
return false unless @host
|
|
1400
|
+
|
|
1401
|
+
@host.__node__.key?(name.to_s)
|
|
1402
|
+
end
|
|
1403
|
+
|
|
1404
|
+
def host_type
|
|
1405
|
+
return nil unless @host
|
|
1406
|
+
|
|
1407
|
+
@host.respond_to?(:type) ? @host.type : ""
|
|
1408
|
+
end
|
|
1409
|
+
|
|
1410
|
+
def custom_message
|
|
1411
|
+
return "" unless @host
|
|
1412
|
+
|
|
1413
|
+
(@host.instance_variable_get(:@custom_validity_message) || "").to_s
|
|
1414
|
+
end
|
|
1415
|
+
|
|
1416
|
+
def numeric_host?
|
|
1417
|
+
@host.is_a?(HTMLInputElement) && %w[number range].include?(host_type)
|
|
1418
|
+
end
|
|
1419
|
+
|
|
1420
|
+
def numeric_value
|
|
1421
|
+
v = host_value.to_s
|
|
1422
|
+
return nil if v.empty?
|
|
1423
|
+
|
|
1424
|
+
Float(v)
|
|
1425
|
+
rescue ArgumentError
|
|
1426
|
+
nil
|
|
1427
|
+
end
|
|
1428
|
+
|
|
1429
|
+
def truthy?(value)
|
|
1430
|
+
v = value.to_s
|
|
1431
|
+
!v.empty? && v != "false" && v != "0"
|
|
1432
|
+
end
|
|
1433
|
+
end
|
|
1434
|
+
|
|
1435
|
+
# `<option>` — value, label, selected, disabled, text, index, form.
|
|
1436
|
+
class HTMLOptionElement < HTMLElement
|
|
1437
|
+
def value
|
|
1438
|
+
# Per spec, value defaults to text content if the `value`
|
|
1439
|
+
# attribute is absent.
|
|
1440
|
+
@__node__.key?("value") ? @__node__["value"].to_s : text_content
|
|
1441
|
+
end
|
|
1442
|
+
|
|
1443
|
+
def value=(v)
|
|
1444
|
+
set_reflected_string("value", v)
|
|
1445
|
+
end
|
|
1446
|
+
|
|
1447
|
+
def label
|
|
1448
|
+
@__node__.key?("label") ? @__node__["label"].to_s : text_content
|
|
1449
|
+
end
|
|
1450
|
+
|
|
1451
|
+
def label=(v)
|
|
1452
|
+
set_reflected_string("label", v)
|
|
1453
|
+
end
|
|
1454
|
+
|
|
1455
|
+
def selected
|
|
1456
|
+
reflected_boolean("selected")
|
|
1457
|
+
end
|
|
1458
|
+
|
|
1459
|
+
def selected=(v)
|
|
1460
|
+
set_reflected_boolean("selected", v)
|
|
1461
|
+
end
|
|
1462
|
+
|
|
1463
|
+
def default_selected
|
|
1464
|
+
selected
|
|
1465
|
+
end
|
|
1466
|
+
|
|
1467
|
+
def default_selected=(v)
|
|
1468
|
+
self.selected = v
|
|
1469
|
+
end
|
|
1470
|
+
|
|
1471
|
+
def disabled
|
|
1472
|
+
reflected_boolean("disabled")
|
|
1473
|
+
end
|
|
1474
|
+
|
|
1475
|
+
def disabled=(v)
|
|
1476
|
+
set_reflected_boolean("disabled", v)
|
|
1477
|
+
end
|
|
1478
|
+
|
|
1479
|
+
def text
|
|
1480
|
+
text_content
|
|
1481
|
+
end
|
|
1482
|
+
|
|
1483
|
+
def text=(v)
|
|
1484
|
+
self.text_content = v
|
|
1485
|
+
end
|
|
1486
|
+
|
|
1487
|
+
def form
|
|
1488
|
+
closest("form")
|
|
1489
|
+
end
|
|
1490
|
+
|
|
1491
|
+
# `index` — position within the containing select's options list.
|
|
1492
|
+
def index
|
|
1493
|
+
sel = closest("select")
|
|
1494
|
+
return 0 unless sel
|
|
1495
|
+
|
|
1496
|
+
sel.options.find_index { |o| o.__node__ == @__node__ } || 0
|
|
1497
|
+
end
|
|
1498
|
+
|
|
1499
|
+
def __js_get__(key)
|
|
1500
|
+
case key
|
|
1501
|
+
when "value"
|
|
1502
|
+
value
|
|
1503
|
+
when "label"
|
|
1504
|
+
label
|
|
1505
|
+
when "selected"
|
|
1506
|
+
selected
|
|
1507
|
+
when "defaultSelected"
|
|
1508
|
+
default_selected
|
|
1509
|
+
when "disabled"
|
|
1510
|
+
disabled
|
|
1511
|
+
when "text"
|
|
1512
|
+
text
|
|
1513
|
+
when "form"
|
|
1514
|
+
form
|
|
1515
|
+
when "index"
|
|
1516
|
+
index
|
|
1517
|
+
else
|
|
1518
|
+
super
|
|
1519
|
+
end
|
|
1520
|
+
end
|
|
1521
|
+
|
|
1522
|
+
def __js_set__(key, v)
|
|
1523
|
+
case key
|
|
1524
|
+
when "value"
|
|
1525
|
+
self.value = v
|
|
1526
|
+
when "label"
|
|
1527
|
+
self.label = v
|
|
1528
|
+
when "selected", "defaultSelected"
|
|
1529
|
+
self.selected = v
|
|
1530
|
+
when "disabled"
|
|
1531
|
+
self.disabled = v
|
|
1532
|
+
when "text"
|
|
1533
|
+
self.text = v
|
|
1534
|
+
else
|
|
1535
|
+
super
|
|
1536
|
+
end
|
|
1537
|
+
end
|
|
1538
|
+
end
|
|
1539
|
+
|
|
1540
|
+
# `<optgroup>` — label + disabled, container for options.
|
|
1541
|
+
class HTMLOptGroupElement < HTMLElement
|
|
1542
|
+
def label
|
|
1543
|
+
reflected_string("label")
|
|
1544
|
+
end
|
|
1545
|
+
|
|
1546
|
+
def label=(v)
|
|
1547
|
+
set_reflected_string("label", v)
|
|
1548
|
+
end
|
|
1549
|
+
|
|
1550
|
+
def disabled
|
|
1551
|
+
reflected_boolean("disabled")
|
|
1552
|
+
end
|
|
1553
|
+
|
|
1554
|
+
def disabled=(v)
|
|
1555
|
+
set_reflected_boolean("disabled", v)
|
|
1556
|
+
end
|
|
1557
|
+
|
|
1558
|
+
def __js_get__(key)
|
|
1559
|
+
case key
|
|
1560
|
+
when "label"
|
|
1561
|
+
label
|
|
1562
|
+
when "disabled"
|
|
1563
|
+
disabled
|
|
1564
|
+
else
|
|
1565
|
+
super
|
|
1566
|
+
end
|
|
1567
|
+
end
|
|
1568
|
+
|
|
1569
|
+
def __js_set__(key, v)
|
|
1570
|
+
case key
|
|
1571
|
+
when "label"
|
|
1572
|
+
self.label = v
|
|
1573
|
+
when "disabled"
|
|
1574
|
+
self.disabled = v
|
|
1575
|
+
else
|
|
1576
|
+
super
|
|
1577
|
+
end
|
|
1578
|
+
end
|
|
1579
|
+
end
|
|
1580
|
+
|
|
1581
|
+
# `<textarea>` — multi-line text input.
|
|
1582
|
+
class HTMLTextAreaElement < HTMLElement
|
|
1583
|
+
def value
|
|
1584
|
+
@__node__["value"] || text_content
|
|
1585
|
+
end
|
|
1586
|
+
|
|
1587
|
+
def value=(v)
|
|
1588
|
+
@__node__["value"] = v.to_s
|
|
1589
|
+
self.text_content = v.to_s
|
|
1590
|
+
end
|
|
1591
|
+
|
|
1592
|
+
def default_value
|
|
1593
|
+
text_content
|
|
1594
|
+
end
|
|
1595
|
+
|
|
1596
|
+
def default_value=(v)
|
|
1597
|
+
self.text_content = v
|
|
1598
|
+
end
|
|
1599
|
+
|
|
1600
|
+
def name
|
|
1601
|
+
reflected_string("name")
|
|
1602
|
+
end
|
|
1603
|
+
|
|
1604
|
+
def name=(v)
|
|
1605
|
+
set_reflected_string("name", v)
|
|
1606
|
+
end
|
|
1607
|
+
|
|
1608
|
+
def placeholder
|
|
1609
|
+
reflected_string("placeholder")
|
|
1610
|
+
end
|
|
1611
|
+
|
|
1612
|
+
def placeholder=(v)
|
|
1613
|
+
set_reflected_string("placeholder", v)
|
|
1614
|
+
end
|
|
1615
|
+
|
|
1616
|
+
def rows
|
|
1617
|
+
(@__node__["rows"] || "2").to_i
|
|
1618
|
+
end
|
|
1619
|
+
|
|
1620
|
+
def rows=(v)
|
|
1621
|
+
set_reflected_string("rows", v.to_s)
|
|
1622
|
+
end
|
|
1623
|
+
|
|
1624
|
+
def cols
|
|
1625
|
+
(@__node__["cols"] || "20").to_i
|
|
1626
|
+
end
|
|
1627
|
+
|
|
1628
|
+
def cols=(v)
|
|
1629
|
+
set_reflected_string("cols", v.to_s)
|
|
1630
|
+
end
|
|
1631
|
+
|
|
1632
|
+
def wrap
|
|
1633
|
+
reflected_string("wrap")
|
|
1634
|
+
end
|
|
1635
|
+
|
|
1636
|
+
def max_length
|
|
1637
|
+
(@__node__["maxlength"] || "-1").to_i
|
|
1638
|
+
end
|
|
1639
|
+
|
|
1640
|
+
def min_length
|
|
1641
|
+
(@__node__["minlength"] || "-1").to_i
|
|
1642
|
+
end
|
|
1643
|
+
|
|
1644
|
+
def text_length
|
|
1645
|
+
value.length
|
|
1646
|
+
end
|
|
1647
|
+
|
|
1648
|
+
def autocomplete
|
|
1649
|
+
reflected_string("autocomplete")
|
|
1650
|
+
end
|
|
1651
|
+
|
|
1652
|
+
def type
|
|
1653
|
+
"textarea"
|
|
1654
|
+
end
|
|
1655
|
+
|
|
1656
|
+
def form
|
|
1657
|
+
closest("form")
|
|
1658
|
+
end
|
|
1659
|
+
|
|
1660
|
+
def labels
|
|
1661
|
+
return [] if id.empty?
|
|
1662
|
+
|
|
1663
|
+
@document.query_selector_all("label[for='#{id}']")
|
|
1664
|
+
end
|
|
1665
|
+
|
|
1666
|
+
# No real selection — same stub story as input.
|
|
1667
|
+
def select
|
|
1668
|
+
nil
|
|
1669
|
+
end
|
|
1670
|
+
|
|
1671
|
+
def set_selection_range(_s, _e, _direction = nil)
|
|
1672
|
+
nil
|
|
1673
|
+
end
|
|
1674
|
+
|
|
1675
|
+
def set_range_text(_replacement, *_)
|
|
1676
|
+
nil
|
|
1677
|
+
end
|
|
1678
|
+
|
|
1679
|
+
def validity
|
|
1680
|
+
@__validity ||= ValidityState.new(self)
|
|
1681
|
+
end
|
|
1682
|
+
|
|
1683
|
+
def will_validate
|
|
1684
|
+
!reflected_boolean("disabled") && !reflected_boolean("readonly")
|
|
1685
|
+
end
|
|
1686
|
+
|
|
1687
|
+
def validation_message
|
|
1688
|
+
return "" unless will_validate
|
|
1689
|
+
|
|
1690
|
+
msg = (@custom_validity_message || "").to_s
|
|
1691
|
+
return msg unless msg.empty?
|
|
1692
|
+
return "Please fill out this field." if validity.value_missing
|
|
1693
|
+
|
|
1694
|
+
""
|
|
1695
|
+
end
|
|
1696
|
+
|
|
1697
|
+
def check_validity
|
|
1698
|
+
ok = !will_validate || validity.valid
|
|
1699
|
+
dispatch_event(Event.new("invalid", "bubbles" => false, "cancelable" => true)) unless ok
|
|
1700
|
+
ok
|
|
1701
|
+
end
|
|
1702
|
+
|
|
1703
|
+
def report_validity
|
|
1704
|
+
check_validity
|
|
1705
|
+
end
|
|
1706
|
+
|
|
1707
|
+
def set_custom_validity(msg)
|
|
1708
|
+
@custom_validity_message = msg.to_s
|
|
1709
|
+
nil
|
|
1710
|
+
end
|
|
1711
|
+
|
|
1712
|
+
def __js_get__(key)
|
|
1713
|
+
case key
|
|
1714
|
+
when "value"
|
|
1715
|
+
value
|
|
1716
|
+
when "defaultValue"
|
|
1717
|
+
default_value
|
|
1718
|
+
when "name"
|
|
1719
|
+
name
|
|
1720
|
+
when "placeholder"
|
|
1721
|
+
placeholder
|
|
1722
|
+
when "rows"
|
|
1723
|
+
rows
|
|
1724
|
+
when "cols"
|
|
1725
|
+
cols
|
|
1726
|
+
when "wrap"
|
|
1727
|
+
wrap
|
|
1728
|
+
when "maxLength"
|
|
1729
|
+
max_length
|
|
1730
|
+
when "minLength"
|
|
1731
|
+
min_length
|
|
1732
|
+
when "textLength"
|
|
1733
|
+
text_length
|
|
1734
|
+
when "autocomplete"
|
|
1735
|
+
autocomplete
|
|
1736
|
+
when "type"
|
|
1737
|
+
type
|
|
1738
|
+
when "form"
|
|
1739
|
+
form
|
|
1740
|
+
when "labels"
|
|
1741
|
+
labels
|
|
1742
|
+
when "validity"
|
|
1743
|
+
validity
|
|
1744
|
+
when "willValidate"
|
|
1745
|
+
will_validate
|
|
1746
|
+
when "validationMessage"
|
|
1747
|
+
validation_message
|
|
1748
|
+
else
|
|
1749
|
+
super
|
|
1750
|
+
end
|
|
1751
|
+
end
|
|
1752
|
+
|
|
1753
|
+
def __js_set__(key, v)
|
|
1754
|
+
case key
|
|
1755
|
+
when "value"
|
|
1756
|
+
self.value = v
|
|
1757
|
+
when "defaultValue"
|
|
1758
|
+
self.default_value = v
|
|
1759
|
+
when "name"
|
|
1760
|
+
set_reflected_string("name", v)
|
|
1761
|
+
when "placeholder"
|
|
1762
|
+
set_reflected_string("placeholder", v)
|
|
1763
|
+
when "rows"
|
|
1764
|
+
self.rows = v
|
|
1765
|
+
when "cols"
|
|
1766
|
+
self.cols = v
|
|
1767
|
+
when "wrap"
|
|
1768
|
+
set_reflected_string("wrap", v)
|
|
1769
|
+
when "maxLength"
|
|
1770
|
+
set_reflected_string("maxlength", v.to_s)
|
|
1771
|
+
when "minLength"
|
|
1772
|
+
set_reflected_string("minlength", v.to_s)
|
|
1773
|
+
else
|
|
1774
|
+
super
|
|
1775
|
+
end
|
|
1776
|
+
end
|
|
1777
|
+
|
|
1778
|
+
def __js_call__(method, args)
|
|
1779
|
+
case method
|
|
1780
|
+
when "select"
|
|
1781
|
+
select
|
|
1782
|
+
when "setSelectionRange"
|
|
1783
|
+
set_selection_range(args[0], args[1], args[2])
|
|
1784
|
+
when "setRangeText"
|
|
1785
|
+
set_range_text(args[0])
|
|
1786
|
+
when "checkValidity"
|
|
1787
|
+
check_validity
|
|
1788
|
+
when "reportValidity"
|
|
1789
|
+
report_validity
|
|
1790
|
+
when "setCustomValidity"
|
|
1791
|
+
set_custom_validity(args[0])
|
|
1792
|
+
else
|
|
1793
|
+
super
|
|
1794
|
+
end
|
|
1795
|
+
end
|
|
1796
|
+
# end HTMLTextAreaElement
|
|
1797
|
+
end
|
|
1798
|
+
|
|
1799
|
+
# `<label>` — `htmlFor` IDL maps to the HTML `for` attribute;
|
|
1800
|
+
# `control` returns the labelled form control.
|
|
1801
|
+
class HTMLLabelElement < HTMLElement
|
|
1802
|
+
def html_for
|
|
1803
|
+
reflected_string("for")
|
|
1804
|
+
end
|
|
1805
|
+
|
|
1806
|
+
def html_for=(v)
|
|
1807
|
+
set_reflected_string("for", v)
|
|
1808
|
+
end
|
|
1809
|
+
|
|
1810
|
+
# `label.control` — the form control associated with this label.
|
|
1811
|
+
# Priority: explicit `for=`, then first form control descendant.
|
|
1812
|
+
def control
|
|
1813
|
+
target = html_for
|
|
1814
|
+
if !target.empty?
|
|
1815
|
+
@document.get_element_by_id(target)
|
|
1816
|
+
else
|
|
1817
|
+
query_selector("input, select, textarea, button, output, meter, progress")
|
|
1818
|
+
end
|
|
1819
|
+
end
|
|
1820
|
+
|
|
1821
|
+
def form
|
|
1822
|
+
closest("form")
|
|
1823
|
+
end
|
|
1824
|
+
|
|
1825
|
+
def __js_get__(key)
|
|
1826
|
+
case key
|
|
1827
|
+
when "htmlFor"
|
|
1828
|
+
html_for
|
|
1829
|
+
when "control"
|
|
1830
|
+
control
|
|
1831
|
+
when "form"
|
|
1832
|
+
form
|
|
1833
|
+
else
|
|
1834
|
+
super
|
|
1835
|
+
end
|
|
1836
|
+
end
|
|
1837
|
+
|
|
1838
|
+
def __js_set__(key, v)
|
|
1839
|
+
case key
|
|
1840
|
+
when "htmlFor"
|
|
1841
|
+
self.html_for = v
|
|
1842
|
+
else
|
|
1843
|
+
super
|
|
1844
|
+
end
|
|
1845
|
+
end
|
|
1846
|
+
end
|
|
1847
|
+
|
|
1848
|
+
# `<fieldset>` — disabled-state-propagating wrapper; exposes
|
|
1849
|
+
# `elements` collection like form.
|
|
1850
|
+
class HTMLFieldsetElement < HTMLElement
|
|
1851
|
+
def name
|
|
1852
|
+
reflected_string("name")
|
|
1853
|
+
end
|
|
1854
|
+
|
|
1855
|
+
def name=(v)
|
|
1856
|
+
set_reflected_string("name", v)
|
|
1857
|
+
end
|
|
1858
|
+
|
|
1859
|
+
def disabled
|
|
1860
|
+
reflected_boolean("disabled")
|
|
1861
|
+
end
|
|
1862
|
+
|
|
1863
|
+
def disabled=(v)
|
|
1864
|
+
set_reflected_boolean("disabled", v)
|
|
1865
|
+
end
|
|
1866
|
+
|
|
1867
|
+
def type
|
|
1868
|
+
"fieldset"
|
|
1869
|
+
end
|
|
1870
|
+
|
|
1871
|
+
def form
|
|
1872
|
+
closest("form")
|
|
1873
|
+
end
|
|
1874
|
+
|
|
1875
|
+
def elements
|
|
1876
|
+
el = self
|
|
1877
|
+
HTMLCollection.new do
|
|
1878
|
+
el
|
|
1879
|
+
.__node__
|
|
1880
|
+
.css("input, select, textarea, button, output, fieldset")
|
|
1881
|
+
.map do |n|
|
|
1882
|
+
el.document.wrap_node(n)
|
|
1883
|
+
end
|
|
1884
|
+
.compact
|
|
1885
|
+
end
|
|
1886
|
+
end
|
|
1887
|
+
|
|
1888
|
+
def validity
|
|
1889
|
+
ValidityState.new
|
|
1890
|
+
end
|
|
1891
|
+
|
|
1892
|
+
def check_validity
|
|
1893
|
+
true
|
|
1894
|
+
end
|
|
1895
|
+
|
|
1896
|
+
def report_validity
|
|
1897
|
+
true
|
|
1898
|
+
end
|
|
1899
|
+
|
|
1900
|
+
def __js_get__(key)
|
|
1901
|
+
case key
|
|
1902
|
+
when "name"
|
|
1903
|
+
name
|
|
1904
|
+
when "disabled"
|
|
1905
|
+
disabled
|
|
1906
|
+
when "type"
|
|
1907
|
+
type
|
|
1908
|
+
when "form"
|
|
1909
|
+
form
|
|
1910
|
+
when "elements"
|
|
1911
|
+
elements
|
|
1912
|
+
when "validity"
|
|
1913
|
+
validity
|
|
1914
|
+
else
|
|
1915
|
+
super
|
|
1916
|
+
end
|
|
1917
|
+
end
|
|
1918
|
+
|
|
1919
|
+
def __js_set__(key, v)
|
|
1920
|
+
case key
|
|
1921
|
+
when "name"
|
|
1922
|
+
self.name = v
|
|
1923
|
+
when "disabled"
|
|
1924
|
+
self.disabled = v
|
|
1925
|
+
else
|
|
1926
|
+
super
|
|
1927
|
+
end
|
|
1928
|
+
end
|
|
1929
|
+
end
|
|
1930
|
+
|
|
1931
|
+
# `<output>` — calculation result element.
|
|
1932
|
+
class HTMLOutputElement < HTMLElement
|
|
1933
|
+
def value
|
|
1934
|
+
text_content
|
|
1935
|
+
end
|
|
1936
|
+
|
|
1937
|
+
def value=(v)
|
|
1938
|
+
self.text_content = v
|
|
1939
|
+
end
|
|
1940
|
+
|
|
1941
|
+
def default_value
|
|
1942
|
+
text_content
|
|
1943
|
+
end
|
|
1944
|
+
|
|
1945
|
+
def default_value=(v)
|
|
1946
|
+
self.text_content = v
|
|
1947
|
+
end
|
|
1948
|
+
|
|
1949
|
+
def name
|
|
1950
|
+
reflected_string("name")
|
|
1951
|
+
end
|
|
1952
|
+
|
|
1953
|
+
def name=(v)
|
|
1954
|
+
set_reflected_string("name", v)
|
|
1955
|
+
end
|
|
1956
|
+
|
|
1957
|
+
# `for` attribute is a space-separated list of IDs.
|
|
1958
|
+
def html_for_tokens
|
|
1959
|
+
reflected_string("for").split(/\s+/).reject(&:empty?)
|
|
1960
|
+
end
|
|
1961
|
+
|
|
1962
|
+
def form
|
|
1963
|
+
closest("form")
|
|
1964
|
+
end
|
|
1965
|
+
|
|
1966
|
+
def labels
|
|
1967
|
+
return [] if id.empty?
|
|
1968
|
+
|
|
1969
|
+
@document.query_selector_all("label[for='#{id}']")
|
|
1970
|
+
end
|
|
1971
|
+
|
|
1972
|
+
def type
|
|
1973
|
+
"output"
|
|
1974
|
+
end
|
|
1975
|
+
|
|
1976
|
+
def validity
|
|
1977
|
+
ValidityState.new
|
|
1978
|
+
end
|
|
1979
|
+
|
|
1980
|
+
def check_validity
|
|
1981
|
+
true
|
|
1982
|
+
end
|
|
1983
|
+
|
|
1984
|
+
def report_validity
|
|
1985
|
+
true
|
|
1986
|
+
end
|
|
1987
|
+
|
|
1988
|
+
def __js_get__(key)
|
|
1989
|
+
case key
|
|
1990
|
+
when "value"
|
|
1991
|
+
value
|
|
1992
|
+
when "defaultValue"
|
|
1993
|
+
default_value
|
|
1994
|
+
when "name"
|
|
1995
|
+
name
|
|
1996
|
+
when "type"
|
|
1997
|
+
type
|
|
1998
|
+
when "form"
|
|
1999
|
+
form
|
|
2000
|
+
when "labels"
|
|
2001
|
+
labels
|
|
2002
|
+
when "validity"
|
|
2003
|
+
validity
|
|
2004
|
+
when "htmlFor"
|
|
2005
|
+
reflected_string("for")
|
|
2006
|
+
else
|
|
2007
|
+
super
|
|
2008
|
+
end
|
|
2009
|
+
end
|
|
2010
|
+
|
|
2011
|
+
def __js_set__(key, v)
|
|
2012
|
+
case key
|
|
2013
|
+
when "value"
|
|
2014
|
+
self.value = v
|
|
2015
|
+
when "defaultValue"
|
|
2016
|
+
self.default_value = v
|
|
2017
|
+
when "name"
|
|
2018
|
+
self.name = v
|
|
2019
|
+
when "htmlFor"
|
|
2020
|
+
set_reflected_string("for", v)
|
|
2021
|
+
else
|
|
2022
|
+
super
|
|
2023
|
+
end
|
|
2024
|
+
end
|
|
2025
|
+
end
|
|
2026
|
+
|
|
2027
|
+
# `<legend>` — primarily exposes its `form` back-ref.
|
|
2028
|
+
class HTMLLegendElement < HTMLElement
|
|
2029
|
+
def form
|
|
2030
|
+
fieldset = closest("fieldset")
|
|
2031
|
+
fieldset&.closest("form") || closest("form")
|
|
2032
|
+
end
|
|
2033
|
+
|
|
2034
|
+
def __js_get__(key)
|
|
2035
|
+
key == "form" ? form : super
|
|
2036
|
+
end
|
|
2037
|
+
end
|
|
2038
|
+
|
|
2039
|
+
# `<slot>` — composes light DOM into the shadow tree. Light DOM
|
|
2040
|
+
# children of the shadow's host get assigned to slots: those whose
|
|
2041
|
+
# `slot=name` attribute matches a named slot, or those without a
|
|
2042
|
+
# `slot` attribute go to the unnamed default slot. If nothing is
|
|
2043
|
+
# assigned, the slot's own children render as fallback content.
|
|
2044
|
+
class HTMLSlotElement < HTMLElement
|
|
2045
|
+
def name
|
|
2046
|
+
reflected_string("name")
|
|
2047
|
+
end
|
|
2048
|
+
|
|
2049
|
+
def name=(v)
|
|
2050
|
+
set_reflected_string("name", v)
|
|
2051
|
+
end
|
|
2052
|
+
|
|
2053
|
+
# `slot.assignedNodes({ flatten: true|false })` — returns the
|
|
2054
|
+
# light DOM children currently composed into this slot. With
|
|
2055
|
+
# `flatten: true` and no assigned nodes, falls back to the
|
|
2056
|
+
# slot's own children (the default content).
|
|
2057
|
+
def assigned_nodes(options = nil)
|
|
2058
|
+
flatten = options.is_a?(Hash) ? (options["flatten"] || options[:flatten]) : false
|
|
2059
|
+
nodes = matching_light_nodes
|
|
2060
|
+
if nodes.empty? && flatten
|
|
2061
|
+
@__node__.children.map { |n| @document.wrap_node(n) }.compact
|
|
2062
|
+
else
|
|
2063
|
+
nodes
|
|
2064
|
+
end
|
|
2065
|
+
end
|
|
2066
|
+
|
|
2067
|
+
def assigned_elements(options = nil)
|
|
2068
|
+
assigned_nodes(options).select { |n| n.is_a?(Element) }
|
|
2069
|
+
end
|
|
2070
|
+
|
|
2071
|
+
# `slot.assign(...)` — manual assignment (honored only when the
|
|
2072
|
+
# owning shadow uses `slotAssignment: "manual"`). We accept the
|
|
2073
|
+
# call and fire `slotchange` in both modes; named mode simply
|
|
2074
|
+
# ignores the override.
|
|
2075
|
+
def assign(*nodes)
|
|
2076
|
+
@__manual_assignment = nodes.flatten.select { |n| n.respond_to?(:__node__) }
|
|
2077
|
+
dispatch_event(Event.new("slotchange", "bubbles" => true))
|
|
2078
|
+
nil
|
|
2079
|
+
end
|
|
2080
|
+
|
|
2081
|
+
def __js_get__(key)
|
|
2082
|
+
case key
|
|
2083
|
+
when "name"
|
|
2084
|
+
name
|
|
2085
|
+
when "assignedNodes"
|
|
2086
|
+
assigned_nodes
|
|
2087
|
+
when "assignedElements"
|
|
2088
|
+
assigned_elements
|
|
2089
|
+
else
|
|
2090
|
+
super
|
|
2091
|
+
end
|
|
2092
|
+
end
|
|
2093
|
+
|
|
2094
|
+
def __js_set__(key, value)
|
|
2095
|
+
case key
|
|
2096
|
+
when "name"
|
|
2097
|
+
self.name = value
|
|
2098
|
+
else
|
|
2099
|
+
super
|
|
2100
|
+
end
|
|
2101
|
+
end
|
|
2102
|
+
|
|
2103
|
+
def __js_call__(method, args)
|
|
2104
|
+
case method
|
|
2105
|
+
when "assignedNodes"
|
|
2106
|
+
assigned_nodes(args[0])
|
|
2107
|
+
when "assignedElements"
|
|
2108
|
+
assigned_elements(args[0])
|
|
2109
|
+
when "assign"
|
|
2110
|
+
assign(*args)
|
|
2111
|
+
else
|
|
2112
|
+
super
|
|
2113
|
+
end
|
|
2114
|
+
end
|
|
2115
|
+
|
|
2116
|
+
private
|
|
2117
|
+
|
|
2118
|
+
def matching_light_nodes
|
|
2119
|
+
sr = @document.__shadow_root_containing__(@__node__)
|
|
2120
|
+
return [] unless sr
|
|
2121
|
+
|
|
2122
|
+
host = sr.host
|
|
2123
|
+
return [] unless host
|
|
2124
|
+
|
|
2125
|
+
slot_name = name
|
|
2126
|
+
# Manual mode honors the explicit list.
|
|
2127
|
+
if sr.slot_assignment == "manual" && @__manual_assignment
|
|
2128
|
+
return @__manual_assignment
|
|
2129
|
+
end
|
|
2130
|
+
|
|
2131
|
+
host
|
|
2132
|
+
.__node__
|
|
2133
|
+
.children
|
|
2134
|
+
.map do |child|
|
|
2135
|
+
wrapped = @document.wrap_node(child)
|
|
2136
|
+
next nil unless wrapped
|
|
2137
|
+
|
|
2138
|
+
attr_value = child.element? ? child["slot"].to_s : ""
|
|
2139
|
+
if slot_name.empty?
|
|
2140
|
+
attr_value.empty? ? wrapped : nil
|
|
2141
|
+
else
|
|
2142
|
+
(child.element? && attr_value == slot_name) ? wrapped : nil
|
|
2143
|
+
end
|
|
2144
|
+
end
|
|
2145
|
+
.compact
|
|
2146
|
+
end
|
|
2147
|
+
end
|
|
2148
|
+
|
|
2149
|
+
# `<select>` — exposes `value` (selected option's value), `options`,
|
|
2150
|
+
# `selectedIndex`, and dispatches change events. Minimal compared to
|
|
2151
|
+
# happy-dom's full HTMLSelectElement, but covers common test cases.
|
|
2152
|
+
class HTMLSelectElement < HTMLElement
|
|
2153
|
+
def name
|
|
2154
|
+
reflected_string("name")
|
|
2155
|
+
end
|
|
2156
|
+
|
|
2157
|
+
def name=(v)
|
|
2158
|
+
set_reflected_string("name", v)
|
|
2159
|
+
end
|
|
2160
|
+
|
|
2161
|
+
def multiple
|
|
2162
|
+
reflected_boolean("multiple")
|
|
2163
|
+
end
|
|
2164
|
+
|
|
2165
|
+
def multiple=(v)
|
|
2166
|
+
set_reflected_boolean("multiple", v)
|
|
2167
|
+
end
|
|
2168
|
+
|
|
2169
|
+
def size
|
|
2170
|
+
@__node__["size"].to_s.to_i
|
|
2171
|
+
end
|
|
2172
|
+
|
|
2173
|
+
# `options` — all <option> descendants (including those inside
|
|
2174
|
+
# <optgroup>). Live HTMLOptionsCollection (HTMLCollection +
|
|
2175
|
+
# add/remove/selectedIndex/length= helpers).
|
|
2176
|
+
def options
|
|
2177
|
+
el = self
|
|
2178
|
+
HTMLOptionsCollection.new(self) do
|
|
2179
|
+
el.__node__.css("option").map { |n| el.document.wrap_node(n) }.compact
|
|
2180
|
+
end
|
|
2181
|
+
end
|
|
2182
|
+
|
|
2183
|
+
# `selectedOptions` — live collection of options with `selected`
|
|
2184
|
+
# attribute. When nothing is explicitly selected, browsers fall
|
|
2185
|
+
# back to the first option for non-multiple selects.
|
|
2186
|
+
def selected_options
|
|
2187
|
+
el = self
|
|
2188
|
+
HTMLCollection.new do
|
|
2189
|
+
opts = el.__node__.css("option").map { |n| el.document.wrap_node(n) }.compact
|
|
2190
|
+
chosen = opts.select { |o| o.__node__.key?("selected") }
|
|
2191
|
+
next chosen unless chosen.empty?
|
|
2192
|
+
next [] if el.multiple
|
|
2193
|
+
|
|
2194
|
+
opts.first ? [opts.first] : []
|
|
2195
|
+
end
|
|
2196
|
+
end
|
|
2197
|
+
|
|
2198
|
+
def length
|
|
2199
|
+
options.size
|
|
2200
|
+
end
|
|
2201
|
+
|
|
2202
|
+
def form
|
|
2203
|
+
closest("form")
|
|
2204
|
+
end
|
|
2205
|
+
|
|
2206
|
+
# `selectedIndex` — first option with `selected`, or 0 if none and
|
|
2207
|
+
# not multiple, or -1 if multiple and none.
|
|
2208
|
+
def selected_index
|
|
2209
|
+
opts = options
|
|
2210
|
+
idx = opts.find_index { |o| o.__node__.key?("selected") }
|
|
2211
|
+
return idx if idx
|
|
2212
|
+
|
|
2213
|
+
multiple ? -1 : (opts.empty? ? -1 : 0)
|
|
2214
|
+
end
|
|
2215
|
+
|
|
2216
|
+
def selected_index=(i)
|
|
2217
|
+
opts = options
|
|
2218
|
+
opts.each_with_index do |o, idx|
|
|
2219
|
+
if idx == i.to_i
|
|
2220
|
+
o.set_attribute("selected", "")
|
|
2221
|
+
elsif o.__node__.key?("selected")
|
|
2222
|
+
o.remove_attribute("selected")
|
|
2223
|
+
end
|
|
2224
|
+
end
|
|
2225
|
+
end
|
|
2226
|
+
|
|
2227
|
+
# `value` of the select = value of the selected option, or "".
|
|
2228
|
+
def value
|
|
2229
|
+
opts = options
|
|
2230
|
+
sel = opts.find { |o| o.__node__.key?("selected") } || opts.first
|
|
2231
|
+
sel ? (sel.__node__["value"] || sel.text_content).to_s : ""
|
|
2232
|
+
end
|
|
2233
|
+
|
|
2234
|
+
def value=(new_value)
|
|
2235
|
+
target = options.find { |o| (o.__node__["value"] || o.text_content).to_s == new_value.to_s }
|
|
2236
|
+
return unless target
|
|
2237
|
+
|
|
2238
|
+
options.each { |o| o.remove_attribute("selected") if o.__node__.key?("selected") }
|
|
2239
|
+
target.set_attribute("selected", "")
|
|
2240
|
+
end
|
|
2241
|
+
|
|
2242
|
+
# `select.item(i)` — returns the option at index i.
|
|
2243
|
+
def item(i)
|
|
2244
|
+
options[i.to_i]
|
|
2245
|
+
end
|
|
2246
|
+
|
|
2247
|
+
# `select.add(option, before)` — appends or inserts before `before`.
|
|
2248
|
+
def add(option, before = nil)
|
|
2249
|
+
return nil unless option.respond_to?(:__node__)
|
|
2250
|
+
|
|
2251
|
+
if before.respond_to?(:__node__)
|
|
2252
|
+
insert_before(option, before)
|
|
2253
|
+
else
|
|
2254
|
+
append_child(option)
|
|
2255
|
+
end
|
|
2256
|
+
|
|
2257
|
+
nil
|
|
2258
|
+
end
|
|
2259
|
+
|
|
2260
|
+
# `select.remove(i)` — removes the option at index i. (Note: also
|
|
2261
|
+
# inherits `remove()` from ChildNode for self-removal; spec lets
|
|
2262
|
+
# both forms coexist via overloading.)
|
|
2263
|
+
def remove_option(i)
|
|
2264
|
+
target = options[i.to_i]
|
|
2265
|
+
target&.remove
|
|
2266
|
+
end
|
|
2267
|
+
|
|
2268
|
+
def labels
|
|
2269
|
+
return [] if id.empty?
|
|
2270
|
+
|
|
2271
|
+
@document.query_selector_all("label[for='#{id}']")
|
|
2272
|
+
end
|
|
2273
|
+
|
|
2274
|
+
def type
|
|
2275
|
+
multiple ? "select-multiple" : "select-one"
|
|
2276
|
+
end
|
|
2277
|
+
|
|
2278
|
+
def validity
|
|
2279
|
+
@__validity ||= ValidityState.new(self)
|
|
2280
|
+
end
|
|
2281
|
+
|
|
2282
|
+
def will_validate
|
|
2283
|
+
!reflected_boolean("disabled")
|
|
2284
|
+
end
|
|
2285
|
+
|
|
2286
|
+
def validation_message
|
|
2287
|
+
return "" unless will_validate
|
|
2288
|
+
|
|
2289
|
+
msg = (@custom_validity_message || "").to_s
|
|
2290
|
+
return msg unless msg.empty?
|
|
2291
|
+
return "Please select an item in the list." if validity.value_missing
|
|
2292
|
+
|
|
2293
|
+
""
|
|
2294
|
+
end
|
|
2295
|
+
|
|
2296
|
+
def check_validity
|
|
2297
|
+
ok = !will_validate || validity.valid
|
|
2298
|
+
dispatch_event(Event.new("invalid", "bubbles" => false, "cancelable" => true)) unless ok
|
|
2299
|
+
ok
|
|
2300
|
+
end
|
|
2301
|
+
|
|
2302
|
+
def report_validity
|
|
2303
|
+
check_validity
|
|
2304
|
+
end
|
|
2305
|
+
|
|
2306
|
+
def set_custom_validity(msg)
|
|
2307
|
+
@custom_validity_message = msg.to_s
|
|
2308
|
+
nil
|
|
2309
|
+
end
|
|
2310
|
+
|
|
2311
|
+
def __js_get__(key)
|
|
2312
|
+
case key
|
|
2313
|
+
when "options"
|
|
2314
|
+
options
|
|
2315
|
+
when "length"
|
|
2316
|
+
length
|
|
2317
|
+
when "value"
|
|
2318
|
+
value
|
|
2319
|
+
when "name"
|
|
2320
|
+
name
|
|
2321
|
+
when "multiple"
|
|
2322
|
+
multiple
|
|
2323
|
+
when "size"
|
|
2324
|
+
size
|
|
2325
|
+
when "selectedIndex"
|
|
2326
|
+
selected_index
|
|
2327
|
+
when "form"
|
|
2328
|
+
form
|
|
2329
|
+
when "labels"
|
|
2330
|
+
labels
|
|
2331
|
+
when "type"
|
|
2332
|
+
type
|
|
2333
|
+
when "validity"
|
|
2334
|
+
validity
|
|
2335
|
+
when "willValidate"
|
|
2336
|
+
will_validate
|
|
2337
|
+
when "validationMessage"
|
|
2338
|
+
validation_message
|
|
2339
|
+
else
|
|
2340
|
+
super
|
|
2341
|
+
end
|
|
2342
|
+
end
|
|
2343
|
+
|
|
2344
|
+
def __js_set__(key, val)
|
|
2345
|
+
case key
|
|
2346
|
+
when "value"
|
|
2347
|
+
self.value = val
|
|
2348
|
+
when "name"
|
|
2349
|
+
set_reflected_string("name", val)
|
|
2350
|
+
when "multiple"
|
|
2351
|
+
set_reflected_boolean("multiple", val)
|
|
2352
|
+
when "selectedIndex"
|
|
2353
|
+
self.selected_index = val
|
|
2354
|
+
else
|
|
2355
|
+
super
|
|
2356
|
+
end
|
|
2357
|
+
end
|
|
2358
|
+
|
|
2359
|
+
def __js_call__(method, args)
|
|
2360
|
+
case method
|
|
2361
|
+
when "item"
|
|
2362
|
+
item(args[0])
|
|
2363
|
+
when "add"
|
|
2364
|
+
add(args[0], args[1])
|
|
2365
|
+
when "checkValidity"
|
|
2366
|
+
check_validity
|
|
2367
|
+
when "reportValidity"
|
|
2368
|
+
report_validity
|
|
2369
|
+
when "setCustomValidity"
|
|
2370
|
+
set_custom_validity(args[0])
|
|
2371
|
+
else
|
|
2372
|
+
super
|
|
2373
|
+
end
|
|
2374
|
+
end
|
|
2375
|
+
end
|
|
2376
|
+
|
|
2377
|
+
# `<dialog>` — `open` reflected boolean, `show()` / `showModal()` /
|
|
2378
|
+
# `close(returnValue?)`. Dommy has no modal stack, so showModal is
|
|
2379
|
+
# functionally identical to show (no backdrop, no escape-to-close).
|
|
2380
|
+
class HTMLDialogElement < HTMLElement
|
|
2381
|
+
def open
|
|
2382
|
+
reflected_boolean("open")
|
|
2383
|
+
end
|
|
2384
|
+
|
|
2385
|
+
def open=(v)
|
|
2386
|
+
set_reflected_boolean("open", v)
|
|
2387
|
+
end
|
|
2388
|
+
|
|
2389
|
+
def return_value
|
|
2390
|
+
@return_value ||= ""
|
|
2391
|
+
end
|
|
2392
|
+
|
|
2393
|
+
def return_value=(v)
|
|
2394
|
+
@return_value = v.to_s
|
|
2395
|
+
end
|
|
2396
|
+
|
|
2397
|
+
def show
|
|
2398
|
+
self.open = true
|
|
2399
|
+
nil
|
|
2400
|
+
end
|
|
2401
|
+
|
|
2402
|
+
def show_modal
|
|
2403
|
+
self.open = true
|
|
2404
|
+
nil
|
|
2405
|
+
end
|
|
2406
|
+
|
|
2407
|
+
def close(value = nil)
|
|
2408
|
+
self.open = false
|
|
2409
|
+
@return_value = value.to_s unless value.nil?
|
|
2410
|
+
dispatch_event(Event.new("close", "bubbles" => false, "cancelable" => false))
|
|
2411
|
+
nil
|
|
2412
|
+
end
|
|
2413
|
+
|
|
2414
|
+
def __js_get__(key)
|
|
2415
|
+
case key
|
|
2416
|
+
when "open"
|
|
2417
|
+
open
|
|
2418
|
+
when "returnValue"
|
|
2419
|
+
return_value
|
|
2420
|
+
else
|
|
2421
|
+
super
|
|
2422
|
+
end
|
|
2423
|
+
end
|
|
2424
|
+
|
|
2425
|
+
def __js_set__(key, value)
|
|
2426
|
+
case key
|
|
2427
|
+
when "open"
|
|
2428
|
+
self.open = value
|
|
2429
|
+
when "returnValue"
|
|
2430
|
+
self.return_value = value
|
|
2431
|
+
else
|
|
2432
|
+
super
|
|
2433
|
+
end
|
|
2434
|
+
end
|
|
2435
|
+
|
|
2436
|
+
def __js_call__(method, args)
|
|
2437
|
+
case method
|
|
2438
|
+
when "show"
|
|
2439
|
+
show
|
|
2440
|
+
when "showModal"
|
|
2441
|
+
show_modal
|
|
2442
|
+
when "close"
|
|
2443
|
+
close(args[0])
|
|
2444
|
+
else
|
|
2445
|
+
super
|
|
2446
|
+
end
|
|
2447
|
+
end
|
|
2448
|
+
end
|
|
2449
|
+
|
|
2450
|
+
# `<details>` — `open` reflected boolean. Toggling it fires a
|
|
2451
|
+
# `toggle` event (non-bubbling per spec).
|
|
2452
|
+
class HTMLDetailsElement < HTMLElement
|
|
2453
|
+
def open
|
|
2454
|
+
reflected_boolean("open")
|
|
2455
|
+
end
|
|
2456
|
+
|
|
2457
|
+
def open=(v)
|
|
2458
|
+
was = open
|
|
2459
|
+
set_reflected_boolean("open", v)
|
|
2460
|
+
now = open
|
|
2461
|
+
return if was == now
|
|
2462
|
+
|
|
2463
|
+
dispatch_event(Event.new("toggle", "bubbles" => false, "cancelable" => false))
|
|
2464
|
+
end
|
|
2465
|
+
|
|
2466
|
+
def __js_get__(key)
|
|
2467
|
+
key == "open" ? open : super
|
|
2468
|
+
end
|
|
2469
|
+
|
|
2470
|
+
def __js_set__(key, value)
|
|
2471
|
+
if key == "open"
|
|
2472
|
+
self.open = value
|
|
2473
|
+
else
|
|
2474
|
+
super
|
|
2475
|
+
end
|
|
2476
|
+
end
|
|
2477
|
+
end
|
|
2478
|
+
|
|
2479
|
+
# `<meter>` — gauge with `value` / `min` / `max` (default 0/0/1)
|
|
2480
|
+
# plus `low` / `high` / `optimum`. All numeric; `labels` via the
|
|
2481
|
+
# standard `<label for="...">` association.
|
|
2482
|
+
class HTMLMeterElement < HTMLElement
|
|
2483
|
+
def value
|
|
2484
|
+
numeric_attr("value", 0.0)
|
|
2485
|
+
end
|
|
2486
|
+
|
|
2487
|
+
def value=(v)
|
|
2488
|
+
set_reflected_string("value", v.to_s)
|
|
2489
|
+
end
|
|
2490
|
+
|
|
2491
|
+
def min
|
|
2492
|
+
numeric_attr("min", 0.0)
|
|
2493
|
+
end
|
|
2494
|
+
|
|
2495
|
+
def min=(v)
|
|
2496
|
+
set_reflected_string("min", v.to_s)
|
|
2497
|
+
end
|
|
2498
|
+
|
|
2499
|
+
def max
|
|
2500
|
+
numeric_attr("max", 1.0)
|
|
2501
|
+
end
|
|
2502
|
+
|
|
2503
|
+
def max=(v)
|
|
2504
|
+
set_reflected_string("max", v.to_s)
|
|
2505
|
+
end
|
|
2506
|
+
|
|
2507
|
+
def low
|
|
2508
|
+
numeric_attr("low", min)
|
|
2509
|
+
end
|
|
2510
|
+
|
|
2511
|
+
def low=(v)
|
|
2512
|
+
set_reflected_string("low", v.to_s)
|
|
2513
|
+
end
|
|
2514
|
+
|
|
2515
|
+
def high
|
|
2516
|
+
numeric_attr("high", max)
|
|
2517
|
+
end
|
|
2518
|
+
|
|
2519
|
+
def high=(v)
|
|
2520
|
+
set_reflected_string("high", v.to_s)
|
|
2521
|
+
end
|
|
2522
|
+
|
|
2523
|
+
def optimum
|
|
2524
|
+
numeric_attr("optimum", (min + max) / 2.0)
|
|
2525
|
+
end
|
|
2526
|
+
|
|
2527
|
+
def optimum=(v)
|
|
2528
|
+
set_reflected_string("optimum", v.to_s)
|
|
2529
|
+
end
|
|
2530
|
+
|
|
2531
|
+
def labels
|
|
2532
|
+
return [] if id.empty?
|
|
2533
|
+
|
|
2534
|
+
@document.query_selector_all("label[for='#{id}']")
|
|
2535
|
+
end
|
|
2536
|
+
|
|
2537
|
+
def __js_get__(key)
|
|
2538
|
+
case key
|
|
2539
|
+
when "value"
|
|
2540
|
+
value
|
|
2541
|
+
when "min"
|
|
2542
|
+
min
|
|
2543
|
+
when "max"
|
|
2544
|
+
max
|
|
2545
|
+
when "low"
|
|
2546
|
+
low
|
|
2547
|
+
when "high"
|
|
2548
|
+
high
|
|
2549
|
+
when "optimum"
|
|
2550
|
+
optimum
|
|
2551
|
+
when "labels"
|
|
2552
|
+
labels
|
|
2553
|
+
else
|
|
2554
|
+
super
|
|
2555
|
+
end
|
|
2556
|
+
end
|
|
2557
|
+
|
|
2558
|
+
def __js_set__(key, v)
|
|
2559
|
+
case key
|
|
2560
|
+
when "value", "min", "max", "low", "high", "optimum"
|
|
2561
|
+
set_reflected_string(key, v.to_s)
|
|
2562
|
+
else
|
|
2563
|
+
super
|
|
2564
|
+
end
|
|
2565
|
+
end
|
|
2566
|
+
|
|
2567
|
+
private
|
|
2568
|
+
|
|
2569
|
+
def numeric_attr(name, default)
|
|
2570
|
+
raw = @__node__[name].to_s
|
|
2571
|
+
raw.empty? ? default : Float(raw) rescue default
|
|
2572
|
+
end
|
|
2573
|
+
end
|
|
2574
|
+
|
|
2575
|
+
# `<progress>` — `value` and `max` (default max=1). `position`
|
|
2576
|
+
# returns `value / max` for a "determinate" progress bar, or -1
|
|
2577
|
+
# when no value is set ("indeterminate").
|
|
2578
|
+
class HTMLProgressElement < HTMLElement
|
|
2579
|
+
def value
|
|
2580
|
+
raw = @__node__["value"].to_s
|
|
2581
|
+
raw.empty? ? nil : Float(raw)
|
|
2582
|
+
rescue ArgumentError
|
|
2583
|
+
nil
|
|
2584
|
+
end
|
|
2585
|
+
|
|
2586
|
+
def value=(v)
|
|
2587
|
+
set_reflected_string("value", v.to_s)
|
|
2588
|
+
end
|
|
2589
|
+
|
|
2590
|
+
def max
|
|
2591
|
+
raw = @__node__["max"].to_s
|
|
2592
|
+
raw.empty? ? 1.0 : (Float(raw) rescue 1.0)
|
|
2593
|
+
end
|
|
2594
|
+
|
|
2595
|
+
def max=(v)
|
|
2596
|
+
set_reflected_string("max", v.to_s)
|
|
2597
|
+
end
|
|
2598
|
+
|
|
2599
|
+
# `position` = value/max for determinate progress; -1 if value
|
|
2600
|
+
# was never set (indeterminate).
|
|
2601
|
+
def position
|
|
2602
|
+
v = value
|
|
2603
|
+
return -1.0 if v.nil?
|
|
2604
|
+
|
|
2605
|
+
m = max
|
|
2606
|
+
m <= 0 ? 1.0 : (v / m)
|
|
2607
|
+
end
|
|
2608
|
+
|
|
2609
|
+
def labels
|
|
2610
|
+
return [] if id.empty?
|
|
2611
|
+
|
|
2612
|
+
@document.query_selector_all("label[for='#{id}']")
|
|
2613
|
+
end
|
|
2614
|
+
|
|
2615
|
+
def __js_get__(key)
|
|
2616
|
+
case key
|
|
2617
|
+
when "value"
|
|
2618
|
+
value
|
|
2619
|
+
when "max"
|
|
2620
|
+
max
|
|
2621
|
+
when "position"
|
|
2622
|
+
position
|
|
2623
|
+
when "labels"
|
|
2624
|
+
labels
|
|
2625
|
+
else
|
|
2626
|
+
super
|
|
2627
|
+
end
|
|
2628
|
+
end
|
|
2629
|
+
|
|
2630
|
+
def __js_set__(key, v)
|
|
2631
|
+
case key
|
|
2632
|
+
when "value", "max"
|
|
2633
|
+
set_reflected_string(key, v.to_s)
|
|
2634
|
+
else
|
|
2635
|
+
super
|
|
2636
|
+
end
|
|
2637
|
+
end
|
|
2638
|
+
end
|
|
2639
|
+
|
|
2640
|
+
# `<template>` — `content` returns the DocumentFragment that
|
|
2641
|
+
# owns the template's children. Reuses the document-level
|
|
2642
|
+
# template_content storage so existing template handling stays
|
|
2643
|
+
# consistent.
|
|
2644
|
+
class HTMLTemplateElement < HTMLElement
|
|
2645
|
+
def content
|
|
2646
|
+
@document.template_content_fragment(self)
|
|
2647
|
+
end
|
|
2648
|
+
|
|
2649
|
+
def __js_get__(key)
|
|
2650
|
+
case key
|
|
2651
|
+
when "content"
|
|
2652
|
+
content
|
|
2653
|
+
else
|
|
2654
|
+
super
|
|
2655
|
+
end
|
|
2656
|
+
end
|
|
2657
|
+
end
|
|
2658
|
+
|
|
2659
|
+
# `<td>` / `<th>` — single table cell. `cellIndex` is the
|
|
2660
|
+
# position within the parent row's cells collection.
|
|
2661
|
+
class HTMLTableCellElement < HTMLElement
|
|
2662
|
+
def cell_index
|
|
2663
|
+
row = closest("tr")
|
|
2664
|
+
return -1 unless row
|
|
2665
|
+
|
|
2666
|
+
row.cells.find_index { |c| c.__node__ == @__node__ } || -1
|
|
2667
|
+
end
|
|
2668
|
+
|
|
2669
|
+
def col_span
|
|
2670
|
+
(@__node__["colspan"] || "1").to_i
|
|
2671
|
+
end
|
|
2672
|
+
|
|
2673
|
+
def col_span=(v)
|
|
2674
|
+
set_reflected_string("colspan", v.to_s)
|
|
2675
|
+
end
|
|
2676
|
+
|
|
2677
|
+
def row_span
|
|
2678
|
+
(@__node__["rowspan"] || "1").to_i
|
|
2679
|
+
end
|
|
2680
|
+
|
|
2681
|
+
def row_span=(v)
|
|
2682
|
+
set_reflected_string("rowspan", v.to_s)
|
|
2683
|
+
end
|
|
2684
|
+
|
|
2685
|
+
def headers
|
|
2686
|
+
reflected_string("headers")
|
|
2687
|
+
end
|
|
2688
|
+
|
|
2689
|
+
def headers=(v)
|
|
2690
|
+
set_reflected_string("headers", v)
|
|
2691
|
+
end
|
|
2692
|
+
|
|
2693
|
+
# `scope` / `abbr` are only meaningful on `<th>`, but the IDL
|
|
2694
|
+
# exposes them on the cell element either way.
|
|
2695
|
+
def scope
|
|
2696
|
+
reflected_string("scope")
|
|
2697
|
+
end
|
|
2698
|
+
|
|
2699
|
+
def scope=(v)
|
|
2700
|
+
set_reflected_string("scope", v)
|
|
2701
|
+
end
|
|
2702
|
+
|
|
2703
|
+
def abbr
|
|
2704
|
+
reflected_string("abbr")
|
|
2705
|
+
end
|
|
2706
|
+
|
|
2707
|
+
def abbr=(v)
|
|
2708
|
+
set_reflected_string("abbr", v)
|
|
2709
|
+
end
|
|
2710
|
+
|
|
2711
|
+
def __js_get__(key)
|
|
2712
|
+
case key
|
|
2713
|
+
when "cellIndex"
|
|
2714
|
+
cell_index
|
|
2715
|
+
when "colSpan"
|
|
2716
|
+
col_span
|
|
2717
|
+
when "rowSpan"
|
|
2718
|
+
row_span
|
|
2719
|
+
when "headers"
|
|
2720
|
+
headers
|
|
2721
|
+
when "scope"
|
|
2722
|
+
scope
|
|
2723
|
+
when "abbr"
|
|
2724
|
+
abbr
|
|
2725
|
+
else
|
|
2726
|
+
super
|
|
2727
|
+
end
|
|
2728
|
+
end
|
|
2729
|
+
|
|
2730
|
+
def __js_set__(key, value)
|
|
2731
|
+
case key
|
|
2732
|
+
when "colSpan"
|
|
2733
|
+
self.col_span = value
|
|
2734
|
+
when "rowSpan"
|
|
2735
|
+
self.row_span = value
|
|
2736
|
+
when "headers"
|
|
2737
|
+
self.headers = value
|
|
2738
|
+
when "scope"
|
|
2739
|
+
self.scope = value
|
|
2740
|
+
when "abbr"
|
|
2741
|
+
self.abbr = value
|
|
2742
|
+
else
|
|
2743
|
+
super
|
|
2744
|
+
end
|
|
2745
|
+
end
|
|
2746
|
+
end
|
|
2747
|
+
|
|
2748
|
+
# `<tr>` — table row. `cells` are direct `<td>`/`<th>` children.
|
|
2749
|
+
# `rowIndex` walks the enclosing table; `sectionRowIndex` walks
|
|
2750
|
+
# the enclosing thead/tbody/tfoot.
|
|
2751
|
+
class HTMLTableRowElement < HTMLElement
|
|
2752
|
+
def cells
|
|
2753
|
+
el = self
|
|
2754
|
+
HTMLCollection.new do
|
|
2755
|
+
el
|
|
2756
|
+
.__node__
|
|
2757
|
+
.element_children
|
|
2758
|
+
.select { |n| %w[td th].include?(n.name) }
|
|
2759
|
+
.map { |n| el.document.wrap_node(n) }
|
|
2760
|
+
.compact
|
|
2761
|
+
end
|
|
2762
|
+
end
|
|
2763
|
+
|
|
2764
|
+
def row_index
|
|
2765
|
+
table = closest("table")
|
|
2766
|
+
return -1 unless table
|
|
2767
|
+
|
|
2768
|
+
table.rows.find_index { |r| r.__node__ == @__node__ } || -1
|
|
2769
|
+
end
|
|
2770
|
+
|
|
2771
|
+
def section_row_index
|
|
2772
|
+
section = @__node__.parent
|
|
2773
|
+
return -1 unless section && section.element? && %w[thead tbody tfoot].include?(section.name)
|
|
2774
|
+
|
|
2775
|
+
section.element_children.select { |n| n.name == "tr" }.find_index { |n| n == @__node__ } || -1
|
|
2776
|
+
end
|
|
2777
|
+
|
|
2778
|
+
# `insertCell(index)` — adds a `<td>` at the given index
|
|
2779
|
+
# (defaults to end). Returns the new cell.
|
|
2780
|
+
def insert_cell(index = -1)
|
|
2781
|
+
cell = @document.create_element("td")
|
|
2782
|
+
list = cells
|
|
2783
|
+
if index.to_i == -1 || index.to_i >= list.size
|
|
2784
|
+
append_child(cell)
|
|
2785
|
+
else
|
|
2786
|
+
insert_before(cell, list[index.to_i])
|
|
2787
|
+
end
|
|
2788
|
+
|
|
2789
|
+
cell
|
|
2790
|
+
end
|
|
2791
|
+
|
|
2792
|
+
def delete_cell(index)
|
|
2793
|
+
target = cells[index.to_i]
|
|
2794
|
+
target&.remove
|
|
2795
|
+
nil
|
|
2796
|
+
end
|
|
2797
|
+
|
|
2798
|
+
def __js_get__(key)
|
|
2799
|
+
case key
|
|
2800
|
+
when "cells"
|
|
2801
|
+
cells
|
|
2802
|
+
when "rowIndex"
|
|
2803
|
+
row_index
|
|
2804
|
+
when "sectionRowIndex"
|
|
2805
|
+
section_row_index
|
|
2806
|
+
else
|
|
2807
|
+
super
|
|
2808
|
+
end
|
|
2809
|
+
end
|
|
2810
|
+
|
|
2811
|
+
def __js_call__(method, args)
|
|
2812
|
+
case method
|
|
2813
|
+
when "insertCell"
|
|
2814
|
+
insert_cell(args[0] || -1)
|
|
2815
|
+
when "deleteCell"
|
|
2816
|
+
delete_cell(args[0])
|
|
2817
|
+
else
|
|
2818
|
+
super
|
|
2819
|
+
end
|
|
2820
|
+
end
|
|
2821
|
+
end
|
|
2822
|
+
|
|
2823
|
+
# `<thead>` / `<tbody>` / `<tfoot>` — share section-level row
|
|
2824
|
+
# collection + insertRow / deleteRow.
|
|
2825
|
+
class HTMLTableSectionElement < HTMLElement
|
|
2826
|
+
def rows
|
|
2827
|
+
el = self
|
|
2828
|
+
HTMLCollection.new do
|
|
2829
|
+
el
|
|
2830
|
+
.__node__
|
|
2831
|
+
.element_children
|
|
2832
|
+
.select { |n| n.name == "tr" }
|
|
2833
|
+
.map { |n| el.document.wrap_node(n) }
|
|
2834
|
+
.compact
|
|
2835
|
+
end
|
|
2836
|
+
end
|
|
2837
|
+
|
|
2838
|
+
def insert_row(index = -1)
|
|
2839
|
+
tr = @document.create_element("tr")
|
|
2840
|
+
list = rows
|
|
2841
|
+
if index.to_i == -1 || index.to_i >= list.size
|
|
2842
|
+
append_child(tr)
|
|
2843
|
+
else
|
|
2844
|
+
insert_before(tr, list[index.to_i])
|
|
2845
|
+
end
|
|
2846
|
+
|
|
2847
|
+
tr
|
|
2848
|
+
end
|
|
2849
|
+
|
|
2850
|
+
def delete_row(index)
|
|
2851
|
+
rows[index.to_i]&.remove
|
|
2852
|
+
nil
|
|
2853
|
+
end
|
|
2854
|
+
|
|
2855
|
+
def __js_get__(key)
|
|
2856
|
+
key == "rows" ? rows : super
|
|
2857
|
+
end
|
|
2858
|
+
|
|
2859
|
+
def __js_call__(method, args)
|
|
2860
|
+
case method
|
|
2861
|
+
when "insertRow"
|
|
2862
|
+
insert_row(args[0] || -1)
|
|
2863
|
+
when "deleteRow"
|
|
2864
|
+
delete_row(args[0])
|
|
2865
|
+
else
|
|
2866
|
+
super
|
|
2867
|
+
end
|
|
2868
|
+
end
|
|
2869
|
+
end
|
|
2870
|
+
|
|
2871
|
+
# `<caption>` — table caption, minimal subclass.
|
|
2872
|
+
class HTMLTableCaptionElement < HTMLElement
|
|
2873
|
+
end
|
|
2874
|
+
|
|
2875
|
+
# `<table>` — top-level table element. `rows` returns rows from
|
|
2876
|
+
# all sections (thead → tbody → tfoot); `tBodies` is a list of
|
|
2877
|
+
# tbody elements. `insertRow(-1)` appends to the last tbody (or
|
|
2878
|
+
# creates one); `deleteRow` works against the merged `rows` list.
|
|
2879
|
+
class HTMLTableElement < HTMLElement
|
|
2880
|
+
def caption
|
|
2881
|
+
@__node__.element_children.find { |n| n.name == "caption" }&.then { |n| @document.wrap_node(n) }
|
|
2882
|
+
end
|
|
2883
|
+
|
|
2884
|
+
def caption=(new_caption)
|
|
2885
|
+
delete_caption
|
|
2886
|
+
return unless new_caption.respond_to?(:__node__)
|
|
2887
|
+
|
|
2888
|
+
first = @__node__.children.first
|
|
2889
|
+
first ? first.add_previous_sibling(new_caption.__node__) : @__node__.add_child(new_caption.__node__)
|
|
2890
|
+
end
|
|
2891
|
+
|
|
2892
|
+
def t_head
|
|
2893
|
+
@__node__.element_children.find { |n| n.name == "thead" }&.then { |n| @document.wrap_node(n) }
|
|
2894
|
+
end
|
|
2895
|
+
|
|
2896
|
+
def t_foot
|
|
2897
|
+
@__node__.element_children.find { |n| n.name == "tfoot" }&.then { |n| @document.wrap_node(n) }
|
|
2898
|
+
end
|
|
2899
|
+
|
|
2900
|
+
def t_bodies
|
|
2901
|
+
el = self
|
|
2902
|
+
HTMLCollection.new do
|
|
2903
|
+
el
|
|
2904
|
+
.__node__
|
|
2905
|
+
.element_children
|
|
2906
|
+
.select { |n| n.name == "tbody" }
|
|
2907
|
+
.map { |n| el.document.wrap_node(n) }
|
|
2908
|
+
.compact
|
|
2909
|
+
end
|
|
2910
|
+
end
|
|
2911
|
+
|
|
2912
|
+
def rows
|
|
2913
|
+
el = self
|
|
2914
|
+
HTMLCollection.new do
|
|
2915
|
+
ordered = []
|
|
2916
|
+
head = el.__node__.element_children.find { |n| n.name == "thead" }
|
|
2917
|
+
bodies = el.__node__.element_children.select { |n| n.name == "tbody" }
|
|
2918
|
+
direct = el.__node__.element_children.select { |n| n.name == "tr" }
|
|
2919
|
+
foot = el.__node__.element_children.find { |n| n.name == "tfoot" }
|
|
2920
|
+
[head, *bodies, foot].compact.each do |sec|
|
|
2921
|
+
sec.element_children.select { |n| n.name == "tr" }.each { |n| ordered << n }
|
|
2922
|
+
end
|
|
2923
|
+
|
|
2924
|
+
direct.each { |n| ordered << n }
|
|
2925
|
+
ordered.map { |n| el.document.wrap_node(n) }.compact
|
|
2926
|
+
end
|
|
2927
|
+
end
|
|
2928
|
+
|
|
2929
|
+
def create_caption
|
|
2930
|
+
existing = caption
|
|
2931
|
+
return existing if existing
|
|
2932
|
+
|
|
2933
|
+
cap = @document.create_element("caption")
|
|
2934
|
+
first = @__node__.children.first
|
|
2935
|
+
first ? first.add_previous_sibling(cap.__node__) : @__node__.add_child(cap.__node__)
|
|
2936
|
+
cap
|
|
2937
|
+
end
|
|
2938
|
+
|
|
2939
|
+
def delete_caption
|
|
2940
|
+
cap = caption
|
|
2941
|
+
cap&.remove
|
|
2942
|
+
nil
|
|
2943
|
+
end
|
|
2944
|
+
|
|
2945
|
+
def create_t_head
|
|
2946
|
+
existing = t_head
|
|
2947
|
+
return existing if existing
|
|
2948
|
+
|
|
2949
|
+
head = @document.create_element("thead")
|
|
2950
|
+
cap = caption
|
|
2951
|
+
if cap
|
|
2952
|
+
cap.__node__.add_next_sibling(head.__node__)
|
|
2953
|
+
else
|
|
2954
|
+
first = @__node__.children.first
|
|
2955
|
+
first ? first.add_previous_sibling(head.__node__) : @__node__.add_child(head.__node__)
|
|
2956
|
+
end
|
|
2957
|
+
|
|
2958
|
+
head
|
|
2959
|
+
end
|
|
2960
|
+
|
|
2961
|
+
def delete_t_head
|
|
2962
|
+
t_head&.remove
|
|
2963
|
+
nil
|
|
2964
|
+
end
|
|
2965
|
+
|
|
2966
|
+
def create_t_foot
|
|
2967
|
+
existing = t_foot
|
|
2968
|
+
return existing if existing
|
|
2969
|
+
|
|
2970
|
+
foot = @document.create_element("tfoot")
|
|
2971
|
+
@__node__.add_child(foot.__node__)
|
|
2972
|
+
foot
|
|
2973
|
+
end
|
|
2974
|
+
|
|
2975
|
+
def delete_t_foot
|
|
2976
|
+
t_foot&.remove
|
|
2977
|
+
nil
|
|
2978
|
+
end
|
|
2979
|
+
|
|
2980
|
+
def create_t_body
|
|
2981
|
+
tb = @document.create_element("tbody")
|
|
2982
|
+
last_tbody = t_bodies.last
|
|
2983
|
+
if last_tbody
|
|
2984
|
+
last_tbody.__node__.add_next_sibling(tb.__node__)
|
|
2985
|
+
else
|
|
2986
|
+
@__node__.add_child(tb.__node__)
|
|
2987
|
+
end
|
|
2988
|
+
|
|
2989
|
+
tb
|
|
2990
|
+
end
|
|
2991
|
+
|
|
2992
|
+
# `table.insertRow(index)` — inserts a `<tr>` at the merged
|
|
2993
|
+
# index. Per spec, if no `<tbody>` exists and the table is
|
|
2994
|
+
# empty, the row is inserted directly; otherwise it goes into
|
|
2995
|
+
# the last `<tbody>`.
|
|
2996
|
+
def insert_row(index = -1)
|
|
2997
|
+
list = rows.to_a
|
|
2998
|
+
raw = index.to_i
|
|
2999
|
+
raise DOMException::IndexSizeError, "row index #{raw} out of range" if raw < -1 || raw > list.size
|
|
3000
|
+
|
|
3001
|
+
idx = raw == -1 ? list.size : raw
|
|
3002
|
+
|
|
3003
|
+
tr = @document.create_element("tr")
|
|
3004
|
+
if idx == list.size
|
|
3005
|
+
target_section = t_bodies.last || create_t_body
|
|
3006
|
+
target_section.append_child(tr)
|
|
3007
|
+
else
|
|
3008
|
+
anchor = list[idx]
|
|
3009
|
+
section = anchor.__node__.parent
|
|
3010
|
+
if section
|
|
3011
|
+
anchor.__node__.add_previous_sibling(tr.__node__)
|
|
3012
|
+
@document.notify_child_list_mutation(target_node: section, added_nodes: [tr.__node__], removed_nodes: [])
|
|
3013
|
+
end
|
|
3014
|
+
end
|
|
3015
|
+
|
|
3016
|
+
tr
|
|
3017
|
+
end
|
|
3018
|
+
|
|
3019
|
+
def delete_row(index)
|
|
3020
|
+
rows[index.to_i]&.remove
|
|
3021
|
+
nil
|
|
3022
|
+
end
|
|
3023
|
+
|
|
3024
|
+
def __js_get__(key)
|
|
3025
|
+
case key
|
|
3026
|
+
when "caption"
|
|
3027
|
+
caption
|
|
3028
|
+
when "tHead"
|
|
3029
|
+
t_head
|
|
3030
|
+
when "tFoot"
|
|
3031
|
+
t_foot
|
|
3032
|
+
when "tBodies"
|
|
3033
|
+
t_bodies
|
|
3034
|
+
when "rows"
|
|
3035
|
+
rows
|
|
3036
|
+
else
|
|
3037
|
+
super
|
|
3038
|
+
end
|
|
3039
|
+
end
|
|
3040
|
+
|
|
3041
|
+
def __js_set__(key, value)
|
|
3042
|
+
case key
|
|
3043
|
+
when "caption"
|
|
3044
|
+
self.caption = value
|
|
3045
|
+
else
|
|
3046
|
+
super
|
|
3047
|
+
end
|
|
3048
|
+
end
|
|
3049
|
+
|
|
3050
|
+
def __js_call__(method, args)
|
|
3051
|
+
case method
|
|
3052
|
+
when "insertRow"
|
|
3053
|
+
insert_row(args[0] || -1)
|
|
3054
|
+
when "deleteRow"
|
|
3055
|
+
delete_row(args[0])
|
|
3056
|
+
when "createCaption"
|
|
3057
|
+
create_caption
|
|
3058
|
+
when "deleteCaption"
|
|
3059
|
+
delete_caption
|
|
3060
|
+
when "createTHead"
|
|
3061
|
+
create_t_head
|
|
3062
|
+
when "deleteTHead"
|
|
3063
|
+
delete_t_head
|
|
3064
|
+
when "createTFoot"
|
|
3065
|
+
create_t_foot
|
|
3066
|
+
when "deleteTFoot"
|
|
3067
|
+
delete_t_foot
|
|
3068
|
+
when "createTBody"
|
|
3069
|
+
create_t_body
|
|
3070
|
+
else
|
|
3071
|
+
super
|
|
3072
|
+
end
|
|
3073
|
+
end
|
|
3074
|
+
end
|
|
3075
|
+
|
|
3076
|
+
# `<audio>` / `<video>` shared base. The actual media engine is
|
|
3077
|
+
# absent in Dommy — getters return inert values, `play()` returns
|
|
3078
|
+
# a resolved Promise, and `pause()` flips `paused` back to true.
|
|
3079
|
+
class HTMLMediaElement < HTMLElement
|
|
3080
|
+
NETWORK_EMPTY = 0
|
|
3081
|
+
NETWORK_IDLE = 1
|
|
3082
|
+
NETWORK_LOADING = 2
|
|
3083
|
+
NETWORK_NO_SOURCE = 3
|
|
3084
|
+
|
|
3085
|
+
HAVE_NOTHING = 0
|
|
3086
|
+
HAVE_METADATA = 1
|
|
3087
|
+
HAVE_CURRENT_DATA = 2
|
|
3088
|
+
HAVE_FUTURE_DATA = 3
|
|
3089
|
+
HAVE_ENOUGH_DATA = 4
|
|
3090
|
+
|
|
3091
|
+
def src
|
|
3092
|
+
reflected_string("src")
|
|
3093
|
+
end
|
|
3094
|
+
|
|
3095
|
+
def src=(v)
|
|
3096
|
+
set_reflected_string("src", v)
|
|
3097
|
+
end
|
|
3098
|
+
|
|
3099
|
+
def current_src
|
|
3100
|
+
src
|
|
3101
|
+
end
|
|
3102
|
+
|
|
3103
|
+
def preload
|
|
3104
|
+
reflected_string("preload")
|
|
3105
|
+
end
|
|
3106
|
+
|
|
3107
|
+
def preload=(v)
|
|
3108
|
+
set_reflected_string("preload", v)
|
|
3109
|
+
end
|
|
3110
|
+
|
|
3111
|
+
def crossorigin
|
|
3112
|
+
reflected_string("crossorigin")
|
|
3113
|
+
end
|
|
3114
|
+
|
|
3115
|
+
def autoplay
|
|
3116
|
+
reflected_boolean("autoplay")
|
|
3117
|
+
end
|
|
3118
|
+
|
|
3119
|
+
def autoplay=(v)
|
|
3120
|
+
set_reflected_boolean("autoplay", v)
|
|
3121
|
+
end
|
|
3122
|
+
|
|
3123
|
+
def loop_
|
|
3124
|
+
reflected_boolean("loop")
|
|
3125
|
+
end
|
|
3126
|
+
|
|
3127
|
+
def loop_=(v)
|
|
3128
|
+
set_reflected_boolean("loop", v)
|
|
3129
|
+
end
|
|
3130
|
+
|
|
3131
|
+
def controls
|
|
3132
|
+
reflected_boolean("controls")
|
|
3133
|
+
end
|
|
3134
|
+
|
|
3135
|
+
def controls=(v)
|
|
3136
|
+
set_reflected_boolean("controls", v)
|
|
3137
|
+
end
|
|
3138
|
+
|
|
3139
|
+
def muted
|
|
3140
|
+
@__muted == true || reflected_boolean("muted")
|
|
3141
|
+
end
|
|
3142
|
+
|
|
3143
|
+
def muted=(v)
|
|
3144
|
+
@__muted = !!v
|
|
3145
|
+
end
|
|
3146
|
+
|
|
3147
|
+
def default_muted
|
|
3148
|
+
reflected_boolean("muted")
|
|
3149
|
+
end
|
|
3150
|
+
|
|
3151
|
+
def default_muted=(v)
|
|
3152
|
+
set_reflected_boolean("muted", v)
|
|
3153
|
+
end
|
|
3154
|
+
|
|
3155
|
+
def paused
|
|
3156
|
+
@__paused.nil? ? true : @__paused
|
|
3157
|
+
end
|
|
3158
|
+
|
|
3159
|
+
def ended
|
|
3160
|
+
false
|
|
3161
|
+
end
|
|
3162
|
+
|
|
3163
|
+
def seeking
|
|
3164
|
+
false
|
|
3165
|
+
end
|
|
3166
|
+
|
|
3167
|
+
def volume
|
|
3168
|
+
@__volume.nil? ? 1.0 : @__volume
|
|
3169
|
+
end
|
|
3170
|
+
|
|
3171
|
+
def volume=(v)
|
|
3172
|
+
@__volume = v.to_f
|
|
3173
|
+
end
|
|
3174
|
+
|
|
3175
|
+
def playback_rate
|
|
3176
|
+
@__rate.nil? ? 1.0 : @__rate
|
|
3177
|
+
end
|
|
3178
|
+
|
|
3179
|
+
def playback_rate=(v)
|
|
3180
|
+
@__rate = v.to_f
|
|
3181
|
+
end
|
|
3182
|
+
|
|
3183
|
+
def default_playback_rate
|
|
3184
|
+
@__default_rate.nil? ? 1.0 : @__default_rate
|
|
3185
|
+
end
|
|
3186
|
+
|
|
3187
|
+
def default_playback_rate=(v)
|
|
3188
|
+
@__default_rate = v.to_f
|
|
3189
|
+
end
|
|
3190
|
+
|
|
3191
|
+
def current_time
|
|
3192
|
+
@__current_time.to_f
|
|
3193
|
+
end
|
|
3194
|
+
|
|
3195
|
+
def current_time=(v)
|
|
3196
|
+
@__current_time = v.to_f
|
|
3197
|
+
end
|
|
3198
|
+
|
|
3199
|
+
def duration
|
|
3200
|
+
Float::NAN
|
|
3201
|
+
end
|
|
3202
|
+
|
|
3203
|
+
def network_state
|
|
3204
|
+
NETWORK_EMPTY
|
|
3205
|
+
end
|
|
3206
|
+
|
|
3207
|
+
def ready_state
|
|
3208
|
+
HAVE_NOTHING
|
|
3209
|
+
end
|
|
3210
|
+
|
|
3211
|
+
def play
|
|
3212
|
+
@__paused = false
|
|
3213
|
+
promise = PromiseValue.new(@document.default_view)
|
|
3214
|
+
promise.fulfill(nil)
|
|
3215
|
+
promise
|
|
3216
|
+
end
|
|
3217
|
+
|
|
3218
|
+
def pause
|
|
3219
|
+
@__paused = true
|
|
3220
|
+
nil
|
|
3221
|
+
end
|
|
3222
|
+
|
|
3223
|
+
def load
|
|
3224
|
+
nil
|
|
3225
|
+
end
|
|
3226
|
+
|
|
3227
|
+
def can_play_type(_type)
|
|
3228
|
+
# spec: "" | "maybe" | "probably". We don't decode → "".
|
|
3229
|
+
""
|
|
3230
|
+
end
|
|
3231
|
+
|
|
3232
|
+
def __js_get__(key)
|
|
3233
|
+
case key
|
|
3234
|
+
when "src"
|
|
3235
|
+
src
|
|
3236
|
+
when "currentSrc"
|
|
3237
|
+
current_src
|
|
3238
|
+
when "preload"
|
|
3239
|
+
preload
|
|
3240
|
+
when "crossOrigin"
|
|
3241
|
+
crossorigin
|
|
3242
|
+
when "autoplay"
|
|
3243
|
+
autoplay
|
|
3244
|
+
when "loop"
|
|
3245
|
+
loop_
|
|
3246
|
+
when "controls"
|
|
3247
|
+
controls
|
|
3248
|
+
when "muted"
|
|
3249
|
+
muted
|
|
3250
|
+
when "defaultMuted"
|
|
3251
|
+
default_muted
|
|
3252
|
+
when "paused"
|
|
3253
|
+
paused
|
|
3254
|
+
when "ended"
|
|
3255
|
+
ended
|
|
3256
|
+
when "seeking"
|
|
3257
|
+
seeking
|
|
3258
|
+
when "volume"
|
|
3259
|
+
volume
|
|
3260
|
+
when "playbackRate"
|
|
3261
|
+
playback_rate
|
|
3262
|
+
when "defaultPlaybackRate"
|
|
3263
|
+
default_playback_rate
|
|
3264
|
+
when "currentTime"
|
|
3265
|
+
current_time
|
|
3266
|
+
when "duration"
|
|
3267
|
+
duration
|
|
3268
|
+
when "networkState"
|
|
3269
|
+
network_state
|
|
3270
|
+
when "readyState"
|
|
3271
|
+
ready_state
|
|
3272
|
+
when "NETWORK_EMPTY"
|
|
3273
|
+
NETWORK_EMPTY
|
|
3274
|
+
when "NETWORK_IDLE"
|
|
3275
|
+
NETWORK_IDLE
|
|
3276
|
+
when "NETWORK_LOADING"
|
|
3277
|
+
NETWORK_LOADING
|
|
3278
|
+
when "NETWORK_NO_SOURCE"
|
|
3279
|
+
NETWORK_NO_SOURCE
|
|
3280
|
+
when "HAVE_NOTHING"
|
|
3281
|
+
HAVE_NOTHING
|
|
3282
|
+
when "HAVE_METADATA"
|
|
3283
|
+
HAVE_METADATA
|
|
3284
|
+
when "HAVE_CURRENT_DATA"
|
|
3285
|
+
HAVE_CURRENT_DATA
|
|
3286
|
+
when "HAVE_FUTURE_DATA"
|
|
3287
|
+
HAVE_FUTURE_DATA
|
|
3288
|
+
when "HAVE_ENOUGH_DATA"
|
|
3289
|
+
HAVE_ENOUGH_DATA
|
|
3290
|
+
else
|
|
3291
|
+
super
|
|
3292
|
+
end
|
|
3293
|
+
end
|
|
3294
|
+
|
|
3295
|
+
def __js_set__(key, value)
|
|
3296
|
+
case key
|
|
3297
|
+
when "src"
|
|
3298
|
+
self.src = value
|
|
3299
|
+
when "preload"
|
|
3300
|
+
self.preload = value
|
|
3301
|
+
when "autoplay"
|
|
3302
|
+
self.autoplay = value
|
|
3303
|
+
when "loop"
|
|
3304
|
+
self.loop_ = value
|
|
3305
|
+
when "controls"
|
|
3306
|
+
self.controls = value
|
|
3307
|
+
when "muted"
|
|
3308
|
+
self.muted = value
|
|
3309
|
+
when "defaultMuted"
|
|
3310
|
+
self.default_muted = value
|
|
3311
|
+
when "volume"
|
|
3312
|
+
self.volume = value
|
|
3313
|
+
when "playbackRate"
|
|
3314
|
+
self.playback_rate = value
|
|
3315
|
+
when "defaultPlaybackRate"
|
|
3316
|
+
self.default_playback_rate = value
|
|
3317
|
+
when "currentTime"
|
|
3318
|
+
self.current_time = value
|
|
3319
|
+
else
|
|
3320
|
+
super
|
|
3321
|
+
end
|
|
3322
|
+
end
|
|
3323
|
+
|
|
3324
|
+
def __js_call__(method, args)
|
|
3325
|
+
case method
|
|
3326
|
+
when "play"
|
|
3327
|
+
play
|
|
3328
|
+
when "pause"
|
|
3329
|
+
pause
|
|
3330
|
+
when "load"
|
|
3331
|
+
load
|
|
3332
|
+
when "canPlayType"
|
|
3333
|
+
can_play_type(args[0])
|
|
3334
|
+
else
|
|
3335
|
+
super
|
|
3336
|
+
end
|
|
3337
|
+
end
|
|
3338
|
+
end
|
|
3339
|
+
|
|
3340
|
+
class HTMLAudioElement < HTMLMediaElement
|
|
3341
|
+
end
|
|
3342
|
+
|
|
3343
|
+
class HTMLVideoElement < HTMLMediaElement
|
|
3344
|
+
def poster
|
|
3345
|
+
reflected_string("poster")
|
|
3346
|
+
end
|
|
3347
|
+
|
|
3348
|
+
def poster=(v)
|
|
3349
|
+
set_reflected_string("poster", v)
|
|
3350
|
+
end
|
|
3351
|
+
|
|
3352
|
+
def width
|
|
3353
|
+
@__node__["width"].to_s.to_i
|
|
3354
|
+
end
|
|
3355
|
+
|
|
3356
|
+
def width=(v)
|
|
3357
|
+
set_reflected_string("width", v.to_s)
|
|
3358
|
+
end
|
|
3359
|
+
|
|
3360
|
+
def height
|
|
3361
|
+
@__node__["height"].to_s.to_i
|
|
3362
|
+
end
|
|
3363
|
+
|
|
3364
|
+
def height=(v)
|
|
3365
|
+
set_reflected_string("height", v.to_s)
|
|
3366
|
+
end
|
|
3367
|
+
|
|
3368
|
+
def video_width
|
|
3369
|
+
width
|
|
3370
|
+
end
|
|
3371
|
+
|
|
3372
|
+
def video_height
|
|
3373
|
+
height
|
|
3374
|
+
end
|
|
3375
|
+
|
|
3376
|
+
def plays_inline
|
|
3377
|
+
reflected_boolean("playsinline")
|
|
3378
|
+
end
|
|
3379
|
+
|
|
3380
|
+
def plays_inline=(v)
|
|
3381
|
+
set_reflected_boolean("playsinline", v)
|
|
3382
|
+
end
|
|
3383
|
+
|
|
3384
|
+
def __js_get__(key)
|
|
3385
|
+
case key
|
|
3386
|
+
when "poster"
|
|
3387
|
+
poster
|
|
3388
|
+
when "width"
|
|
3389
|
+
width
|
|
3390
|
+
when "height"
|
|
3391
|
+
height
|
|
3392
|
+
when "videoWidth"
|
|
3393
|
+
video_width
|
|
3394
|
+
when "videoHeight"
|
|
3395
|
+
video_height
|
|
3396
|
+
when "playsInline"
|
|
3397
|
+
plays_inline
|
|
3398
|
+
else
|
|
3399
|
+
super
|
|
3400
|
+
end
|
|
3401
|
+
end
|
|
3402
|
+
|
|
3403
|
+
def __js_set__(key, value)
|
|
3404
|
+
case key
|
|
3405
|
+
when "poster"
|
|
3406
|
+
self.poster = value
|
|
3407
|
+
when "width"
|
|
3408
|
+
self.width = value
|
|
3409
|
+
when "height"
|
|
3410
|
+
self.height = value
|
|
3411
|
+
when "playsInline"
|
|
3412
|
+
self.plays_inline = value
|
|
3413
|
+
else
|
|
3414
|
+
super
|
|
3415
|
+
end
|
|
3416
|
+
end
|
|
3417
|
+
end
|
|
3418
|
+
|
|
3419
|
+
class HTMLSourceElement < HTMLElement
|
|
3420
|
+
def src
|
|
3421
|
+
reflected_string("src")
|
|
3422
|
+
end
|
|
3423
|
+
|
|
3424
|
+
def src=(v)
|
|
3425
|
+
set_reflected_string("src", v)
|
|
3426
|
+
end
|
|
3427
|
+
|
|
3428
|
+
def type
|
|
3429
|
+
reflected_string("type")
|
|
3430
|
+
end
|
|
3431
|
+
|
|
3432
|
+
def type=(v)
|
|
3433
|
+
set_reflected_string("type", v)
|
|
3434
|
+
end
|
|
3435
|
+
|
|
3436
|
+
def media
|
|
3437
|
+
reflected_string("media")
|
|
3438
|
+
end
|
|
3439
|
+
|
|
3440
|
+
def media=(v)
|
|
3441
|
+
set_reflected_string("media", v)
|
|
3442
|
+
end
|
|
3443
|
+
|
|
3444
|
+
def srcset
|
|
3445
|
+
reflected_string("srcset")
|
|
3446
|
+
end
|
|
3447
|
+
|
|
3448
|
+
def srcset=(v)
|
|
3449
|
+
set_reflected_string("srcset", v)
|
|
3450
|
+
end
|
|
3451
|
+
|
|
3452
|
+
def sizes
|
|
3453
|
+
reflected_string("sizes")
|
|
3454
|
+
end
|
|
3455
|
+
|
|
3456
|
+
def sizes=(v)
|
|
3457
|
+
set_reflected_string("sizes", v)
|
|
3458
|
+
end
|
|
3459
|
+
|
|
3460
|
+
def width
|
|
3461
|
+
@__node__["width"].to_s.to_i
|
|
3462
|
+
end
|
|
3463
|
+
|
|
3464
|
+
def width=(v)
|
|
3465
|
+
set_reflected_string("width", v.to_s)
|
|
3466
|
+
end
|
|
3467
|
+
|
|
3468
|
+
def height
|
|
3469
|
+
@__node__["height"].to_s.to_i
|
|
3470
|
+
end
|
|
3471
|
+
|
|
3472
|
+
def height=(v)
|
|
3473
|
+
set_reflected_string("height", v.to_s)
|
|
3474
|
+
end
|
|
3475
|
+
|
|
3476
|
+
def __js_get__(key)
|
|
3477
|
+
case key
|
|
3478
|
+
when "src"
|
|
3479
|
+
src
|
|
3480
|
+
when "type"
|
|
3481
|
+
type
|
|
3482
|
+
when "media"
|
|
3483
|
+
media
|
|
3484
|
+
when "srcset"
|
|
3485
|
+
srcset
|
|
3486
|
+
when "sizes"
|
|
3487
|
+
sizes
|
|
3488
|
+
when "width"
|
|
3489
|
+
width
|
|
3490
|
+
when "height"
|
|
3491
|
+
height
|
|
3492
|
+
else
|
|
3493
|
+
super
|
|
3494
|
+
end
|
|
3495
|
+
end
|
|
3496
|
+
|
|
3497
|
+
def __js_set__(key, value)
|
|
3498
|
+
case key
|
|
3499
|
+
when "src", "type", "media", "srcset", "sizes"
|
|
3500
|
+
set_reflected_string(key, value)
|
|
3501
|
+
when "width"
|
|
3502
|
+
self.width = value
|
|
3503
|
+
when "height"
|
|
3504
|
+
self.height = value
|
|
3505
|
+
else
|
|
3506
|
+
super
|
|
3507
|
+
end
|
|
3508
|
+
end
|
|
3509
|
+
end
|
|
3510
|
+
|
|
3511
|
+
class HTMLTrackElement < HTMLElement
|
|
3512
|
+
NONE = 0
|
|
3513
|
+
LOADING = 1
|
|
3514
|
+
LOADED = 2
|
|
3515
|
+
ERROR = 3
|
|
3516
|
+
|
|
3517
|
+
def kind
|
|
3518
|
+
reflected_string("kind")
|
|
3519
|
+
end
|
|
3520
|
+
|
|
3521
|
+
def kind=(v)
|
|
3522
|
+
set_reflected_string("kind", v)
|
|
3523
|
+
end
|
|
3524
|
+
|
|
3525
|
+
def src
|
|
3526
|
+
reflected_string("src")
|
|
3527
|
+
end
|
|
3528
|
+
|
|
3529
|
+
def src=(v)
|
|
3530
|
+
set_reflected_string("src", v)
|
|
3531
|
+
end
|
|
3532
|
+
|
|
3533
|
+
def srclang
|
|
3534
|
+
reflected_string("srclang")
|
|
3535
|
+
end
|
|
3536
|
+
|
|
3537
|
+
def srclang=(v)
|
|
3538
|
+
set_reflected_string("srclang", v)
|
|
3539
|
+
end
|
|
3540
|
+
|
|
3541
|
+
def label
|
|
3542
|
+
reflected_string("label")
|
|
3543
|
+
end
|
|
3544
|
+
|
|
3545
|
+
def label=(v)
|
|
3546
|
+
set_reflected_string("label", v)
|
|
3547
|
+
end
|
|
3548
|
+
|
|
3549
|
+
def default_
|
|
3550
|
+
reflected_boolean("default")
|
|
3551
|
+
end
|
|
3552
|
+
|
|
3553
|
+
def default_=(v)
|
|
3554
|
+
set_reflected_boolean("default", v)
|
|
3555
|
+
end
|
|
3556
|
+
|
|
3557
|
+
def ready_state
|
|
3558
|
+
NONE
|
|
3559
|
+
end
|
|
3560
|
+
|
|
3561
|
+
def __js_get__(key)
|
|
3562
|
+
case key
|
|
3563
|
+
when "kind"
|
|
3564
|
+
kind
|
|
3565
|
+
when "src"
|
|
3566
|
+
src
|
|
3567
|
+
when "srclang"
|
|
3568
|
+
srclang
|
|
3569
|
+
when "label"
|
|
3570
|
+
label
|
|
3571
|
+
when "default"
|
|
3572
|
+
default_
|
|
3573
|
+
when "readyState"
|
|
3574
|
+
ready_state
|
|
3575
|
+
else
|
|
3576
|
+
super
|
|
3577
|
+
end
|
|
3578
|
+
end
|
|
3579
|
+
|
|
3580
|
+
def __js_set__(key, value)
|
|
3581
|
+
case key
|
|
3582
|
+
when "kind", "src", "srclang", "label"
|
|
3583
|
+
set_reflected_string(key, value)
|
|
3584
|
+
when "default"
|
|
3585
|
+
self.default_ = value
|
|
3586
|
+
else
|
|
3587
|
+
super
|
|
3588
|
+
end
|
|
3589
|
+
end
|
|
3590
|
+
end
|
|
3591
|
+
|
|
3592
|
+
class HTMLIFrameElement < HTMLElement
|
|
3593
|
+
def src
|
|
3594
|
+
reflected_string("src")
|
|
3595
|
+
end
|
|
3596
|
+
|
|
3597
|
+
def src=(v)
|
|
3598
|
+
set_reflected_string("src", v)
|
|
3599
|
+
end
|
|
3600
|
+
|
|
3601
|
+
def srcdoc
|
|
3602
|
+
reflected_string("srcdoc")
|
|
3603
|
+
end
|
|
3604
|
+
|
|
3605
|
+
def srcdoc=(v)
|
|
3606
|
+
set_reflected_string("srcdoc", v)
|
|
3607
|
+
end
|
|
3608
|
+
|
|
3609
|
+
def name
|
|
3610
|
+
reflected_string("name")
|
|
3611
|
+
end
|
|
3612
|
+
|
|
3613
|
+
def name=(v)
|
|
3614
|
+
set_reflected_string("name", v)
|
|
3615
|
+
end
|
|
3616
|
+
|
|
3617
|
+
def sandbox
|
|
3618
|
+
reflected_string("sandbox")
|
|
3619
|
+
end
|
|
3620
|
+
|
|
3621
|
+
def sandbox=(v)
|
|
3622
|
+
set_reflected_string("sandbox", v)
|
|
3623
|
+
end
|
|
3624
|
+
|
|
3625
|
+
def allow
|
|
3626
|
+
reflected_string("allow")
|
|
3627
|
+
end
|
|
3628
|
+
|
|
3629
|
+
def allow=(v)
|
|
3630
|
+
set_reflected_string("allow", v)
|
|
3631
|
+
end
|
|
3632
|
+
|
|
3633
|
+
def allow_fullscreen
|
|
3634
|
+
reflected_boolean("allowfullscreen")
|
|
3635
|
+
end
|
|
3636
|
+
|
|
3637
|
+
def allow_fullscreen=(v)
|
|
3638
|
+
set_reflected_boolean("allowfullscreen", v)
|
|
3639
|
+
end
|
|
3640
|
+
|
|
3641
|
+
def referrer_policy
|
|
3642
|
+
reflected_string("referrerpolicy")
|
|
3643
|
+
end
|
|
3644
|
+
|
|
3645
|
+
def referrer_policy=(v)
|
|
3646
|
+
set_reflected_string("referrerpolicy", v)
|
|
3647
|
+
end
|
|
3648
|
+
|
|
3649
|
+
def loading
|
|
3650
|
+
reflected_string("loading")
|
|
3651
|
+
end
|
|
3652
|
+
|
|
3653
|
+
def loading=(v)
|
|
3654
|
+
set_reflected_string("loading", v)
|
|
3655
|
+
end
|
|
3656
|
+
|
|
3657
|
+
def width
|
|
3658
|
+
@__node__["width"].to_s
|
|
3659
|
+
end
|
|
3660
|
+
|
|
3661
|
+
def width=(v)
|
|
3662
|
+
set_reflected_string("width", v.to_s)
|
|
3663
|
+
end
|
|
3664
|
+
|
|
3665
|
+
def height
|
|
3666
|
+
@__node__["height"].to_s
|
|
3667
|
+
end
|
|
3668
|
+
|
|
3669
|
+
def height=(v)
|
|
3670
|
+
set_reflected_string("height", v.to_s)
|
|
3671
|
+
end
|
|
3672
|
+
|
|
3673
|
+
def content_document
|
|
3674
|
+
nil
|
|
3675
|
+
end
|
|
3676
|
+
|
|
3677
|
+
def content_window
|
|
3678
|
+
nil
|
|
3679
|
+
end
|
|
3680
|
+
|
|
3681
|
+
def __js_get__(key)
|
|
3682
|
+
case key
|
|
3683
|
+
when "src"
|
|
3684
|
+
src
|
|
3685
|
+
when "srcdoc"
|
|
3686
|
+
srcdoc
|
|
3687
|
+
when "name"
|
|
3688
|
+
name
|
|
3689
|
+
when "sandbox"
|
|
3690
|
+
sandbox
|
|
3691
|
+
when "allow"
|
|
3692
|
+
allow
|
|
3693
|
+
when "allowFullscreen"
|
|
3694
|
+
allow_fullscreen
|
|
3695
|
+
when "referrerPolicy"
|
|
3696
|
+
referrer_policy
|
|
3697
|
+
when "loading"
|
|
3698
|
+
loading
|
|
3699
|
+
when "width"
|
|
3700
|
+
width
|
|
3701
|
+
when "height"
|
|
3702
|
+
height
|
|
3703
|
+
when "contentDocument"
|
|
3704
|
+
content_document
|
|
3705
|
+
when "contentWindow"
|
|
3706
|
+
content_window
|
|
3707
|
+
else
|
|
3708
|
+
super
|
|
3709
|
+
end
|
|
3710
|
+
end
|
|
3711
|
+
|
|
3712
|
+
def __js_set__(key, value)
|
|
3713
|
+
case key
|
|
3714
|
+
when "src", "srcdoc", "name", "sandbox", "allow", "loading"
|
|
3715
|
+
set_reflected_string(key, value)
|
|
3716
|
+
when "allowFullscreen"
|
|
3717
|
+
self.allow_fullscreen = value
|
|
3718
|
+
when "referrerPolicy"
|
|
3719
|
+
set_reflected_string("referrerpolicy", value)
|
|
3720
|
+
when "width"
|
|
3721
|
+
self.width = value
|
|
3722
|
+
when "height"
|
|
3723
|
+
self.height = value
|
|
3724
|
+
else
|
|
3725
|
+
super
|
|
3726
|
+
end
|
|
3727
|
+
end
|
|
3728
|
+
end
|
|
3729
|
+
|
|
3730
|
+
class HTMLPictureElement < HTMLElement
|
|
3731
|
+
end
|
|
3732
|
+
|
|
3733
|
+
class HTMLOListElement < HTMLElement
|
|
3734
|
+
def start
|
|
3735
|
+
(@__node__["start"] || "1").to_i
|
|
3736
|
+
end
|
|
3737
|
+
|
|
3738
|
+
def start=(v)
|
|
3739
|
+
set_reflected_string("start", v.to_s)
|
|
3740
|
+
end
|
|
3741
|
+
|
|
3742
|
+
def reversed
|
|
3743
|
+
reflected_boolean("reversed")
|
|
3744
|
+
end
|
|
3745
|
+
|
|
3746
|
+
def reversed=(v)
|
|
3747
|
+
set_reflected_boolean("reversed", v)
|
|
3748
|
+
end
|
|
3749
|
+
|
|
3750
|
+
def type
|
|
3751
|
+
reflected_string("type")
|
|
3752
|
+
end
|
|
3753
|
+
|
|
3754
|
+
def type=(v)
|
|
3755
|
+
set_reflected_string("type", v)
|
|
3756
|
+
end
|
|
3757
|
+
|
|
3758
|
+
def __js_get__(key)
|
|
3759
|
+
case key
|
|
3760
|
+
when "start"
|
|
3761
|
+
start
|
|
3762
|
+
when "reversed"
|
|
3763
|
+
reversed
|
|
3764
|
+
when "type"
|
|
3765
|
+
type
|
|
3766
|
+
else
|
|
3767
|
+
super
|
|
3768
|
+
end
|
|
3769
|
+
end
|
|
3770
|
+
|
|
3771
|
+
def __js_set__(key, value)
|
|
3772
|
+
case key
|
|
3773
|
+
when "start"
|
|
3774
|
+
self.start = value
|
|
3775
|
+
when "reversed"
|
|
3776
|
+
self.reversed = value
|
|
3777
|
+
when "type"
|
|
3778
|
+
self.type = value
|
|
3779
|
+
else
|
|
3780
|
+
super
|
|
3781
|
+
end
|
|
3782
|
+
end
|
|
3783
|
+
end
|
|
3784
|
+
|
|
3785
|
+
class HTMLUListElement < HTMLElement
|
|
3786
|
+
end
|
|
3787
|
+
|
|
3788
|
+
class HTMLLIElement < HTMLElement
|
|
3789
|
+
def value
|
|
3790
|
+
@__node__["value"]&.to_i
|
|
3791
|
+
end
|
|
3792
|
+
|
|
3793
|
+
def value=(v)
|
|
3794
|
+
set_reflected_string("value", v.to_s)
|
|
3795
|
+
end
|
|
3796
|
+
|
|
3797
|
+
def __js_get__(key)
|
|
3798
|
+
key == "value" ? value : super
|
|
3799
|
+
end
|
|
3800
|
+
|
|
3801
|
+
def __js_set__(key, value)
|
|
3802
|
+
key == "value" ? (self.value = value) : super
|
|
3803
|
+
end
|
|
3804
|
+
end
|
|
3805
|
+
|
|
3806
|
+
class HTMLTimeElement < HTMLElement
|
|
3807
|
+
def date_time
|
|
3808
|
+
reflected_string("datetime")
|
|
3809
|
+
end
|
|
3810
|
+
|
|
3811
|
+
def date_time=(v)
|
|
3812
|
+
set_reflected_string("datetime", v)
|
|
3813
|
+
end
|
|
3814
|
+
|
|
3815
|
+
def __js_get__(key)
|
|
3816
|
+
key == "dateTime" ? date_time : super
|
|
3817
|
+
end
|
|
3818
|
+
|
|
3819
|
+
def __js_set__(key, value)
|
|
3820
|
+
key == "dateTime" ? (self.date_time = value) : super
|
|
3821
|
+
end
|
|
3822
|
+
end
|
|
3823
|
+
|
|
3824
|
+
class HTMLDataElement < HTMLElement
|
|
3825
|
+
def value
|
|
3826
|
+
reflected_string("value")
|
|
3827
|
+
end
|
|
3828
|
+
|
|
3829
|
+
def value=(v)
|
|
3830
|
+
set_reflected_string("value", v)
|
|
3831
|
+
end
|
|
3832
|
+
|
|
3833
|
+
def __js_get__(key)
|
|
3834
|
+
key == "value" ? value : super
|
|
3835
|
+
end
|
|
3836
|
+
|
|
3837
|
+
def __js_set__(key, value)
|
|
3838
|
+
key == "value" ? (self.value = value) : super
|
|
3839
|
+
end
|
|
3840
|
+
end
|
|
3841
|
+
|
|
3842
|
+
class HTMLAreaElement < HTMLElement
|
|
3843
|
+
def alt
|
|
3844
|
+
reflected_string("alt")
|
|
3845
|
+
end
|
|
3846
|
+
|
|
3847
|
+
def alt=(v)
|
|
3848
|
+
set_reflected_string("alt", v)
|
|
3849
|
+
end
|
|
3850
|
+
|
|
3851
|
+
def coords
|
|
3852
|
+
reflected_string("coords")
|
|
3853
|
+
end
|
|
3854
|
+
|
|
3855
|
+
def coords=(v)
|
|
3856
|
+
set_reflected_string("coords", v)
|
|
3857
|
+
end
|
|
3858
|
+
|
|
3859
|
+
def shape
|
|
3860
|
+
reflected_string("shape")
|
|
3861
|
+
end
|
|
3862
|
+
|
|
3863
|
+
def shape=(v)
|
|
3864
|
+
set_reflected_string("shape", v)
|
|
3865
|
+
end
|
|
3866
|
+
|
|
3867
|
+
def href
|
|
3868
|
+
reflected_string("href")
|
|
3869
|
+
end
|
|
3870
|
+
|
|
3871
|
+
def href=(v)
|
|
3872
|
+
set_reflected_string("href", v)
|
|
3873
|
+
end
|
|
3874
|
+
|
|
3875
|
+
def target
|
|
3876
|
+
reflected_string("target")
|
|
3877
|
+
end
|
|
3878
|
+
|
|
3879
|
+
def target=(v)
|
|
3880
|
+
set_reflected_string("target", v)
|
|
3881
|
+
end
|
|
3882
|
+
|
|
3883
|
+
def rel
|
|
3884
|
+
reflected_string("rel")
|
|
3885
|
+
end
|
|
3886
|
+
|
|
3887
|
+
def rel=(v)
|
|
3888
|
+
set_reflected_string("rel", v)
|
|
3889
|
+
end
|
|
3890
|
+
|
|
3891
|
+
def __js_get__(key)
|
|
3892
|
+
case key
|
|
3893
|
+
when "alt"
|
|
3894
|
+
alt
|
|
3895
|
+
when "coords"
|
|
3896
|
+
coords
|
|
3897
|
+
when "shape"
|
|
3898
|
+
shape
|
|
3899
|
+
when "href"
|
|
3900
|
+
href
|
|
3901
|
+
when "target"
|
|
3902
|
+
target
|
|
3903
|
+
when "rel"
|
|
3904
|
+
rel
|
|
3905
|
+
else
|
|
3906
|
+
super
|
|
3907
|
+
end
|
|
3908
|
+
end
|
|
3909
|
+
|
|
3910
|
+
def __js_set__(key, value)
|
|
3911
|
+
case key
|
|
3912
|
+
when "alt", "coords", "shape", "href", "target", "rel"
|
|
3913
|
+
set_reflected_string(key, value)
|
|
3914
|
+
else
|
|
3915
|
+
super
|
|
3916
|
+
end
|
|
3917
|
+
end
|
|
3918
|
+
end
|
|
3919
|
+
|
|
3920
|
+
class HTMLMapElement < HTMLElement
|
|
3921
|
+
def name
|
|
3922
|
+
reflected_string("name")
|
|
3923
|
+
end
|
|
3924
|
+
|
|
3925
|
+
def name=(v)
|
|
3926
|
+
set_reflected_string("name", v)
|
|
3927
|
+
end
|
|
3928
|
+
|
|
3929
|
+
def areas
|
|
3930
|
+
HTMLCollection.new do
|
|
3931
|
+
@__node__.css("area").map { |n| @document.wrap_node(n) }.compact
|
|
3932
|
+
end
|
|
3933
|
+
end
|
|
3934
|
+
|
|
3935
|
+
def __js_get__(key)
|
|
3936
|
+
case key
|
|
3937
|
+
when "name"
|
|
3938
|
+
name
|
|
3939
|
+
when "areas"
|
|
3940
|
+
areas
|
|
3941
|
+
else
|
|
3942
|
+
super
|
|
3943
|
+
end
|
|
3944
|
+
end
|
|
3945
|
+
|
|
3946
|
+
def __js_set__(key, value)
|
|
3947
|
+
key == "name" ? (self.name = value) : super
|
|
3948
|
+
end
|
|
3949
|
+
end
|
|
3950
|
+
|
|
3951
|
+
class HTMLObjectElement < HTMLElement
|
|
3952
|
+
def data
|
|
3953
|
+
reflected_string("data")
|
|
3954
|
+
end
|
|
3955
|
+
|
|
3956
|
+
def data=(v)
|
|
3957
|
+
set_reflected_string("data", v)
|
|
3958
|
+
end
|
|
3959
|
+
|
|
3960
|
+
def type
|
|
3961
|
+
reflected_string("type")
|
|
3962
|
+
end
|
|
3963
|
+
|
|
3964
|
+
def type=(v)
|
|
3965
|
+
set_reflected_string("type", v)
|
|
3966
|
+
end
|
|
3967
|
+
|
|
3968
|
+
def name
|
|
3969
|
+
reflected_string("name")
|
|
3970
|
+
end
|
|
3971
|
+
|
|
3972
|
+
def name=(v)
|
|
3973
|
+
set_reflected_string("name", v)
|
|
3974
|
+
end
|
|
3975
|
+
|
|
3976
|
+
def use_map
|
|
3977
|
+
reflected_string("usemap")
|
|
3978
|
+
end
|
|
3979
|
+
|
|
3980
|
+
def use_map=(v)
|
|
3981
|
+
set_reflected_string("usemap", v)
|
|
3982
|
+
end
|
|
3983
|
+
|
|
3984
|
+
def width
|
|
3985
|
+
@__node__["width"].to_s
|
|
3986
|
+
end
|
|
3987
|
+
|
|
3988
|
+
def width=(v)
|
|
3989
|
+
set_reflected_string("width", v.to_s)
|
|
3990
|
+
end
|
|
3991
|
+
|
|
3992
|
+
def height
|
|
3993
|
+
@__node__["height"].to_s
|
|
3994
|
+
end
|
|
3995
|
+
|
|
3996
|
+
def height=(v)
|
|
3997
|
+
set_reflected_string("height", v.to_s)
|
|
3998
|
+
end
|
|
3999
|
+
|
|
4000
|
+
def content_document
|
|
4001
|
+
nil
|
|
4002
|
+
end
|
|
4003
|
+
|
|
4004
|
+
def content_window
|
|
4005
|
+
nil
|
|
4006
|
+
end
|
|
4007
|
+
|
|
4008
|
+
def __js_get__(key)
|
|
4009
|
+
case key
|
|
4010
|
+
when "data"
|
|
4011
|
+
data
|
|
4012
|
+
when "type"
|
|
4013
|
+
type
|
|
4014
|
+
when "name"
|
|
4015
|
+
name
|
|
4016
|
+
when "useMap"
|
|
4017
|
+
use_map
|
|
4018
|
+
when "width"
|
|
4019
|
+
width
|
|
4020
|
+
when "height"
|
|
4021
|
+
height
|
|
4022
|
+
when "contentDocument"
|
|
4023
|
+
content_document
|
|
4024
|
+
when "contentWindow"
|
|
4025
|
+
content_window
|
|
4026
|
+
else
|
|
4027
|
+
super
|
|
4028
|
+
end
|
|
4029
|
+
end
|
|
4030
|
+
|
|
4031
|
+
def __js_set__(key, value)
|
|
4032
|
+
case key
|
|
4033
|
+
when "data", "type", "name"
|
|
4034
|
+
set_reflected_string(key, value)
|
|
4035
|
+
when "useMap"
|
|
4036
|
+
set_reflected_string("usemap", value)
|
|
4037
|
+
when "width"
|
|
4038
|
+
self.width = value
|
|
4039
|
+
when "height"
|
|
4040
|
+
self.height = value
|
|
4041
|
+
else
|
|
4042
|
+
super
|
|
4043
|
+
end
|
|
4044
|
+
end
|
|
4045
|
+
end
|
|
4046
|
+
|
|
4047
|
+
class HTMLEmbedElement < HTMLElement
|
|
4048
|
+
def src
|
|
4049
|
+
reflected_string("src")
|
|
4050
|
+
end
|
|
4051
|
+
|
|
4052
|
+
def src=(v)
|
|
4053
|
+
set_reflected_string("src", v)
|
|
4054
|
+
end
|
|
4055
|
+
|
|
4056
|
+
def type
|
|
4057
|
+
reflected_string("type")
|
|
4058
|
+
end
|
|
4059
|
+
|
|
4060
|
+
def type=(v)
|
|
4061
|
+
set_reflected_string("type", v)
|
|
4062
|
+
end
|
|
4063
|
+
|
|
4064
|
+
def width
|
|
4065
|
+
@__node__["width"].to_s
|
|
4066
|
+
end
|
|
4067
|
+
|
|
4068
|
+
def width=(v)
|
|
4069
|
+
set_reflected_string("width", v.to_s)
|
|
4070
|
+
end
|
|
4071
|
+
|
|
4072
|
+
def height
|
|
4073
|
+
@__node__["height"].to_s
|
|
4074
|
+
end
|
|
4075
|
+
|
|
4076
|
+
def height=(v)
|
|
4077
|
+
set_reflected_string("height", v.to_s)
|
|
4078
|
+
end
|
|
4079
|
+
|
|
4080
|
+
def __js_get__(key)
|
|
4081
|
+
case key
|
|
4082
|
+
when "src"
|
|
4083
|
+
src
|
|
4084
|
+
when "type"
|
|
4085
|
+
type
|
|
4086
|
+
when "width"
|
|
4087
|
+
width
|
|
4088
|
+
when "height"
|
|
4089
|
+
height
|
|
4090
|
+
else
|
|
4091
|
+
super
|
|
4092
|
+
end
|
|
4093
|
+
end
|
|
4094
|
+
|
|
4095
|
+
def __js_set__(key, value)
|
|
4096
|
+
case key
|
|
4097
|
+
when "src", "type"
|
|
4098
|
+
set_reflected_string(key, value)
|
|
4099
|
+
when "width"
|
|
4100
|
+
self.width = value
|
|
4101
|
+
when "height"
|
|
4102
|
+
self.height = value
|
|
4103
|
+
else
|
|
4104
|
+
super
|
|
4105
|
+
end
|
|
4106
|
+
end
|
|
4107
|
+
end
|
|
4108
|
+
|
|
4109
|
+
class HTMLBaseElement < HTMLElement
|
|
4110
|
+
def href
|
|
4111
|
+
reflected_string("href")
|
|
4112
|
+
end
|
|
4113
|
+
|
|
4114
|
+
def href=(v)
|
|
4115
|
+
set_reflected_string("href", v)
|
|
4116
|
+
end
|
|
4117
|
+
|
|
4118
|
+
def target
|
|
4119
|
+
reflected_string("target")
|
|
4120
|
+
end
|
|
4121
|
+
|
|
4122
|
+
def target=(v)
|
|
4123
|
+
set_reflected_string("target", v)
|
|
4124
|
+
end
|
|
4125
|
+
|
|
4126
|
+
def __js_get__(key)
|
|
4127
|
+
case key
|
|
4128
|
+
when "href"
|
|
4129
|
+
href
|
|
4130
|
+
when "target"
|
|
4131
|
+
target
|
|
4132
|
+
else
|
|
4133
|
+
super
|
|
4134
|
+
end
|
|
4135
|
+
end
|
|
4136
|
+
|
|
4137
|
+
def __js_set__(key, value)
|
|
4138
|
+
case key
|
|
4139
|
+
when "href", "target"
|
|
4140
|
+
set_reflected_string(key, value)
|
|
4141
|
+
else
|
|
4142
|
+
super
|
|
4143
|
+
end
|
|
4144
|
+
end
|
|
4145
|
+
end
|
|
4146
|
+
|
|
4147
|
+
class HTMLMetaElement < HTMLElement
|
|
4148
|
+
def name
|
|
4149
|
+
reflected_string("name")
|
|
4150
|
+
end
|
|
4151
|
+
|
|
4152
|
+
def name=(v)
|
|
4153
|
+
set_reflected_string("name", v)
|
|
4154
|
+
end
|
|
4155
|
+
|
|
4156
|
+
def content
|
|
4157
|
+
reflected_string("content")
|
|
4158
|
+
end
|
|
4159
|
+
|
|
4160
|
+
def content=(v)
|
|
4161
|
+
set_reflected_string("content", v)
|
|
4162
|
+
end
|
|
4163
|
+
|
|
4164
|
+
def charset
|
|
4165
|
+
reflected_string("charset")
|
|
4166
|
+
end
|
|
4167
|
+
|
|
4168
|
+
def charset=(v)
|
|
4169
|
+
set_reflected_string("charset", v)
|
|
4170
|
+
end
|
|
4171
|
+
|
|
4172
|
+
def http_equiv
|
|
4173
|
+
reflected_string("http-equiv")
|
|
4174
|
+
end
|
|
4175
|
+
|
|
4176
|
+
def http_equiv=(v)
|
|
4177
|
+
set_reflected_string("http-equiv", v)
|
|
4178
|
+
end
|
|
4179
|
+
|
|
4180
|
+
def __js_get__(key)
|
|
4181
|
+
case key
|
|
4182
|
+
when "name"
|
|
4183
|
+
name
|
|
4184
|
+
when "content"
|
|
4185
|
+
content
|
|
4186
|
+
when "charset"
|
|
4187
|
+
charset
|
|
4188
|
+
when "httpEquiv"
|
|
4189
|
+
http_equiv
|
|
4190
|
+
else
|
|
4191
|
+
super
|
|
4192
|
+
end
|
|
4193
|
+
end
|
|
4194
|
+
|
|
4195
|
+
def __js_set__(key, value)
|
|
4196
|
+
case key
|
|
4197
|
+
when "name", "content", "charset"
|
|
4198
|
+
set_reflected_string(key, value)
|
|
4199
|
+
when "httpEquiv"
|
|
4200
|
+
set_reflected_string("http-equiv", value)
|
|
4201
|
+
else
|
|
4202
|
+
super
|
|
4203
|
+
end
|
|
4204
|
+
end
|
|
4205
|
+
end
|
|
4206
|
+
|
|
4207
|
+
class HTMLStyleElement < HTMLElement
|
|
4208
|
+
def type
|
|
4209
|
+
reflected_string("type")
|
|
4210
|
+
end
|
|
4211
|
+
|
|
4212
|
+
def type=(v)
|
|
4213
|
+
set_reflected_string("type", v)
|
|
4214
|
+
end
|
|
4215
|
+
|
|
4216
|
+
def media
|
|
4217
|
+
reflected_string("media")
|
|
4218
|
+
end
|
|
4219
|
+
|
|
4220
|
+
def media=(v)
|
|
4221
|
+
set_reflected_string("media", v)
|
|
4222
|
+
end
|
|
4223
|
+
|
|
4224
|
+
def disabled
|
|
4225
|
+
@__disabled == true
|
|
4226
|
+
end
|
|
4227
|
+
|
|
4228
|
+
def disabled=(v)
|
|
4229
|
+
@__disabled = !!v
|
|
4230
|
+
end
|
|
4231
|
+
|
|
4232
|
+
# `style.sheet` — always non-nil for `<style>` (in browsers the
|
|
4233
|
+
# text content is parsed; we hand back an empty sheet stub that
|
|
4234
|
+
# consumers can manipulate via insertRule/deleteRule).
|
|
4235
|
+
def sheet
|
|
4236
|
+
@__sheet ||= CSSStyleSheet.new(
|
|
4237
|
+
owner_node: self,
|
|
4238
|
+
media: media,
|
|
4239
|
+
title: @__node__["title"].to_s,
|
|
4240
|
+
type: (type.empty? ? "text/css" : type)
|
|
4241
|
+
)
|
|
4242
|
+
end
|
|
4243
|
+
|
|
4244
|
+
def __js_get__(key)
|
|
4245
|
+
case key
|
|
4246
|
+
when "type"
|
|
4247
|
+
type
|
|
4248
|
+
when "media"
|
|
4249
|
+
media
|
|
4250
|
+
when "disabled"
|
|
4251
|
+
disabled
|
|
4252
|
+
when "sheet"
|
|
4253
|
+
sheet
|
|
4254
|
+
else
|
|
4255
|
+
super
|
|
4256
|
+
end
|
|
4257
|
+
end
|
|
4258
|
+
|
|
4259
|
+
def __js_set__(key, value)
|
|
4260
|
+
case key
|
|
4261
|
+
when "type", "media"
|
|
4262
|
+
set_reflected_string(key, value)
|
|
4263
|
+
when "disabled"
|
|
4264
|
+
self.disabled = value
|
|
4265
|
+
else
|
|
4266
|
+
super
|
|
4267
|
+
end
|
|
4268
|
+
end
|
|
4269
|
+
end
|
|
4270
|
+
|
|
4271
|
+
class HTMLTitleElement < HTMLElement
|
|
4272
|
+
def text
|
|
4273
|
+
text_content
|
|
4274
|
+
end
|
|
4275
|
+
|
|
4276
|
+
def text=(v)
|
|
4277
|
+
self.text_content = v.to_s
|
|
4278
|
+
end
|
|
4279
|
+
|
|
4280
|
+
def __js_get__(key)
|
|
4281
|
+
key == "text" ? text : super
|
|
4282
|
+
end
|
|
4283
|
+
|
|
4284
|
+
def __js_set__(key, value)
|
|
4285
|
+
key == "text" ? (self.text = value) : super
|
|
4286
|
+
end
|
|
4287
|
+
end
|
|
4288
|
+
|
|
4289
|
+
class HTMLQuoteElement < HTMLElement
|
|
4290
|
+
def cite
|
|
4291
|
+
reflected_string("cite")
|
|
4292
|
+
end
|
|
4293
|
+
|
|
4294
|
+
def cite=(v)
|
|
4295
|
+
set_reflected_string("cite", v)
|
|
4296
|
+
end
|
|
4297
|
+
|
|
4298
|
+
def __js_get__(key)
|
|
4299
|
+
key == "cite" ? cite : super
|
|
4300
|
+
end
|
|
4301
|
+
|
|
4302
|
+
def __js_set__(key, value)
|
|
4303
|
+
key == "cite" ? (self.cite = value) : super
|
|
4304
|
+
end
|
|
4305
|
+
end
|
|
4306
|
+
|
|
4307
|
+
class HTMLModElement < HTMLElement
|
|
4308
|
+
def cite
|
|
4309
|
+
reflected_string("cite")
|
|
4310
|
+
end
|
|
4311
|
+
|
|
4312
|
+
def cite=(v)
|
|
4313
|
+
set_reflected_string("cite", v)
|
|
4314
|
+
end
|
|
4315
|
+
|
|
4316
|
+
def date_time
|
|
4317
|
+
reflected_string("datetime")
|
|
4318
|
+
end
|
|
4319
|
+
|
|
4320
|
+
def date_time=(v)
|
|
4321
|
+
set_reflected_string("datetime", v)
|
|
4322
|
+
end
|
|
4323
|
+
|
|
4324
|
+
def __js_get__(key)
|
|
4325
|
+
case key
|
|
4326
|
+
when "cite"
|
|
4327
|
+
cite
|
|
4328
|
+
when "dateTime"
|
|
4329
|
+
date_time
|
|
4330
|
+
else
|
|
4331
|
+
super
|
|
4332
|
+
end
|
|
4333
|
+
end
|
|
4334
|
+
|
|
4335
|
+
def __js_set__(key, value)
|
|
4336
|
+
case key
|
|
4337
|
+
when "cite"
|
|
4338
|
+
self.cite = value
|
|
4339
|
+
when "dateTime"
|
|
4340
|
+
self.date_time = value
|
|
4341
|
+
else
|
|
4342
|
+
super
|
|
4343
|
+
end
|
|
4344
|
+
end
|
|
4345
|
+
end
|
|
4346
|
+
|
|
4347
|
+
# Identity-only subclasses — useful for `instanceof` / `is_a?` checks
|
|
4348
|
+
# in consumer code, even though they don't add reflected IDL attrs
|
|
4349
|
+
# beyond what HTMLElement already exposes.
|
|
4350
|
+
class HTMLDivElement < HTMLElement
|
|
4351
|
+
end
|
|
4352
|
+
|
|
4353
|
+
class HTMLSpanElement < HTMLElement
|
|
4354
|
+
end
|
|
4355
|
+
|
|
4356
|
+
class HTMLParagraphElement < HTMLElement
|
|
4357
|
+
end
|
|
4358
|
+
|
|
4359
|
+
class HTMLHeadingElement < HTMLElement
|
|
4360
|
+
end
|
|
4361
|
+
|
|
4362
|
+
class HTMLBRElement < HTMLElement
|
|
4363
|
+
end
|
|
4364
|
+
|
|
4365
|
+
class HTMLHRElement < HTMLElement
|
|
4366
|
+
end
|
|
4367
|
+
|
|
4368
|
+
class HTMLPreElement < HTMLElement
|
|
4369
|
+
end
|
|
4370
|
+
|
|
4371
|
+
class HTMLBodyElement < HTMLElement
|
|
4372
|
+
end
|
|
4373
|
+
|
|
4374
|
+
class HTMLHeadElement < HTMLElement
|
|
4375
|
+
end
|
|
4376
|
+
|
|
4377
|
+
class HTMLHtmlElement < HTMLElement
|
|
4378
|
+
end
|
|
4379
|
+
|
|
4380
|
+
# Look up the subclass for a given HTML tag. Document#wrap_node
|
|
4381
|
+
# consults this map; defaults to plain Element.
|
|
4382
|
+
HTML_ELEMENT_CLASSES = {
|
|
4383
|
+
"a" => HTMLAnchorElement,
|
|
4384
|
+
"form" => HTMLFormElement,
|
|
4385
|
+
"input" => HTMLInputElement,
|
|
4386
|
+
"button" => HTMLButtonElement,
|
|
4387
|
+
"img" => HTMLImageElement,
|
|
4388
|
+
"script" => HTMLScriptElement,
|
|
4389
|
+
"link" => HTMLLinkElement,
|
|
4390
|
+
"select" => HTMLSelectElement,
|
|
4391
|
+
"option" => HTMLOptionElement,
|
|
4392
|
+
"optgroup" => HTMLOptGroupElement,
|
|
4393
|
+
"textarea" => HTMLTextAreaElement,
|
|
4394
|
+
"label" => HTMLLabelElement,
|
|
4395
|
+
"fieldset" => HTMLFieldsetElement,
|
|
4396
|
+
"output" => HTMLOutputElement,
|
|
4397
|
+
"legend" => HTMLLegendElement,
|
|
4398
|
+
"slot" => HTMLSlotElement,
|
|
4399
|
+
"table" => HTMLTableElement,
|
|
4400
|
+
"thead" => HTMLTableSectionElement,
|
|
4401
|
+
"tbody" => HTMLTableSectionElement,
|
|
4402
|
+
"tfoot" => HTMLTableSectionElement,
|
|
4403
|
+
"tr" => HTMLTableRowElement,
|
|
4404
|
+
"td" => HTMLTableCellElement,
|
|
4405
|
+
"th" => HTMLTableCellElement,
|
|
4406
|
+
"caption" => HTMLTableCaptionElement,
|
|
4407
|
+
"dialog" => HTMLDialogElement,
|
|
4408
|
+
"details" => HTMLDetailsElement,
|
|
4409
|
+
"meter" => HTMLMeterElement,
|
|
4410
|
+
"progress" => HTMLProgressElement,
|
|
4411
|
+
"template" => HTMLTemplateElement,
|
|
4412
|
+
"audio" => HTMLAudioElement,
|
|
4413
|
+
"video" => HTMLVideoElement,
|
|
4414
|
+
"source" => HTMLSourceElement,
|
|
4415
|
+
"track" => HTMLTrackElement,
|
|
4416
|
+
"iframe" => HTMLIFrameElement,
|
|
4417
|
+
"picture" => HTMLPictureElement,
|
|
4418
|
+
"ol" => HTMLOListElement,
|
|
4419
|
+
"ul" => HTMLUListElement,
|
|
4420
|
+
"li" => HTMLLIElement,
|
|
4421
|
+
"time" => HTMLTimeElement,
|
|
4422
|
+
"data" => HTMLDataElement,
|
|
4423
|
+
"area" => HTMLAreaElement,
|
|
4424
|
+
"map" => HTMLMapElement,
|
|
4425
|
+
"object" => HTMLObjectElement,
|
|
4426
|
+
"embed" => HTMLEmbedElement,
|
|
4427
|
+
"base" => HTMLBaseElement,
|
|
4428
|
+
"meta" => HTMLMetaElement,
|
|
4429
|
+
"style" => HTMLStyleElement,
|
|
4430
|
+
"title" => HTMLTitleElement,
|
|
4431
|
+
"q" => HTMLQuoteElement,
|
|
4432
|
+
"blockquote" => HTMLQuoteElement,
|
|
4433
|
+
"ins" => HTMLModElement,
|
|
4434
|
+
"del" => HTMLModElement,
|
|
4435
|
+
"div" => HTMLDivElement,
|
|
4436
|
+
"span" => HTMLSpanElement,
|
|
4437
|
+
"p" => HTMLParagraphElement,
|
|
4438
|
+
"h1" => HTMLHeadingElement,
|
|
4439
|
+
"h2" => HTMLHeadingElement,
|
|
4440
|
+
"h3" => HTMLHeadingElement,
|
|
4441
|
+
"h4" => HTMLHeadingElement,
|
|
4442
|
+
"h5" => HTMLHeadingElement,
|
|
4443
|
+
"h6" => HTMLHeadingElement,
|
|
4444
|
+
"br" => HTMLBRElement,
|
|
4445
|
+
"hr" => HTMLHRElement,
|
|
4446
|
+
"pre" => HTMLPreElement,
|
|
4447
|
+
"body" => HTMLBodyElement,
|
|
4448
|
+
"head" => HTMLHeadElement,
|
|
4449
|
+
"html" => HTMLHtmlElement
|
|
4450
|
+
}.freeze
|
|
4451
|
+
|
|
4452
|
+
def self.element_class_for(tag_name)
|
|
4453
|
+
HTML_ELEMENT_CLASSES[tag_name.to_s.downcase] || Element
|
|
4454
|
+
end
|
|
4455
|
+
end
|