dommy 0.9.0 → 0.10.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 +4 -4
- data/lib/dommy/attr.rb +46 -8
- data/lib/dommy/backend/makiri_adapter.rb +34 -0
- data/lib/dommy/backend.rb +29 -0
- data/lib/dommy/blob.rb +48 -6
- data/lib/dommy/browser.rb +239 -36
- data/lib/dommy/callable_invoker.rb +6 -2
- data/lib/dommy/document.rb +525 -57
- data/lib/dommy/element.rb +479 -239
- data/lib/dommy/event.rb +296 -15
- data/lib/dommy/fetch.rb +431 -25
- data/lib/dommy/history.rb +24 -3
- data/lib/dommy/html_collection.rb +145 -21
- data/lib/dommy/html_elements.rb +1675 -266
- data/lib/dommy/interaction/driver.rb +155 -14
- data/lib/dommy/interaction/event_synthesis.rb +115 -7
- data/lib/dommy/interaction/field_interactor.rb +60 -0
- data/lib/dommy/internal/child_node.rb +199 -0
- data/lib/dommy/internal/css/cascade.rb +44 -2
- data/lib/dommy/internal/global_functions.rb +23 -10
- data/lib/dommy/internal/mutation_coordinator.rb +27 -7
- data/lib/dommy/internal/node_wrapper_cache.rb +56 -14
- data/lib/dommy/internal/observer_manager.rb +6 -0
- data/lib/dommy/internal/observer_matcher.rb +6 -8
- data/lib/dommy/internal/parent_node.rb +37 -73
- data/lib/dommy/internal/selector_matcher.rb +89 -0
- data/lib/dommy/internal/selector_parser.rb +44 -7
- data/lib/dommy/js/custom_element_bridge.rb +13 -2
- data/lib/dommy/js/dom_interfaces.rb +19 -4
- data/lib/dommy/js/host_bridge.rb +21 -0
- data/lib/dommy/js/host_runtime.js +926 -64
- data/lib/dommy/js/script_boot.rb +80 -0
- data/lib/dommy/location.rb +76 -13
- data/lib/dommy/mutation_observer.rb +3 -4
- data/lib/dommy/navigation.rb +263 -0
- data/lib/dommy/node.rb +180 -27
- data/lib/dommy/range.rb +15 -3
- data/lib/dommy/scheduler.rb +16 -0
- data/lib/dommy/shadow_root.rb +33 -0
- data/lib/dommy/storage.rb +61 -10
- data/lib/dommy/tree_walker.rb +18 -36
- data/lib/dommy/url.rb +4 -2
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +45 -2
- data/lib/dommy/window.rb +126 -7
- data/lib/dommy/xml_http_request.rb +30 -4
- data/lib/dommy.rb +2 -0
- metadata +6 -10
|
@@ -25,18 +25,77 @@ module Dommy
|
|
|
25
25
|
# Shared `getElementsByTagNameNS(namespace, localName)` — a live collection
|
|
26
26
|
# of descendants of `root` matching the (namespace, localName) filter, where
|
|
27
27
|
# "*" matches any. An empty-string namespace means the null namespace.
|
|
28
|
+
HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"
|
|
29
|
+
|
|
30
|
+
# WHATWG `getElementsByTagName(qualifiedName)` — a live collection filtered
|
|
31
|
+
# by qualified name. "*" matches any. In an HTML document, HTML-namespace
|
|
32
|
+
# elements match case-insensitively (ASCII), while other-namespace elements
|
|
33
|
+
# (and everything in a non-HTML document) match case-sensitively.
|
|
34
|
+
def self.elements_by_tag_name(root, document, qualified_name)
|
|
35
|
+
qn = qualified_name.to_s
|
|
36
|
+
html_doc = document.respond_to?(:html_document?) ? document.html_document? : true
|
|
37
|
+
qn_lower = ascii_downcase(qn)
|
|
38
|
+
new do
|
|
39
|
+
root.css("*").filter_map do |node|
|
|
40
|
+
el = document.wrap_node(node)
|
|
41
|
+
next nil unless el
|
|
42
|
+
next el if qn == "*"
|
|
43
|
+
|
|
44
|
+
el_qn = qualified_name_of(el)
|
|
45
|
+
el_ns = el.respond_to?(:namespace_uri) ? el.namespace_uri : nil
|
|
46
|
+
# For an HTML-namespace element in an HTML document, only the QUERY is
|
|
47
|
+
# ASCII-lowercased — the element's own qualified name is compared as-is
|
|
48
|
+
# (so an uppercase-localName HTML element, e.g. createElementNS(html,
|
|
49
|
+
# "I"), never matches "i" or "I").
|
|
50
|
+
match =
|
|
51
|
+
if html_doc && el_ns == HTML_NAMESPACE
|
|
52
|
+
el_qn == qn_lower
|
|
53
|
+
else
|
|
54
|
+
el_qn == qn
|
|
55
|
+
end
|
|
56
|
+
match ? el : nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The element's qualified name (prefix:localName, or just localName). The
|
|
62
|
+
# backend node name can't be trusted — the HTML parser lowercases it — so
|
|
63
|
+
# rebuild it from the case-preserving local name and prefix.
|
|
64
|
+
def self.qualified_name_of(el)
|
|
65
|
+
local = el.respond_to?(:local_name) ? el.local_name.to_s : ""
|
|
66
|
+
prefix = el.respond_to?(:__js_get__) ? el.__js_get__("prefix") : nil
|
|
67
|
+
prefix = nil if prefix.nil? || prefix.to_s.empty? ||
|
|
68
|
+
(defined?(Bridge::UNDEFINED) && prefix.equal?(Bridge::UNDEFINED))
|
|
69
|
+
prefix ? "#{prefix}:#{local}" : local
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# ASCII-only lowercase (A-Z -> a-z), leaving non-ASCII code points intact,
|
|
73
|
+
# per the spec's "converted to ASCII lowercase".
|
|
74
|
+
def self.ascii_downcase(str)
|
|
75
|
+
str.gsub(/[A-Z]/) { |c| (c.ord + 32).chr }
|
|
76
|
+
end
|
|
77
|
+
|
|
28
78
|
def self.elements_by_tag_name_ns(root, document, namespace, local_name)
|
|
29
79
|
ns = namespace.to_s
|
|
30
80
|
ns_filter = ns == "*" ? :any : (ns.empty? ? nil : ns)
|
|
31
81
|
local = local_name.to_s
|
|
82
|
+
local_filter = local == "*" ? :any : local
|
|
32
83
|
new do
|
|
33
|
-
|
|
34
|
-
|
|
84
|
+
# Match on the element's LOCAL NAME (case-sensitive, exact) and
|
|
85
|
+
# namespace — NOT a CSS type selector, which is case-insensitive in an
|
|
86
|
+
# HTML document and keys off the qualified name (so it misses a
|
|
87
|
+
# prefixed `test:body` and wrongly matches `BODY` for `body`).
|
|
88
|
+
root.css("*").filter_map do |node|
|
|
35
89
|
el = document.wrap_node(node)
|
|
36
90
|
next nil unless el
|
|
37
91
|
|
|
38
92
|
el_ns = el.respond_to?(:namespace_uri) ? el.namespace_uri : nil
|
|
39
|
-
|
|
93
|
+
next nil unless ns_filter == :any || el_ns == ns_filter
|
|
94
|
+
|
|
95
|
+
el_local = el.respond_to?(:local_name) ? el.local_name : nil
|
|
96
|
+
next nil unless local_filter == :any || el_local == local_filter
|
|
97
|
+
|
|
98
|
+
el
|
|
40
99
|
end
|
|
41
100
|
end
|
|
42
101
|
end
|
|
@@ -69,7 +128,14 @@ module Dommy
|
|
|
69
128
|
to_a.find do |el|
|
|
70
129
|
next false unless el.respond_to?(:__dommy_backend_node__)
|
|
71
130
|
|
|
72
|
-
|
|
131
|
+
node = el.__dommy_backend_node__
|
|
132
|
+
# `id` matches any element; `name` matches only HTML-namespace elements
|
|
133
|
+
# (WebIDL supported property names), so a null-namespace element's
|
|
134
|
+
# `name` attribute isn't a supported name.
|
|
135
|
+
next true if node["id"].to_s == key
|
|
136
|
+
|
|
137
|
+
html_ns = !el.respond_to?(:namespace_uri) || el.namespace_uri == "http://www.w3.org/1999/xhtml"
|
|
138
|
+
html_ns && node["name"].to_s == key
|
|
73
139
|
end
|
|
74
140
|
end
|
|
75
141
|
|
|
@@ -112,9 +178,11 @@ module Dommy
|
|
|
112
178
|
item(key)
|
|
113
179
|
else
|
|
114
180
|
s = key.to_s
|
|
115
|
-
if s.match?(/\A\d+\z/) && s.to_i < 4_294_967_295
|
|
116
|
-
# A valid array index
|
|
117
|
-
#
|
|
181
|
+
if s.match?(/\A\d+\z/) && s.to_i < 4_294_967_295 && s == s.to_i.to_s
|
|
182
|
+
# A valid array index is the CANONICAL decimal of 0 ≤ n < 2^32-1 (no
|
|
183
|
+
# leading zeros: "03" is NOT an index, it is a named key). A pure
|
|
184
|
+
# indexed lookup — out of range yields nil (→ undefined), no named
|
|
185
|
+
# fallback.
|
|
118
186
|
item(s.to_i)
|
|
119
187
|
else
|
|
120
188
|
# Non-array-index strings (negative, ≥ 2^32-1, or names) use the named
|
|
@@ -157,6 +225,35 @@ module Dommy
|
|
|
157
225
|
end
|
|
158
226
|
end
|
|
159
227
|
|
|
228
|
+
# `HTMLFormControlsCollection` — a form's `elements`. Like HTMLCollection but
|
|
229
|
+
# its named getter returns a RadioNodeList when a name/id matches more than one
|
|
230
|
+
# control (e.g. a radio group), and the single control otherwise.
|
|
231
|
+
class HTMLFormControlsCollection < HTMLCollection
|
|
232
|
+
def named_item(name)
|
|
233
|
+
key = name.to_s
|
|
234
|
+
return nil if key.empty?
|
|
235
|
+
|
|
236
|
+
matches = controls_named(key)
|
|
237
|
+
return nil if matches.empty?
|
|
238
|
+
return matches.first if matches.length == 1
|
|
239
|
+
|
|
240
|
+
# A live RadioNodeList: a reference held across a DOM mutation reflects the
|
|
241
|
+
# updated group (per spec the named getter returns a live NodeList).
|
|
242
|
+
coll = self
|
|
243
|
+
RadioNodeList.new(matches) { coll.controls_named(key) }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# The controls in this collection whose id or name equals `key`, in order.
|
|
247
|
+
def controls_named(key)
|
|
248
|
+
to_a.select do |el|
|
|
249
|
+
next false unless el.respond_to?(:__dommy_backend_node__)
|
|
250
|
+
|
|
251
|
+
node = el.__dommy_backend_node__
|
|
252
|
+
node["id"].to_s == key || node["name"].to_s == key
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
160
257
|
# `HTMLOptionsCollection` — specialized `<select>.options` collection.
|
|
161
258
|
# Adds `add(option, before?)`, `remove(index)`, the `selectedIndex`
|
|
162
259
|
# getter/setter, and a `length=` setter that truncates or extends.
|
|
@@ -169,24 +266,25 @@ module Dommy
|
|
|
169
266
|
@owner = owner
|
|
170
267
|
end
|
|
171
268
|
|
|
172
|
-
# Append (or insert before `before`) an option element. `before`
|
|
173
|
-
#
|
|
174
|
-
#
|
|
269
|
+
# Append (or insert before `before`) an option element. `before` accepts
|
|
270
|
+
# another element (insert before it) or an integer index. Strings/`null`
|
|
271
|
+
# append. The insertion happens in the REFERENCE's parent — which may be an
|
|
272
|
+
# `<optgroup>` — not always the select itself.
|
|
175
273
|
def add(option, before = nil)
|
|
176
274
|
return nil unless option.respond_to?(:__dommy_backend_node__)
|
|
177
275
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
anchor ? @owner.insert_before(option, anchor) : @owner.append_child(option)
|
|
184
|
-
else
|
|
185
|
-
if before.respond_to?(:__dommy_backend_node__)
|
|
186
|
-
@owner.insert_before(option, before)
|
|
187
|
-
else
|
|
188
|
-
@owner.append_child(option)
|
|
276
|
+
reference =
|
|
277
|
+
case before
|
|
278
|
+
when nil then nil
|
|
279
|
+
when Integer then item(before)
|
|
280
|
+
else before.respond_to?(:__dommy_backend_node__) ? before : nil
|
|
189
281
|
end
|
|
282
|
+
|
|
283
|
+
parent = reference&.parent_node
|
|
284
|
+
if reference && parent.respond_to?(:insert_before)
|
|
285
|
+
parent.insert_before(option, reference)
|
|
286
|
+
else
|
|
287
|
+
@owner.append_child(option)
|
|
190
288
|
end
|
|
191
289
|
|
|
192
290
|
nil
|
|
@@ -198,6 +296,29 @@ module Dommy
|
|
|
198
296
|
nil
|
|
199
297
|
end
|
|
200
298
|
|
|
299
|
+
# WebIDL "set an indexed property" for HTMLOptionsCollection:
|
|
300
|
+
# * a null value removes the option at `index`
|
|
301
|
+
# * otherwise, an in-range index replaces that option; an index at or past
|
|
302
|
+
# the end appends (padding with blank options for any gap).
|
|
303
|
+
def __set_indexed__(index, option)
|
|
304
|
+
i = index.to_i
|
|
305
|
+
if option.nil?
|
|
306
|
+
remove(i)
|
|
307
|
+
return nil
|
|
308
|
+
end
|
|
309
|
+
return nil unless option.respond_to?(:__dommy_backend_node__)
|
|
310
|
+
|
|
311
|
+
current = to_a
|
|
312
|
+
if i < current.length
|
|
313
|
+
@owner.insert_before(option, current[i])
|
|
314
|
+
current[i].remove
|
|
315
|
+
else
|
|
316
|
+
(i - current.length).times { @owner.append_child(@owner.document.create_element("option")) }
|
|
317
|
+
@owner.append_child(option)
|
|
318
|
+
end
|
|
319
|
+
nil
|
|
320
|
+
end
|
|
321
|
+
|
|
201
322
|
def selected_index
|
|
202
323
|
@owner.selected_index
|
|
203
324
|
end
|
|
@@ -237,6 +358,9 @@ module Dommy
|
|
|
237
358
|
when "length"
|
|
238
359
|
self.length = value
|
|
239
360
|
else
|
|
361
|
+
# Indexed property setter: `options[i] = option | null`.
|
|
362
|
+
return __set_indexed__(key.to_i, value) if key.is_a?(Integer) || (key.is_a?(String) && key.match?(/\A\d+\z/))
|
|
363
|
+
|
|
240
364
|
return Bridge::UNHANDLED
|
|
241
365
|
end
|
|
242
366
|
|