dommy 0.7.0 → 0.8.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/animation.rb +9 -1
- data/lib/dommy/attr.rb +192 -39
- data/lib/dommy/backend/nokogiri_adapter.rb +76 -0
- data/lib/dommy/backend/nokolexbor_adapter.rb +37 -0
- data/lib/dommy/backend.rb +46 -0
- data/lib/dommy/blob.rb +28 -9
- data/lib/dommy/bridge/constructor_registry.rb +28 -0
- data/lib/dommy/bridge/methods.rb +57 -0
- data/lib/dommy/bridge.rb +97 -0
- data/lib/dommy/callable_invoker.rb +36 -0
- data/lib/dommy/cookie_store.rb +3 -1
- data/lib/dommy/crypto.rb +7 -1
- data/lib/dommy/css.rb +46 -0
- data/lib/dommy/custom_elements.rb +27 -3
- data/lib/dommy/data_transfer.rb +4 -0
- data/lib/dommy/document.rb +615 -48
- data/lib/dommy/dom_parser.rb +28 -15
- data/lib/dommy/element.rb +999 -471
- data/lib/dommy/event.rb +260 -96
- data/lib/dommy/event_source.rb +6 -2
- data/lib/dommy/fetch.rb +505 -43
- data/lib/dommy/file_reader.rb +11 -3
- data/lib/dommy/form_data.rb +2 -0
- data/lib/dommy/history.rb +43 -8
- data/lib/dommy/html_collection.rb +55 -2
- data/lib/dommy/html_elements.rb +102 -1519
- data/lib/dommy/internal/css_pseudo_handlers.rb +109 -0
- data/lib/dommy/internal/global_functions.rb +26 -0
- data/lib/dommy/internal/idna.rb +16 -7
- data/lib/dommy/internal/ipv4_parser.rb +22 -7
- data/lib/dommy/internal/mutation_coordinator.rb +11 -2
- data/lib/dommy/internal/namespaces.rb +70 -0
- data/lib/dommy/internal/node_equality.rb +86 -0
- data/lib/dommy/internal/node_wrapper_cache.rb +62 -27
- data/lib/dommy/internal/observable_callback.rb +1 -5
- data/lib/dommy/internal/parent_node.rb +126 -0
- data/lib/dommy/internal/reflected_attributes.rb +103 -13
- data/lib/dommy/internal/selector_parser.rb +664 -0
- data/lib/dommy/internal/url_parser.rb +677 -0
- data/lib/dommy/intersection_observer.rb +2 -0
- data/lib/dommy/location.rb +2 -0
- data/lib/dommy/media_query_list.rb +7 -1
- data/lib/dommy/message_channel.rb +32 -2
- data/lib/dommy/mutation_observer.rb +55 -12
- data/lib/dommy/navigator.rb +26 -12
- data/lib/dommy/node.rb +158 -28
- data/lib/dommy/notification.rb +3 -1
- data/lib/dommy/performance.rb +4 -0
- data/lib/dommy/performance_observer.rb +2 -0
- data/lib/dommy/promise.rb +14 -14
- data/lib/dommy/range.rb +74 -5
- data/lib/dommy/resize_observer.rb +2 -0
- data/lib/dommy/scheduler.rb +34 -13
- data/lib/dommy/shadow_root.rb +23 -54
- data/lib/dommy/storage.rb +2 -0
- data/lib/dommy/streams.rb +18 -27
- data/lib/dommy/svg_elements.rb +204 -3606
- data/lib/dommy/text_codec.rb +174 -21
- data/lib/dommy/tree_walker.rb +255 -66
- data/lib/dommy/url.rb +287 -449
- data/lib/dommy/url_pattern.rb +2 -0
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +37 -7
- data/lib/dommy/window.rb +202 -213
- data/lib/dommy/worker.rb +7 -7
- data/lib/dommy/xml_http_request.rb +15 -5
- data/lib/dommy.rb +7 -0
- metadata +12 -3
data/lib/dommy/file_reader.rb
CHANGED
|
@@ -45,7 +45,8 @@ module Dommy
|
|
|
45
45
|
alias readAsDataURL read_as_data_url
|
|
46
46
|
|
|
47
47
|
def read_as_array_buffer(blob)
|
|
48
|
-
|
|
48
|
+
# readAsArrayBuffer's result is an ArrayBuffer — cross it as a bare one.
|
|
49
|
+
schedule_read(blob) { |raw| Bridge::ArrayBuffer.new(raw.bytes) }
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
alias readAsArrayBuffer read_as_array_buffer
|
|
@@ -89,10 +90,17 @@ module Dommy
|
|
|
89
90
|
|
|
90
91
|
def __js_set__(key, value)
|
|
91
92
|
event = inline_event_for(key)
|
|
92
|
-
|
|
93
|
+
return Bridge::UNHANDLED unless event
|
|
94
|
+
|
|
95
|
+
set_inline_handler(event, value)
|
|
93
96
|
nil
|
|
94
97
|
end
|
|
95
98
|
|
|
99
|
+
include Bridge::Methods
|
|
100
|
+
js_methods %w[
|
|
101
|
+
readAsText readAsDataURL readAsArrayBuffer readAsBinaryString abort addEventListener
|
|
102
|
+
removeEventListener dispatchEvent
|
|
103
|
+
]
|
|
96
104
|
def __js_call__(method, args)
|
|
97
105
|
case method
|
|
98
106
|
when "readAsText"
|
|
@@ -108,7 +116,7 @@ module Dommy
|
|
|
108
116
|
when "addEventListener"
|
|
109
117
|
add_event_listener(args[0], args[1], args[2])
|
|
110
118
|
when "removeEventListener"
|
|
111
|
-
remove_event_listener(args[0], args[1])
|
|
119
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
112
120
|
when "dispatchEvent"
|
|
113
121
|
dispatch_event(args[0])
|
|
114
122
|
end
|
data/lib/dommy/form_data.rb
CHANGED
data/lib/dommy/history.rb
CHANGED
|
@@ -9,9 +9,10 @@ module Dommy
|
|
|
9
9
|
def initialize(window, location)
|
|
10
10
|
@window = window
|
|
11
11
|
@location = location
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
|
|
12
|
+
# Each entry records the full href it navigated to, so back/forward can
|
|
13
|
+
# restore Location to it (and fire popstate) — a restoration that
|
|
14
|
+
# framework routers like Turbo's depend on to swap the cached snapshot.
|
|
15
|
+
@stack = [{state: nil, url: @location.href}]
|
|
15
16
|
@cursor = 0
|
|
16
17
|
@scroll_restoration = "auto"
|
|
17
18
|
end
|
|
@@ -34,11 +35,15 @@ module Dommy
|
|
|
34
35
|
# values silently retain the current value.
|
|
35
36
|
v = value.to_s
|
|
36
37
|
@scroll_restoration = v if %w[auto manual].include?(v)
|
|
38
|
+
else
|
|
39
|
+
return Bridge::UNHANDLED
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
nil
|
|
40
43
|
end
|
|
41
44
|
|
|
45
|
+
include Bridge::Methods
|
|
46
|
+
js_methods %w[pushState replaceState back forward go]
|
|
42
47
|
def __js_call__(method, args)
|
|
43
48
|
case method
|
|
44
49
|
when "pushState"
|
|
@@ -57,18 +62,43 @@ module Dommy
|
|
|
57
62
|
private
|
|
58
63
|
|
|
59
64
|
def push(state, url)
|
|
65
|
+
resolved = resolve_url!(url)
|
|
60
66
|
@stack = @stack[0..@cursor]
|
|
61
|
-
@location.__internal_set_url__(
|
|
67
|
+
@location.__internal_set_url__(resolved) if resolved
|
|
62
68
|
# WHATWG: pushState serializes the state via structured-clone
|
|
63
69
|
# so subsequent caller-side mutation of the original cannot
|
|
64
70
|
# affect history.state.
|
|
65
|
-
@stack << {state: Dommy.structured_clone(state), url:
|
|
71
|
+
@stack << {state: Dommy.structured_clone(state), url: @location.href}
|
|
66
72
|
@cursor = @stack.size - 1
|
|
67
73
|
end
|
|
68
74
|
|
|
69
75
|
def replace(state, url)
|
|
70
|
-
|
|
71
|
-
@
|
|
76
|
+
resolved = resolve_url!(url)
|
|
77
|
+
@location.__internal_set_url__(resolved) if resolved
|
|
78
|
+
@stack[@cursor] = {state: Dommy.structured_clone(state), url: @location.href}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# WHATWG "URL and history update steps": resolve the given URL against the
|
|
82
|
+
# document URL; a parse failure or a cross-origin result is a SecurityError
|
|
83
|
+
# (same-document history entries must stay same-origin).
|
|
84
|
+
def resolve_url!(url)
|
|
85
|
+
return nil if url.nil? || url.equal?(Bridge::UNDEFINED)
|
|
86
|
+
|
|
87
|
+
current = @location.href.to_s
|
|
88
|
+
resolved =
|
|
89
|
+
begin
|
|
90
|
+
current.empty? ? URL.new(url.to_s) : URL.new(url.to_s, current)
|
|
91
|
+
rescue StandardError
|
|
92
|
+
raise DOMException::SecurityError, "A history state object with URL '#{url}' cannot be created in a document with URL '#{current}'."
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
unless current.empty?
|
|
96
|
+
if resolved.origin != URL.new(current).origin
|
|
97
|
+
raise DOMException::SecurityError, "A history state object with URL '#{resolved.href}' cannot be created in a document with origin '#{URL.new(current).origin}'."
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
resolved.href
|
|
72
102
|
end
|
|
73
103
|
|
|
74
104
|
def go(delta)
|
|
@@ -76,7 +106,12 @@ module Dommy
|
|
|
76
106
|
return if target < 0 || target >= @stack.size
|
|
77
107
|
|
|
78
108
|
@cursor = target
|
|
79
|
-
@
|
|
109
|
+
entry = @stack[@cursor]
|
|
110
|
+
# Restore Location to the target entry's URL BEFORE firing popstate, so a
|
|
111
|
+
# listener that reads `location` (Turbo's restoration visit, the WHATWG
|
|
112
|
+
# traversal steps) sees the destination, not the page we came from.
|
|
113
|
+
@location.__internal_set_url__(entry[:url]) if entry[:url]
|
|
114
|
+
@window.fire_popstate(entry[:state])
|
|
80
115
|
end
|
|
81
116
|
end
|
|
82
117
|
end
|
|
@@ -22,6 +22,25 @@ module Dommy
|
|
|
22
22
|
@compute = compute
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# Shared `getElementsByTagNameNS(namespace, localName)` — a live collection
|
|
26
|
+
# of descendants of `root` matching the (namespace, localName) filter, where
|
|
27
|
+
# "*" matches any. An empty-string namespace means the null namespace.
|
|
28
|
+
def self.elements_by_tag_name_ns(root, document, namespace, local_name)
|
|
29
|
+
ns = namespace.to_s
|
|
30
|
+
ns_filter = ns == "*" ? :any : (ns.empty? ? nil : ns)
|
|
31
|
+
local = local_name.to_s
|
|
32
|
+
new do
|
|
33
|
+
nodes = local == "*" ? root.css("*") : root.css(local)
|
|
34
|
+
nodes.filter_map do |node|
|
|
35
|
+
el = document.wrap_node(node)
|
|
36
|
+
next nil unless el
|
|
37
|
+
|
|
38
|
+
el_ns = el.respond_to?(:namespace_uri) ? el.namespace_uri : nil
|
|
39
|
+
(ns_filter == :any || el_ns == ns_filter) ? el : nil
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
25
44
|
def length
|
|
26
45
|
to_a.length
|
|
27
46
|
end
|
|
@@ -42,7 +61,10 @@ module Dommy
|
|
|
42
61
|
# `namedItem(name)` returns the first element whose `id` or
|
|
43
62
|
# `name` attribute equals `name`. Returns nil if no match.
|
|
44
63
|
def named_item(name)
|
|
45
|
-
|
|
64
|
+
# A numeric argument (`namedItem(2147483648)`) crosses from JS as a Float
|
|
65
|
+
# for values past int32; format it as an integer string so it matches an
|
|
66
|
+
# `id`/`name` attribute like "2147483648" (not "2147483648.0").
|
|
67
|
+
key = (name.is_a?(Float) && name.finite? && name == name.to_i) ? name.to_i.to_s : name.to_s
|
|
46
68
|
return nil if key.empty?
|
|
47
69
|
|
|
48
70
|
to_a.find do |el|
|
|
@@ -91,14 +113,41 @@ module Dommy
|
|
|
91
113
|
item(key)
|
|
92
114
|
else
|
|
93
115
|
s = key.to_s
|
|
94
|
-
if s.match?(/\A\d+\z/)
|
|
116
|
+
if s.match?(/\A\d+\z/) && s.to_i < 4_294_967_295
|
|
117
|
+
# A valid array index (0 ≤ n < 2^32-1) is a pure indexed lookup — out
|
|
118
|
+
# of range yields nil (→ undefined), never a named fallback.
|
|
95
119
|
item(s.to_i)
|
|
96
120
|
else
|
|
121
|
+
# Non-array-index strings (negative, ≥ 2^32-1, or names) use the named
|
|
122
|
+
# getter.
|
|
97
123
|
named_item(s) || (s == "length" ? length : nil)
|
|
98
124
|
end
|
|
99
125
|
end
|
|
100
126
|
end
|
|
101
127
|
|
|
128
|
+
# WebIDL "supported property names" for HTMLCollection: in tree order, each
|
|
129
|
+
# element contributes its non-empty `id`, then (if it is in the HTML
|
|
130
|
+
# namespace) its non-empty `name` — ignoring duplicates.
|
|
131
|
+
def __js_named_props__
|
|
132
|
+
names = []
|
|
133
|
+
to_a.each do |el|
|
|
134
|
+
next unless el.respond_to?(:__dommy_backend_node__)
|
|
135
|
+
|
|
136
|
+
node = el.__dommy_backend_node__
|
|
137
|
+
id = node["id"].to_s
|
|
138
|
+
names << id if !id.empty? && !names.include?(id)
|
|
139
|
+
|
|
140
|
+
name = node["name"].to_s
|
|
141
|
+
next if name.empty? || names.include?(name)
|
|
142
|
+
|
|
143
|
+
html_ns = !el.respond_to?(:namespace_uri) || el.namespace_uri == "http://www.w3.org/1999/xhtml"
|
|
144
|
+
names << name if html_ns
|
|
145
|
+
end
|
|
146
|
+
names
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
include Bridge::Methods
|
|
150
|
+
js_methods %w[item namedItem]
|
|
102
151
|
def __js_call__(method, args)
|
|
103
152
|
case method
|
|
104
153
|
when "item"
|
|
@@ -188,11 +237,15 @@ module Dommy
|
|
|
188
237
|
self.selected_index = value
|
|
189
238
|
when "length"
|
|
190
239
|
self.length = value
|
|
240
|
+
else
|
|
241
|
+
return Bridge::UNHANDLED
|
|
191
242
|
end
|
|
192
243
|
|
|
193
244
|
nil
|
|
194
245
|
end
|
|
195
246
|
|
|
247
|
+
# Adds add/remove on top of the inherited item/namedItem (else -> super).
|
|
248
|
+
js_methods %w[add remove]
|
|
196
249
|
def __js_call__(method, args)
|
|
197
250
|
case method
|
|
198
251
|
when "add"
|