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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae1783921d75a534741bfa2de3be6ddd7492f01a6d63d4a3102b85411518e64d
|
|
4
|
+
data.tar.gz: 3022f80e295f06db17782b034d252e8529435ccae9e7ce7283e936da2cea8804
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3ccc2658999f785bab29f01e94bca1d7c0505370256fb55687cca8822b0e4dcdd8efb1d64d172a3a7dced5a3a4224612bc38d8df48f943088d56bd81bc85cd9
|
|
7
|
+
data.tar.gz: dc65af8b414579d71d72601e1c1f3ac070ed6e4a67a7385cbc17222ed9b896687a494709f974f75af192d2441f12de3cc6be0f4eaaa263198f5bc868335193ab
|
data/lib/dommy/animation.rb
CHANGED
|
@@ -48,6 +48,8 @@ module Dommy
|
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
include Bridge::Methods
|
|
52
|
+
js_methods %w[getTiming updateTiming]
|
|
51
53
|
def __js_call__(method, args)
|
|
52
54
|
case method
|
|
53
55
|
when "getTiming"
|
|
@@ -217,11 +219,17 @@ module Dommy
|
|
|
217
219
|
@playback_rate = value.to_f
|
|
218
220
|
when "id"
|
|
219
221
|
@id = value.to_s
|
|
222
|
+
else
|
|
223
|
+
return Bridge::UNHANDLED
|
|
220
224
|
end
|
|
221
225
|
|
|
222
226
|
nil
|
|
223
227
|
end
|
|
224
228
|
|
|
229
|
+
include Bridge::Methods
|
|
230
|
+
js_methods %w[
|
|
231
|
+
play pause cancel finish reverse addEventListener removeEventListener dispatchEvent
|
|
232
|
+
]
|
|
225
233
|
def __js_call__(method, args)
|
|
226
234
|
case method
|
|
227
235
|
when "play"
|
|
@@ -237,7 +245,7 @@ module Dommy
|
|
|
237
245
|
when "addEventListener"
|
|
238
246
|
add_event_listener(args[0], args[1], args[2])
|
|
239
247
|
when "removeEventListener"
|
|
240
|
-
remove_event_listener(args[0], args[1])
|
|
248
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
241
249
|
when "dispatchEvent"
|
|
242
250
|
dispatch_event(args[0])
|
|
243
251
|
end
|
data/lib/dommy/attr.rb
CHANGED
|
@@ -13,12 +13,32 @@ module Dommy
|
|
|
13
13
|
# not yet attached. Value is stored locally; `setAttributeNode`
|
|
14
14
|
# transfers it to an element.
|
|
15
15
|
class Attr
|
|
16
|
-
|
|
16
|
+
include Node
|
|
17
|
+
include EventTarget
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
attr_reader :name, :namespace_uri, :prefix, :local_name
|
|
20
|
+
|
|
21
|
+
def __internal_event_parent__
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(name, owner: nil, value: "", namespace_uri: nil, prefix: nil, local_name: nil)
|
|
26
|
+
qname = name.to_s
|
|
20
27
|
@owner = owner
|
|
21
28
|
@detached_value = value.to_s
|
|
29
|
+
if namespace_uri && !namespace_uri.to_s.empty?
|
|
30
|
+
# Namespaced attributes preserve case and carry prefix / localName.
|
|
31
|
+
@name = qname
|
|
32
|
+
@namespace_uri = namespace_uri.to_s
|
|
33
|
+
@prefix = prefix
|
|
34
|
+
@local_name = (local_name || qname.split(":", 2).last).to_s
|
|
35
|
+
else
|
|
36
|
+
# Null-namespace (HTML) attributes are lower-cased, as before.
|
|
37
|
+
@name = qname.downcase
|
|
38
|
+
@namespace_uri = nil
|
|
39
|
+
@prefix = nil
|
|
40
|
+
@local_name = @name
|
|
41
|
+
end
|
|
22
42
|
end
|
|
23
43
|
|
|
24
44
|
# The Element this attr is on, or nil if detached.
|
|
@@ -28,7 +48,11 @@ module Dommy
|
|
|
28
48
|
|
|
29
49
|
def value
|
|
30
50
|
if @owner
|
|
31
|
-
@
|
|
51
|
+
if @namespace_uri
|
|
52
|
+
Backend.get_attribute_ns(@owner.__dommy_backend_node__, @namespace_uri, @local_name).to_s
|
|
53
|
+
else
|
|
54
|
+
@owner.__dommy_backend_node__[@name].to_s
|
|
55
|
+
end
|
|
32
56
|
else
|
|
33
57
|
@detached_value
|
|
34
58
|
end
|
|
@@ -36,7 +60,11 @@ module Dommy
|
|
|
36
60
|
|
|
37
61
|
def value=(new_value)
|
|
38
62
|
if @owner
|
|
39
|
-
@
|
|
63
|
+
if @namespace_uri
|
|
64
|
+
@owner.set_attribute_ns(@namespace_uri, @name, new_value.to_s)
|
|
65
|
+
else
|
|
66
|
+
@owner.set_attribute(@name, new_value.to_s)
|
|
67
|
+
end
|
|
40
68
|
else
|
|
41
69
|
@detached_value = new_value.to_s
|
|
42
70
|
end
|
|
@@ -52,36 +80,65 @@ module Dommy
|
|
|
52
80
|
@name
|
|
53
81
|
when "nodeValue"
|
|
54
82
|
value
|
|
83
|
+
when "textContent"
|
|
84
|
+
# Node.textContent for an Attr returns its value (WHATWG DOM).
|
|
85
|
+
value
|
|
55
86
|
when "ownerElement"
|
|
56
87
|
@owner
|
|
57
88
|
when "localName"
|
|
58
|
-
@
|
|
89
|
+
@local_name
|
|
59
90
|
when "namespaceURI"
|
|
60
|
-
|
|
91
|
+
@namespace_uri
|
|
92
|
+
when "prefix"
|
|
93
|
+
@prefix
|
|
61
94
|
when "nodeType"
|
|
62
95
|
2
|
|
96
|
+
when "specified"
|
|
97
|
+
# Legacy/useless attribute — always true (WHATWG DOM).
|
|
98
|
+
true
|
|
63
99
|
end
|
|
64
100
|
end
|
|
65
101
|
|
|
66
102
|
def __js_set__(key, val)
|
|
67
103
|
case key
|
|
68
|
-
when "value", "nodeValue"
|
|
104
|
+
when "value", "nodeValue", "textContent"
|
|
69
105
|
self.value = val
|
|
106
|
+
else
|
|
107
|
+
return Bridge::UNHANDLED
|
|
70
108
|
end
|
|
71
109
|
|
|
72
110
|
nil
|
|
73
111
|
end
|
|
74
112
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def __js_call__(method, _args)
|
|
113
|
+
include Bridge::Methods
|
|
114
|
+
js_methods %w[cloneNode isSameNode getRootNode hasChildNodes normalize compareDocumentPosition
|
|
115
|
+
appendChild insertBefore removeChild replaceChild
|
|
116
|
+
addEventListener removeEventListener dispatchEvent]
|
|
117
|
+
def __js_call__(method, args)
|
|
82
118
|
case method
|
|
83
119
|
when "cloneNode"
|
|
84
|
-
Attr.new(@name, owner: nil, value: value
|
|
120
|
+
Attr.new(@name, owner: nil, value: value,
|
|
121
|
+
namespace_uri: @namespace_uri, prefix: @prefix, local_name: @local_name)
|
|
122
|
+
when "isSameNode"
|
|
123
|
+
is_same_node(args[0])
|
|
124
|
+
when "getRootNode"
|
|
125
|
+
get_root_node(args[0])
|
|
126
|
+
when "compareDocumentPosition"
|
|
127
|
+
compare_document_position(args[0])
|
|
128
|
+
when "appendChild", "insertBefore"
|
|
129
|
+
raise DOMException::HierarchyRequestError, "an Attr may not have children"
|
|
130
|
+
when "removeChild", "replaceChild"
|
|
131
|
+
raise DOMException::NotFoundError, "the node to be removed is not a child of this node"
|
|
132
|
+
when "hasChildNodes"
|
|
133
|
+
false
|
|
134
|
+
when "normalize"
|
|
135
|
+
nil
|
|
136
|
+
when "addEventListener"
|
|
137
|
+
add_event_listener(args[0], args[1], args[2])
|
|
138
|
+
when "removeEventListener"
|
|
139
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
140
|
+
when "dispatchEvent"
|
|
141
|
+
dispatch_event(args[0])
|
|
85
142
|
end
|
|
86
143
|
end
|
|
87
144
|
|
|
@@ -112,49 +169,134 @@ module Dommy
|
|
|
112
169
|
|
|
113
170
|
def initialize(element)
|
|
114
171
|
@element = element
|
|
172
|
+
# Attr-node identity cache, keyed by [namespace_or_nil, localName].
|
|
173
|
+
# Every accessor (item / index / getNamedItem(NS)) returns the SAME
|
|
174
|
+
# Attr object for a given underlying attribute, per the DOM.
|
|
175
|
+
@attrs = {}
|
|
115
176
|
end
|
|
116
177
|
|
|
117
178
|
def length
|
|
118
|
-
@element.__dommy_backend_node__.
|
|
179
|
+
Backend.attribute_nodes(@element.__dommy_backend_node__).size
|
|
119
180
|
end
|
|
120
181
|
|
|
121
182
|
alias size length
|
|
122
183
|
|
|
123
184
|
def item(index)
|
|
124
|
-
|
|
125
|
-
|
|
185
|
+
node = Backend.attribute_nodes(@element.__dommy_backend_node__)[index.to_i]
|
|
186
|
+
node && attr_for(node)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Return the cached Attr for a backend attribute node, creating (and
|
|
190
|
+
# caching) one on first access so DOM node identity holds.
|
|
191
|
+
def attr_for(attr_node)
|
|
192
|
+
info = Backend.attribute_ns_info(attr_node)
|
|
193
|
+
key = [info[:namespace_uri], info[:local_name]]
|
|
194
|
+
cached = @attrs[key]
|
|
195
|
+
return cached if cached && cached.owner_element.equal?(@element)
|
|
196
|
+
|
|
197
|
+
attr = Attr.new(info[:qualified_name], owner: @element,
|
|
198
|
+
namespace_uri: info[:namespace_uri],
|
|
199
|
+
prefix: info[:prefix],
|
|
200
|
+
local_name: info[:local_name])
|
|
201
|
+
@attrs[key] = attr
|
|
202
|
+
attr
|
|
126
203
|
end
|
|
127
204
|
|
|
128
205
|
def get_named_item(name)
|
|
129
206
|
key = name.to_s.downcase
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
207
|
+
node = Backend.attribute_nodes(@element.__dommy_backend_node__).find do |a|
|
|
208
|
+
Backend.attribute_ns_info(a)[:qualified_name] == key
|
|
209
|
+
end
|
|
210
|
+
node && attr_for(node)
|
|
133
211
|
end
|
|
134
212
|
|
|
135
213
|
def set_named_item(attr)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
key = attr.name
|
|
139
|
-
val = attr.value
|
|
140
|
-
attr.__internal_attach__(@element)
|
|
141
|
-
@element.set_attribute(key, val)
|
|
142
|
-
attr
|
|
214
|
+
set_attribute_node(attr)
|
|
143
215
|
end
|
|
144
216
|
|
|
145
217
|
def remove_named_item(name)
|
|
146
218
|
key = name.to_s.downcase
|
|
147
|
-
|
|
219
|
+
node = Backend.attribute_nodes(@element.__dommy_backend_node__).find do |a|
|
|
220
|
+
Backend.attribute_ns_info(a)[:qualified_name] == key
|
|
221
|
+
end
|
|
222
|
+
return nil unless node
|
|
148
223
|
|
|
149
|
-
|
|
224
|
+
removed = attr_for(node)
|
|
150
225
|
@element.remove_attribute(key)
|
|
151
|
-
|
|
226
|
+
removed
|
|
152
227
|
end
|
|
153
228
|
|
|
154
|
-
def each
|
|
155
|
-
@element.__dommy_backend_node__.
|
|
156
|
-
yield
|
|
229
|
+
def each
|
|
230
|
+
Backend.attribute_nodes(@element.__dommy_backend_node__).each do |a|
|
|
231
|
+
yield attr_for(a)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# WHATWG "set an attribute" / "set attribute node". Adopts `attr` (the
|
|
236
|
+
# exact object — identity is preserved), replacing any attribute with the
|
|
237
|
+
# same (namespace, localName) and returning the previous Attr (detached),
|
|
238
|
+
# or nil. Throws InUseAttributeError if `attr` is bound to another element.
|
|
239
|
+
def set_attribute_node(attr)
|
|
240
|
+
return nil unless attr.is_a?(Attr)
|
|
241
|
+
|
|
242
|
+
owner = attr.owner_element
|
|
243
|
+
if owner && !owner.equal?(@element)
|
|
244
|
+
raise DOMException::InUseAttributeError, "attribute is in use by another element"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
ns = attr.namespace_uri
|
|
248
|
+
local = attr.local_name
|
|
249
|
+
old = get_named_item_ns(ns, local)
|
|
250
|
+
return attr if old && old.equal?(attr)
|
|
251
|
+
|
|
252
|
+
value = attr.value
|
|
253
|
+
key = [ns, local]
|
|
254
|
+
if old
|
|
255
|
+
old.__internal_detach__
|
|
256
|
+
@attrs.delete(key)
|
|
257
|
+
end
|
|
258
|
+
attr.__internal_attach__(@element)
|
|
259
|
+
if ns
|
|
260
|
+
@element.set_attribute_ns(ns, attr.name, value)
|
|
261
|
+
else
|
|
262
|
+
@element.set_attribute(attr.name, value)
|
|
157
263
|
end
|
|
264
|
+
@attrs[key] = attr
|
|
265
|
+
old
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Detach and evict the cached Attr for (namespace, localName), if any —
|
|
269
|
+
# called by Element after the underlying attribute is removed so a held
|
|
270
|
+
# reference reports `ownerElement === null`.
|
|
271
|
+
def __internal_evict__(namespace, local_name)
|
|
272
|
+
key = [namespace.to_s.empty? ? nil : namespace.to_s, local_name.to_s]
|
|
273
|
+
attr = @attrs.delete(key)
|
|
274
|
+
attr&.__internal_detach__
|
|
275
|
+
nil
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# ----- Namespaced named-item access (getNamedItemNS etc.) -----
|
|
279
|
+
|
|
280
|
+
def get_named_item_ns(namespace, local_name)
|
|
281
|
+
node = Backend.attribute_nodes(@element.__dommy_backend_node__).find do |a|
|
|
282
|
+
info = Backend.attribute_ns_info(a)
|
|
283
|
+
info[:local_name] == local_name.to_s &&
|
|
284
|
+
(info[:namespace_uri] || nil) == (namespace.to_s.empty? ? nil : namespace.to_s)
|
|
285
|
+
end
|
|
286
|
+
node && attr_for(node)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# setNamedItemNS shares the "set an attribute" algorithm with setNamedItem.
|
|
290
|
+
def set_named_item_ns(attr)
|
|
291
|
+
set_attribute_node(attr)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def remove_named_item_ns(namespace, local_name)
|
|
295
|
+
existing = get_named_item_ns(namespace, local_name)
|
|
296
|
+
return nil unless existing
|
|
297
|
+
|
|
298
|
+
@element.remove_attribute_ns(namespace, local_name)
|
|
299
|
+
existing
|
|
158
300
|
end
|
|
159
301
|
|
|
160
302
|
# Property-style access — `el.attributes.id`, `el.attributes["class"]`.
|
|
@@ -181,12 +323,17 @@ module Dommy
|
|
|
181
323
|
end
|
|
182
324
|
end
|
|
183
325
|
|
|
184
|
-
#
|
|
185
|
-
|
|
186
|
-
def
|
|
187
|
-
|
|
326
|
+
# WebIDL "supported property names" for NamedNodeMap: the qualified name of
|
|
327
|
+
# each attribute, in order (the indexed names are reflected separately).
|
|
328
|
+
def __js_named_props__
|
|
329
|
+
Backend.attribute_nodes(@element.__dommy_backend_node__).map do |a|
|
|
330
|
+
Backend.attribute_ns_info(a)[:qualified_name]
|
|
331
|
+
end
|
|
188
332
|
end
|
|
189
333
|
|
|
334
|
+
include Bridge::Methods
|
|
335
|
+
js_methods %w[item getNamedItem setNamedItem removeNamedItem
|
|
336
|
+
getNamedItemNS setNamedItemNS removeNamedItemNS]
|
|
190
337
|
def __js_call__(method, args)
|
|
191
338
|
case method
|
|
192
339
|
when "item"
|
|
@@ -197,6 +344,12 @@ module Dommy
|
|
|
197
344
|
set_named_item(args[0])
|
|
198
345
|
when "removeNamedItem"
|
|
199
346
|
remove_named_item(args[0])
|
|
347
|
+
when "getNamedItemNS"
|
|
348
|
+
get_named_item_ns(args[0], args[1])
|
|
349
|
+
when "setNamedItemNS"
|
|
350
|
+
set_named_item_ns(args[0])
|
|
351
|
+
when "removeNamedItemNS"
|
|
352
|
+
remove_named_item_ns(args[0], args[1])
|
|
200
353
|
end
|
|
201
354
|
end
|
|
202
355
|
|
|
@@ -21,6 +21,11 @@ module Dommy
|
|
|
21
21
|
::Nokogiri::HTML5(html.to_s, max_errors: 0)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Parse an XML string into an XML document (DOMParser "text/xml" etc.).
|
|
25
|
+
def parse_xml(xml)
|
|
26
|
+
::Nokogiri::XML(xml.to_s)
|
|
27
|
+
end
|
|
28
|
+
|
|
24
29
|
def fragment(html, owner_doc:)
|
|
25
30
|
# owner_doc is unused by Nokogiri — the fragment carries its
|
|
26
31
|
# own document. The Parser layer copies nodes into the target.
|
|
@@ -39,6 +44,12 @@ module Dommy
|
|
|
39
44
|
::Nokogiri::XML::Comment.new(doc, content)
|
|
40
45
|
end
|
|
41
46
|
|
|
47
|
+
def create_cdata(content, doc)
|
|
48
|
+
::Nokogiri::XML::CDATA.new(doc, content.to_s)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def cdata_class = ::Nokogiri::XML::CDATA
|
|
52
|
+
|
|
42
53
|
def namespace_of(node)
|
|
43
54
|
node.namespace
|
|
44
55
|
end
|
|
@@ -46,6 +57,71 @@ module Dommy
|
|
|
46
57
|
def add_namespace_definition(node, prefix, href)
|
|
47
58
|
node.add_namespace_definition(prefix, href)
|
|
48
59
|
end
|
|
60
|
+
|
|
61
|
+
# ----- Namespaced attributes -----
|
|
62
|
+
|
|
63
|
+
def get_attribute_ns(node, namespace, local_name)
|
|
64
|
+
find_attr_ns(node, namespace, local_name)&.value
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def has_attribute_ns?(node, namespace, local_name)
|
|
68
|
+
!find_attr_ns(node, namespace, local_name).nil?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def set_attribute_ns(node, namespace, prefix, local_name, qualified_name, value)
|
|
72
|
+
# WHATWG "set an attribute value": when an attribute with this
|
|
73
|
+
# (namespace, localName) already exists, only its value changes —
|
|
74
|
+
# the existing prefix/qualified name is preserved, not replaced by
|
|
75
|
+
# the one in this call.
|
|
76
|
+
existing = find_attr_ns(node, namespace, local_name)
|
|
77
|
+
if existing
|
|
78
|
+
existing.value = value.to_s
|
|
79
|
+
return value.to_s
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if namespace.nil? || namespace.to_s.empty?
|
|
83
|
+
node[qualified_name] = value.to_s
|
|
84
|
+
return value.to_s
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Defining the namespace before the qualified-name assignment lets
|
|
88
|
+
# libxml2 bind the prefix to it (verified behavior). Reuse an existing
|
|
89
|
+
# matching definition so repeated sets don't pile up declarations.
|
|
90
|
+
node.namespace_definitions.find { |d| d.href == namespace.to_s && d.prefix == prefix } ||
|
|
91
|
+
node.add_namespace_definition(prefix, namespace.to_s)
|
|
92
|
+
node[qualified_name] = value.to_s
|
|
93
|
+
value.to_s
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def remove_attribute_ns(node, namespace, local_name)
|
|
97
|
+
find_attr_ns(node, namespace, local_name)&.remove
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def attribute_ns_info(attr_node)
|
|
102
|
+
ns = attr_node.namespace
|
|
103
|
+
{
|
|
104
|
+
namespace_uri: ns&.href,
|
|
105
|
+
prefix: ns&.prefix,
|
|
106
|
+
local_name: attr_node.name,
|
|
107
|
+
qualified_name: ns&.prefix ? "#{ns.prefix}:#{attr_node.name}" : attr_node.name,
|
|
108
|
+
value: attr_node.value,
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def attribute_nodes(node)
|
|
113
|
+
node.attribute_nodes
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Finds the attribute node matching (namespace, localName). A null
|
|
117
|
+
# namespace matches only the un-namespaced attribute of that local name.
|
|
118
|
+
def find_attr_ns(node, namespace, local_name)
|
|
119
|
+
if namespace.nil? || namespace.to_s.empty?
|
|
120
|
+
node.attribute_nodes.find { |a| a.namespace.nil? && a.name == local_name.to_s }
|
|
121
|
+
else
|
|
122
|
+
node.attribute_with_ns(local_name.to_s, namespace.to_s)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
49
125
|
end
|
|
50
126
|
end
|
|
51
127
|
end
|
|
@@ -63,6 +63,43 @@ module Dommy
|
|
|
63
63
|
# No-op: Nokolexbor doesn't support XML namespaces.
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
# ----- Namespaced attributes (degraded) -----
|
|
67
|
+
# Nokolexbor has no namespace model, so *AttributeNS collapses to the
|
|
68
|
+
# qualified name in the null namespace. Fine for HTML (all attributes are
|
|
69
|
+
# null-namespace); foreign-content (SVG/MathML) fidelity is lost.
|
|
70
|
+
|
|
71
|
+
def get_attribute_ns(node, _namespace, local_name)
|
|
72
|
+
node[local_name.to_s]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def has_attribute_ns?(node, _namespace, local_name)
|
|
76
|
+
node.key?(local_name.to_s)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def set_attribute_ns(node, _namespace, _prefix, _local_name, qualified_name, value)
|
|
80
|
+
node[qualified_name] = value.to_s
|
|
81
|
+
value.to_s
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def remove_attribute_ns(node, _namespace, local_name)
|
|
85
|
+
node.remove_attribute(local_name.to_s) if node.key?(local_name.to_s)
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def attribute_ns_info(attr_node)
|
|
90
|
+
{
|
|
91
|
+
namespace_uri: nil,
|
|
92
|
+
prefix: nil,
|
|
93
|
+
local_name: attr_node.name,
|
|
94
|
+
qualified_name: attr_node.name,
|
|
95
|
+
value: attr_node.value,
|
|
96
|
+
}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def attribute_nodes(node)
|
|
100
|
+
node.attribute_nodes
|
|
101
|
+
end
|
|
102
|
+
|
|
66
103
|
# Internal helper — visible to allow testing.
|
|
67
104
|
def in_svg_subtree?(node)
|
|
68
105
|
return true if node.name.to_s.downcase == "svg"
|
data/lib/dommy/backend.rb
CHANGED
|
@@ -49,6 +49,12 @@ module Dommy
|
|
|
49
49
|
current.parse(html)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# Parse XML input into an XML document. Backends without a real XML parser
|
|
53
|
+
# (HTML-only, e.g. Nokolexbor) fall back to the HTML parser.
|
|
54
|
+
def parse_xml(xml)
|
|
55
|
+
current.respond_to?(:parse_xml) ? current.parse_xml(xml) : current.parse(xml)
|
|
56
|
+
end
|
|
57
|
+
|
|
52
58
|
def fragment(html, owner_doc:)
|
|
53
59
|
current.fragment(html, owner_doc: owner_doc)
|
|
54
60
|
end
|
|
@@ -65,6 +71,16 @@ module Dommy
|
|
|
65
71
|
current.create_comment(content, doc)
|
|
66
72
|
end
|
|
67
73
|
|
|
74
|
+
# CDATA section node (XML documents). Backends without CDATA fall back to a
|
|
75
|
+
# text node.
|
|
76
|
+
def create_cdata(content, doc)
|
|
77
|
+
current.respond_to?(:create_cdata) ? current.create_cdata(content, doc) : current.create_text(content, doc)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def cdata_class
|
|
81
|
+
current.respond_to?(:cdata_class) ? current.cdata_class : nil
|
|
82
|
+
end
|
|
83
|
+
|
|
68
84
|
def namespace_of(node)
|
|
69
85
|
current.namespace_of(node)
|
|
70
86
|
end
|
|
@@ -73,6 +89,36 @@ module Dommy
|
|
|
73
89
|
current.add_namespace_definition(node, prefix, href)
|
|
74
90
|
end
|
|
75
91
|
|
|
92
|
+
# Namespaced attribute access (DOM *AttributeNS). `namespace` is an href
|
|
93
|
+
# String or nil. Nokolexbor degrades to qualified-name (null-namespace).
|
|
94
|
+
def get_attribute_ns(node, namespace, local_name)
|
|
95
|
+
current.get_attribute_ns(node, namespace, local_name)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def set_attribute_ns(node, namespace, prefix, local_name, qualified_name, value)
|
|
99
|
+
current.set_attribute_ns(node, namespace, prefix, local_name, qualified_name, value)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def remove_attribute_ns(node, namespace, local_name)
|
|
103
|
+
current.remove_attribute_ns(node, namespace, local_name)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def has_attribute_ns?(node, namespace, local_name)
|
|
107
|
+
current.has_attribute_ns?(node, namespace, local_name)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Reads a backend attribute node into {namespace_uri:, prefix:,
|
|
111
|
+
# local_name:, qualified_name:, value:} (namespace-aware).
|
|
112
|
+
def attribute_ns_info(attr_node)
|
|
113
|
+
current.attribute_ns_info(attr_node)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# The element's attribute nodes (each readable via attribute_ns_info).
|
|
117
|
+
# The single choke point so DOM code doesn't touch parser internals.
|
|
118
|
+
def attribute_nodes(node)
|
|
119
|
+
current.attribute_nodes(node)
|
|
120
|
+
end
|
|
121
|
+
|
|
76
122
|
# Type constants — proxy through to the current backend so
|
|
77
123
|
# `node.is_a?(Backend::Element)` resolves dynamically.
|
|
78
124
|
def element_class
|
data/lib/dommy/blob.rb
CHANGED
|
@@ -17,12 +17,16 @@ module Dommy
|
|
|
17
17
|
# - anything else: coerced via to_s
|
|
18
18
|
#
|
|
19
19
|
# `options["type"]` sets the MIME type (lowercased per spec).
|
|
20
|
-
|
|
20
|
+
# `window` (optional) lets the JS-facing `text()`/`arrayBuffer()` return real
|
|
21
|
+
# Promises (they need a scheduler). A window-less Blob falls back to a
|
|
22
|
+
# synchronous result, which `await` still handles.
|
|
23
|
+
def initialize(parts = [], options = {}, window = nil)
|
|
21
24
|
parts = [parts] unless parts.is_a?(Array)
|
|
22
25
|
@data = collect_bytes(parts)
|
|
23
26
|
@size = @data.bytesize
|
|
24
27
|
raw_type = options["type"] || options[:type] || ""
|
|
25
28
|
@type = raw_type.to_s.downcase
|
|
29
|
+
@window = window
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
# Return a new Blob over a byte range of this one.
|
|
@@ -31,7 +35,7 @@ module Dommy
|
|
|
31
35
|
s = clamp_index(start.to_i, @size)
|
|
32
36
|
e = clamp_index(last.to_i, @size)
|
|
33
37
|
e = s if e < s
|
|
34
|
-
Blob.new([@data.byteslice(s, e - s) || ""], "type" => content_type.to_s)
|
|
38
|
+
Blob.new([@data.byteslice(s, e - s) || ""], {"type" => content_type.to_s}, @window)
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
# Read the bytes as UTF-8 text. The DOM spec returns a Promise,
|
|
@@ -40,10 +44,11 @@ module Dommy
|
|
|
40
44
|
@data.dup.force_encoding(Encoding::UTF_8)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
|
-
# Read the bytes as
|
|
44
|
-
#
|
|
47
|
+
# Read the bytes as a real ArrayBuffer (the spec return type, wrapped so it
|
|
48
|
+
# crosses the JS boundary as a bare ArrayBuffer rather than an Array/typed
|
|
49
|
+
# array). The DOM spec returns a Promise<ArrayBuffer>; Dommy is synchronous.
|
|
45
50
|
def array_buffer
|
|
46
|
-
@data.bytes
|
|
51
|
+
Bridge::ArrayBuffer.new(@data.bytes)
|
|
47
52
|
end
|
|
48
53
|
|
|
49
54
|
# Raw binary bytes (Ruby ASCII-8BIT string). Used by FormData /
|
|
@@ -61,19 +66,31 @@ module Dommy
|
|
|
61
66
|
end
|
|
62
67
|
end
|
|
63
68
|
|
|
69
|
+
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
70
|
+
# File < Blob inherits these (it adds only properties).
|
|
71
|
+
include Bridge::Methods
|
|
72
|
+
js_methods %w[slice text arrayBuffer]
|
|
64
73
|
def __js_call__(method, args)
|
|
65
74
|
case method
|
|
66
75
|
when "slice"
|
|
67
76
|
slice(args[0] || 0, args[1] || @size, args[2] || "")
|
|
68
77
|
when "text"
|
|
69
|
-
text
|
|
78
|
+
# WHATWG: Blob.text() returns a Promise<string>.
|
|
79
|
+
promise_or_value(text)
|
|
70
80
|
when "arrayBuffer"
|
|
71
|
-
|
|
81
|
+
# WHATWG: Blob.arrayBuffer() returns a Promise<ArrayBuffer>.
|
|
82
|
+
promise_or_value(array_buffer)
|
|
72
83
|
end
|
|
73
84
|
end
|
|
74
85
|
|
|
75
86
|
private
|
|
76
87
|
|
|
88
|
+
# Wrap a consumed value in a resolved Promise when a window is available;
|
|
89
|
+
# otherwise return it directly (a window-less Blob — `await` copes either way).
|
|
90
|
+
def promise_or_value(value)
|
|
91
|
+
@window ? PromiseValue.resolve(@window, value) : value
|
|
92
|
+
end
|
|
93
|
+
|
|
77
94
|
def collect_bytes(parts)
|
|
78
95
|
buf = String.new(encoding: Encoding::ASCII_8BIT)
|
|
79
96
|
parts.each do |part|
|
|
@@ -106,8 +123,8 @@ module Dommy
|
|
|
106
123
|
class File < Blob
|
|
107
124
|
attr_reader :name, :last_modified
|
|
108
125
|
|
|
109
|
-
def initialize(parts, name, options = {})
|
|
110
|
-
super(parts, options)
|
|
126
|
+
def initialize(parts, name, options = {}, window = nil)
|
|
127
|
+
super(parts, options, window)
|
|
111
128
|
@name = name.to_s
|
|
112
129
|
raw_lm = options["lastModified"] || options[:lastModified]
|
|
113
130
|
@last_modified = (raw_lm || (Time.now.to_f * 1000)).to_i
|
|
@@ -172,6 +189,8 @@ module Dommy
|
|
|
172
189
|
end
|
|
173
190
|
end
|
|
174
191
|
|
|
192
|
+
include Bridge::Methods
|
|
193
|
+
js_methods %w[item]
|
|
175
194
|
def __js_call__(method, args)
|
|
176
195
|
case method
|
|
177
196
|
when "item"
|