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/document.rb
CHANGED
|
@@ -11,28 +11,288 @@ require_relative "internal/observer_manager"
|
|
|
11
11
|
require_relative "internal/template_content_registry"
|
|
12
12
|
|
|
13
13
|
module Dommy
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
14
|
+
# DocumentType (`<!doctype html>`) — exposes name / publicId / systemId and
|
|
15
|
+
# nodeType=10. HTML5 doctypes carry empty public/system IDs, but
|
|
16
|
+
# `implementation.createDocumentType` can set them.
|
|
17
17
|
class DocumentType
|
|
18
18
|
include Node
|
|
19
19
|
|
|
20
20
|
attr_reader :name
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
# `owner_document:` links a live doctype (document.doctype) to its document so
|
|
23
|
+
# the ChildNode methods (remove/before/after/replaceWith) act on the tree; a
|
|
24
|
+
# standalone doctype (DOMImplementation.createDocumentType) has none, so those
|
|
25
|
+
# methods are no-ops per spec.
|
|
26
|
+
def initialize(name, public_id = "", system_id = "", owner_document: nil)
|
|
23
27
|
@name = name.to_s
|
|
28
|
+
@public_id = public_id.to_s
|
|
29
|
+
@system_id = system_id.to_s
|
|
30
|
+
@owner_document = owner_document
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# ChildNode mixin — the doctype's parent is the document.
|
|
34
|
+
def remove
|
|
35
|
+
@owner_document&.__internal_remove_doctype__(self)
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def before(*nodes)
|
|
40
|
+
return nil unless @owner_document
|
|
41
|
+
|
|
42
|
+
@owner_document.__internal_insert_at_doctype__(nodes, after: false)
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def after(*nodes)
|
|
47
|
+
return nil unless @owner_document
|
|
48
|
+
|
|
49
|
+
@owner_document.__internal_insert_at_doctype__(nodes, after: true)
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def replace_with(*nodes)
|
|
54
|
+
return nil unless @owner_document
|
|
55
|
+
|
|
56
|
+
@owner_document.__internal_insert_at_doctype__(nodes, after: false)
|
|
57
|
+
remove
|
|
58
|
+
nil
|
|
24
59
|
end
|
|
25
60
|
|
|
26
61
|
def __js_get__(key)
|
|
27
62
|
case key
|
|
28
63
|
when "name"
|
|
29
64
|
@name
|
|
65
|
+
when "nodeName"
|
|
66
|
+
# WHATWG: a DocumentType's nodeName is its name.
|
|
67
|
+
@name
|
|
30
68
|
when "nodeType"
|
|
31
69
|
10
|
|
32
70
|
when "publicId"
|
|
33
|
-
|
|
71
|
+
@public_id
|
|
34
72
|
when "systemId"
|
|
35
|
-
|
|
73
|
+
@system_id
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
include EventTarget
|
|
78
|
+
|
|
79
|
+
def __internal_event_parent__
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
include Bridge::Methods
|
|
84
|
+
js_methods %w[isEqualNode isSameNode getRootNode hasChildNodes normalize compareDocumentPosition
|
|
85
|
+
appendChild insertBefore removeChild replaceChild before after replaceWith remove
|
|
86
|
+
addEventListener removeEventListener dispatchEvent]
|
|
87
|
+
def __js_call__(method, args)
|
|
88
|
+
case method
|
|
89
|
+
when "hasChildNodes"
|
|
90
|
+
false
|
|
91
|
+
when "isEqualNode"
|
|
92
|
+
is_equal_node(args[0])
|
|
93
|
+
when "isSameNode"
|
|
94
|
+
is_same_node(args[0])
|
|
95
|
+
when "getRootNode"
|
|
96
|
+
get_root_node(args[0])
|
|
97
|
+
when "compareDocumentPosition"
|
|
98
|
+
compare_document_position(args[0])
|
|
99
|
+
when "appendChild", "insertBefore"
|
|
100
|
+
raise DOMException::HierarchyRequestError, "a DocumentType may not have children"
|
|
101
|
+
when "removeChild", "replaceChild"
|
|
102
|
+
raise DOMException::NotFoundError, "the node to be removed is not a child of this node"
|
|
103
|
+
when "before"
|
|
104
|
+
before(*args)
|
|
105
|
+
when "after"
|
|
106
|
+
after(*args)
|
|
107
|
+
when "replaceWith"
|
|
108
|
+
replace_with(*args)
|
|
109
|
+
when "remove"
|
|
110
|
+
remove
|
|
111
|
+
when "normalize"
|
|
112
|
+
nil
|
|
113
|
+
when "addEventListener"
|
|
114
|
+
add_event_listener(args[0], args[1], args[2])
|
|
115
|
+
when "removeEventListener"
|
|
116
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
117
|
+
when "dispatchEvent"
|
|
118
|
+
dispatch_event(args[0])
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# ProcessingInstruction (`<?target data?>`) — a CharacterData-like node with a
|
|
124
|
+
# `target`; created via `document.createProcessingInstruction`.
|
|
125
|
+
class ProcessingInstruction
|
|
126
|
+
include Node
|
|
127
|
+
|
|
128
|
+
attr_reader :target
|
|
129
|
+
|
|
130
|
+
def initialize(target, data)
|
|
131
|
+
@target = target.to_s
|
|
132
|
+
@data = data.to_s
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
include EventTarget
|
|
136
|
+
|
|
137
|
+
def data = @data
|
|
138
|
+
|
|
139
|
+
def __js_get__(key)
|
|
140
|
+
case key
|
|
141
|
+
when "target"
|
|
142
|
+
@target
|
|
143
|
+
when "data", "nodeValue", "textContent"
|
|
144
|
+
@data
|
|
145
|
+
when "nodeName"
|
|
146
|
+
@target
|
|
147
|
+
when "nodeType"
|
|
148
|
+
7
|
|
149
|
+
when "length"
|
|
150
|
+
@data.length
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def __js_set__(key, value)
|
|
155
|
+
case key
|
|
156
|
+
when "data", "nodeValue", "textContent"
|
|
157
|
+
@data = value.to_s
|
|
158
|
+
else
|
|
159
|
+
return Bridge::UNHANDLED
|
|
160
|
+
end
|
|
161
|
+
nil
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# A PI is CharacterData: its data methods are string operations on @data.
|
|
165
|
+
def substring_data(offset, count)
|
|
166
|
+
o = offset.to_i
|
|
167
|
+
raise DOMException::IndexSizeError, "offset out of bounds" if o.negative? || o > @data.length
|
|
168
|
+
|
|
169
|
+
@data[o, count.to_i] || ""
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def append_data(value)
|
|
173
|
+
@data += value.to_s
|
|
174
|
+
nil
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def insert_data(offset, value)
|
|
178
|
+
o = offset.to_i
|
|
179
|
+
raise DOMException::IndexSizeError, "offset out of bounds" if o.negative? || o > @data.length
|
|
180
|
+
|
|
181
|
+
@data = @data[0, o].to_s + value.to_s + (@data[o..] || "")
|
|
182
|
+
nil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def delete_data(offset, count)
|
|
186
|
+
o = offset.to_i
|
|
187
|
+
raise DOMException::IndexSizeError, "offset out of bounds" if o.negative? || o > @data.length
|
|
188
|
+
|
|
189
|
+
@data = @data[0, o].to_s + (@data[(o + count.to_i)..] || "")
|
|
190
|
+
nil
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def replace_data(offset, count, value)
|
|
194
|
+
delete_data(offset, count)
|
|
195
|
+
insert_data(offset, value)
|
|
196
|
+
nil
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def __internal_event_parent__
|
|
200
|
+
nil
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
include Bridge::Methods
|
|
204
|
+
js_methods %w[isEqualNode isSameNode getRootNode normalize hasChildNodes
|
|
205
|
+
appendData insertData deleteData replaceData substringData compareDocumentPosition
|
|
206
|
+
appendChild insertBefore removeChild replaceChild before after replaceWith remove
|
|
207
|
+
addEventListener removeEventListener dispatchEvent]
|
|
208
|
+
def __js_call__(method, args)
|
|
209
|
+
case method
|
|
210
|
+
when "hasChildNodes"
|
|
211
|
+
false
|
|
212
|
+
when "isEqualNode"
|
|
213
|
+
is_equal_node(args[0])
|
|
214
|
+
when "isSameNode"
|
|
215
|
+
is_same_node(args[0])
|
|
216
|
+
when "getRootNode"
|
|
217
|
+
get_root_node(args[0])
|
|
218
|
+
when "compareDocumentPosition"
|
|
219
|
+
compare_document_position(args[0])
|
|
220
|
+
when "appendChild", "insertBefore"
|
|
221
|
+
raise DOMException::HierarchyRequestError, "a ProcessingInstruction may not have children"
|
|
222
|
+
when "removeChild", "replaceChild"
|
|
223
|
+
raise DOMException::NotFoundError, "the node to be removed is not a child of this node"
|
|
224
|
+
when "before", "after", "replaceWith", "remove"
|
|
225
|
+
# ChildNode mixin: a created PI has no parent, so these are no-ops per
|
|
226
|
+
# spec (they act only when the node is inserted in a tree).
|
|
227
|
+
nil
|
|
228
|
+
when "normalize"
|
|
229
|
+
nil
|
|
230
|
+
when "substringData"
|
|
231
|
+
substring_data(args[0], args[1])
|
|
232
|
+
when "appendData"
|
|
233
|
+
append_data(args[0])
|
|
234
|
+
when "insertData"
|
|
235
|
+
insert_data(args[0], args[1])
|
|
236
|
+
when "deleteData"
|
|
237
|
+
delete_data(args[0], args[1])
|
|
238
|
+
when "replaceData"
|
|
239
|
+
replace_data(args[0], args[1], args[2])
|
|
240
|
+
when "addEventListener"
|
|
241
|
+
add_event_listener(args[0], args[1], args[2])
|
|
242
|
+
when "removeEventListener"
|
|
243
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
244
|
+
when "dispatchEvent"
|
|
245
|
+
dispatch_event(args[0])
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# `document.implementation` — the DOMImplementation. Only the node factories
|
|
251
|
+
# WPT exercises are provided; createDocument/createHTMLDocument are not yet
|
|
252
|
+
# implemented (foreign documents).
|
|
253
|
+
class DOMImplementation
|
|
254
|
+
def initialize(document)
|
|
255
|
+
@document = document
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def create_document_type(qualified_name, public_id, system_id)
|
|
259
|
+
DocumentType.new(qualified_name, public_id, system_id)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# createDocument(namespace, qualifiedName, doctype?) — a fresh XML document,
|
|
263
|
+
# with a document element (namespace, qualifiedName) when qualifiedName is
|
|
264
|
+
# non-empty. (The doctype argument is accepted but not stored, as document
|
|
265
|
+
# equality compares only structure that survives wrap_node.)
|
|
266
|
+
def create_document(namespace, qualified_name, _doctype = nil)
|
|
267
|
+
doc = Document.new(nil, nokogiri_doc: Backend.document_class.new)
|
|
268
|
+
qn = qualified_name.to_s
|
|
269
|
+
unless qn.empty?
|
|
270
|
+
el = doc.send(:create_element_ns, namespace, qualified_name)
|
|
271
|
+
doc.nokogiri_doc.root = el.__dommy_backend_node__
|
|
272
|
+
end
|
|
273
|
+
doc
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# createHTMLDocument(title?) — a fresh HTML document (doctype + html > head,
|
|
277
|
+
# body), with an optional <title>.
|
|
278
|
+
def create_html_document(title = nil)
|
|
279
|
+
doc = Document.new(nil, nokogiri_doc: Backend.parse("<!DOCTYPE html><html><head></head><body></body></html>"))
|
|
280
|
+
doc.title = title.to_s unless title.nil? || title.equal?(Bridge::UNDEFINED)
|
|
281
|
+
doc
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def __js_get__(_key) = nil
|
|
285
|
+
|
|
286
|
+
include Bridge::Methods
|
|
287
|
+
js_methods %w[createDocumentType createDocument createHTMLDocument]
|
|
288
|
+
def __js_call__(method, args)
|
|
289
|
+
case method
|
|
290
|
+
when "createDocumentType"
|
|
291
|
+
create_document_type(args[0], args[1], args[2])
|
|
292
|
+
when "createDocument"
|
|
293
|
+
create_document(args[0], args[1], args[2])
|
|
294
|
+
when "createHTMLDocument"
|
|
295
|
+
create_html_document(args[0])
|
|
36
296
|
end
|
|
37
297
|
end
|
|
38
298
|
end
|
|
@@ -44,7 +304,7 @@ module Dommy
|
|
|
44
304
|
include EventTarget
|
|
45
305
|
include Node
|
|
46
306
|
|
|
47
|
-
attr_reader :
|
|
307
|
+
attr_reader :nokogiri_doc
|
|
48
308
|
attr_accessor :default_view
|
|
49
309
|
# content_type defaults to "text/html"; settable so an integration layer
|
|
50
310
|
# can reflect the response Content-Type. Read-only over the JS bridge.
|
|
@@ -59,12 +319,33 @@ module Dommy
|
|
|
59
319
|
@cookie_jar = Internal::CookieJar.new
|
|
60
320
|
@template_content_registry = Internal::TemplateContentRegistry.new(self)
|
|
61
321
|
@mutation_coordinator = Internal::MutationCoordinator.new(self, @observer_manager)
|
|
322
|
+
@node_iterators = []
|
|
62
323
|
@nokogiri_doc = nokogiri_doc || Backend.parse("<!doctype html><html><head></head><body></body></html>")
|
|
63
|
-
body_node = @nokogiri_doc.at_css("body")
|
|
64
|
-
@body = wrap_node(body_node) if body_node
|
|
65
324
|
@content_type = "text/html"
|
|
66
325
|
end
|
|
67
326
|
|
|
327
|
+
# Whether this is an "HTML document" in the DOM sense (created by the HTML
|
|
328
|
+
# parser / `text/html`), as opposed to an XML document. It drives the
|
|
329
|
+
# case-folding rules: `createElement` lowercases names and `Element#tagName`
|
|
330
|
+
# uppercases HTML-namespace names only in an HTML document. An XML or XHTML
|
|
331
|
+
# document (e.g. an `application/xhtml+xml` / `text/xml` resource) preserves
|
|
332
|
+
# case.
|
|
333
|
+
def html_document?
|
|
334
|
+
@content_type == "text/html"
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# `document.compatMode` — "CSS1Compat" in no-quirks mode, "BackCompat" in
|
|
338
|
+
# quirks mode. A missing doctype is quirks; a bare `<!DOCTYPE html>` (no
|
|
339
|
+
# public/system identifier) is no-quirks. (The full quirks algorithm keys off
|
|
340
|
+
# specific legacy public ids; this covers the common cases.)
|
|
341
|
+
def compat_mode
|
|
342
|
+
dt = @nokogiri_doc.internal_subset
|
|
343
|
+
return "BackCompat" unless dt
|
|
344
|
+
return "CSS1Compat" if dt.name.to_s.downcase == "html" && dt.external_id.nil?
|
|
345
|
+
|
|
346
|
+
"BackCompat"
|
|
347
|
+
end
|
|
348
|
+
|
|
68
349
|
# ----- Public Ruby API (snake_case) -----
|
|
69
350
|
|
|
70
351
|
def title
|
|
@@ -76,13 +357,23 @@ module Dommy
|
|
|
76
357
|
end
|
|
77
358
|
|
|
78
359
|
def document_element
|
|
79
|
-
|
|
360
|
+
# The document's root element — `<html>` for HTML, the actual root for XML.
|
|
361
|
+
wrap_node(@nokogiri_doc.root)
|
|
80
362
|
end
|
|
81
363
|
|
|
82
364
|
def head
|
|
83
365
|
wrap_node(@nokogiri_doc.at_css("head"))
|
|
84
366
|
end
|
|
85
367
|
|
|
368
|
+
# Resolve `body` fresh from the tree (not memoized) so it tracks a swapped
|
|
369
|
+
# `<body>` — e.g. Turbo's page render does
|
|
370
|
+
# `documentElement.replaceChild(newBody, body)`, after which a stale cached
|
|
371
|
+
# wrapper would keep returning the detached old body. wrap_node caches by
|
|
372
|
+
# node, so identity (`document.body === document.body`) still holds.
|
|
373
|
+
def body
|
|
374
|
+
wrap_node(@nokogiri_doc.at_css("body"))
|
|
375
|
+
end
|
|
376
|
+
|
|
86
377
|
# Serialize the whole document to HTML (including the doctype).
|
|
87
378
|
def to_html
|
|
88
379
|
@nokogiri_doc.to_html
|
|
@@ -185,6 +476,15 @@ module Dommy
|
|
|
185
476
|
end
|
|
186
477
|
end
|
|
187
478
|
|
|
479
|
+
# All child nodes of the document (doctype + document element, …), as a live,
|
|
480
|
+
# cached NodeList — unlike `children`, which is element-only. Cached so
|
|
481
|
+
# `document.childNodes === document.childNodes` and mutations are reflected.
|
|
482
|
+
def child_nodes
|
|
483
|
+
@live_child_nodes ||= LiveNodeList.new do
|
|
484
|
+
@nokogiri_doc.children.map { |n| wrap_node(n) }.compact
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
|
|
188
488
|
def child_element_count
|
|
189
489
|
children.size
|
|
190
490
|
end
|
|
@@ -200,7 +500,18 @@ module Dommy
|
|
|
200
500
|
# Currently-focused element (or body if none). Updated via
|
|
201
501
|
# `el.focus()` / `el.blur()`.
|
|
202
502
|
def active_element
|
|
203
|
-
@active_element ||
|
|
503
|
+
@active_element || body
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# `document.contains(node)` — true if `node` is the document itself or any
|
|
507
|
+
# node attached to its tree (per Node.contains, which all nodes including the
|
|
508
|
+
# document expose). Per spec, false for null / a non-Node.
|
|
509
|
+
def contains?(other)
|
|
510
|
+
return true if other.equal?(self)
|
|
511
|
+
return false unless other.respond_to?(:__dommy_backend_node__)
|
|
512
|
+
|
|
513
|
+
node = other.__dommy_backend_node__
|
|
514
|
+
node.document == @nokogiri_doc && node.ancestors.include?(@nokogiri_doc)
|
|
204
515
|
end
|
|
205
516
|
|
|
206
517
|
def __internal_set_active_element__(el)
|
|
@@ -226,6 +537,25 @@ module Dommy
|
|
|
226
537
|
TreeWalker.new(root, what_to_show, filter)
|
|
227
538
|
end
|
|
228
539
|
|
|
540
|
+
# WebIDL `unsigned long whatToShow = 0xFFFFFFFF`: an omitted or `undefined`
|
|
541
|
+
# argument uses the default; `null` coerces to 0; otherwise ToUint32.
|
|
542
|
+
def coerce_what_to_show(args, index)
|
|
543
|
+
return NodeFilter::SHOW_ALL if args.length <= index
|
|
544
|
+
value = args[index]
|
|
545
|
+
return NodeFilter::SHOW_ALL if defined?(Bridge::UNDEFINED) && value.equal?(Bridge::UNDEFINED)
|
|
546
|
+
return 0 if value.nil?
|
|
547
|
+
|
|
548
|
+
value.to_i % (2**32)
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
# A `null`/`undefined` filter argument means "no filter".
|
|
552
|
+
def normalize_filter(value)
|
|
553
|
+
return nil if value.nil?
|
|
554
|
+
return nil if defined?(Bridge::UNDEFINED) && value.equal?(Bridge::UNDEFINED)
|
|
555
|
+
|
|
556
|
+
value
|
|
557
|
+
end
|
|
558
|
+
|
|
229
559
|
# Copy a node from another document into this one. The returned
|
|
230
560
|
# wrapper is owned by `this`. Per spec, the source node is left
|
|
231
561
|
# in place. `deep: true` copies the entire subtree.
|
|
@@ -340,7 +670,11 @@ module Dommy
|
|
|
340
670
|
# `document.createNodeIterator(root, whatToShow?, filter?)` —
|
|
341
671
|
# flat depth-first iteration.
|
|
342
672
|
def create_node_iterator(root, what_to_show = NodeFilter::SHOW_ALL, filter = nil)
|
|
343
|
-
NodeIterator.new(root, what_to_show, filter)
|
|
673
|
+
iterator = NodeIterator.new(root, what_to_show, filter)
|
|
674
|
+
# Track live iterators so node removal can run the "NodeIterator
|
|
675
|
+
# pre-removing steps" (adjusting referenceNode) before a node detaches.
|
|
676
|
+
@node_iterators << iterator
|
|
677
|
+
iterator
|
|
344
678
|
end
|
|
345
679
|
|
|
346
680
|
# Minimal DocumentType — represents the `<!doctype html>` line.
|
|
@@ -348,7 +682,114 @@ module Dommy
|
|
|
348
682
|
# stub object whose only useful field is `name`. Tests just need
|
|
349
683
|
# `nodeType == 10`.
|
|
350
684
|
def doctype
|
|
351
|
-
|
|
685
|
+
return nil if @doctype_removed
|
|
686
|
+
|
|
687
|
+
@doctype ||= DocumentType.new("html", owner_document: self)
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def implementation
|
|
691
|
+
@implementation ||= DOMImplementation.new(self)
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
def create_processing_instruction(target, data)
|
|
695
|
+
ProcessingInstruction.new(target, data)
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
# Append a node as a child of the document itself (e.g. a comment alongside
|
|
699
|
+
# the document element). Adopts the node into this document.
|
|
700
|
+
def append_child(node)
|
|
701
|
+
return node unless node.respond_to?(:__dommy_backend_node__)
|
|
702
|
+
|
|
703
|
+
@nokogiri_doc.add_child(node.__dommy_backend_node__)
|
|
704
|
+
node
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
# ParentNode / Node mutation on the document's direct children (the doctype
|
|
708
|
+
# and the document element). Operate on the Nokogiri document node; string
|
|
709
|
+
# arguments (which would need a text child the document can't hold) are
|
|
710
|
+
# ignored rather than raising.
|
|
711
|
+
def document_insert(args, prepend:)
|
|
712
|
+
nodes = args.filter_map { |a| backend_node(a) }
|
|
713
|
+
if prepend && (first = @nokogiri_doc.children.first)
|
|
714
|
+
nodes.reverse_each { |n| first.add_previous_sibling(n) }
|
|
715
|
+
else
|
|
716
|
+
nodes.each { |n| @nokogiri_doc.add_child(n) }
|
|
717
|
+
end
|
|
718
|
+
nil
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
def document_replace_children(args)
|
|
722
|
+
@nokogiri_doc.children.each(&:unlink)
|
|
723
|
+
args.filter_map { |a| backend_node(a) }.each { |n| @nokogiri_doc.add_child(n) }
|
|
724
|
+
nil
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
def document_remove_child(node)
|
|
728
|
+
# The doctype is synthesized from the Nokogiri DTD rather than wrapped as a
|
|
729
|
+
# tree node, so remove the internal subset directly.
|
|
730
|
+
return __internal_remove_doctype__(node) if node.is_a?(DocumentType)
|
|
731
|
+
|
|
732
|
+
bn = backend_node(node)
|
|
733
|
+
raise DOMException::NotFoundError, "node is not a child of this document" unless bn && bn.parent == @nokogiri_doc
|
|
734
|
+
|
|
735
|
+
bn.unlink
|
|
736
|
+
node
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
def document_insert_before(node, ref)
|
|
740
|
+
bn = backend_node(node)
|
|
741
|
+
return node unless bn
|
|
742
|
+
|
|
743
|
+
ref_node = ref && backend_node(ref)
|
|
744
|
+
if ref_node && ref_node.parent == @nokogiri_doc
|
|
745
|
+
ref_node.add_previous_sibling(bn)
|
|
746
|
+
else
|
|
747
|
+
@nokogiri_doc.add_child(bn)
|
|
748
|
+
end
|
|
749
|
+
node
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
def document_replace_child(new_child, old_child)
|
|
753
|
+
old_bn = backend_node(old_child)
|
|
754
|
+
raise DOMException::NotFoundError, "node is not a child of this document" unless old_bn && old_bn.parent == @nokogiri_doc
|
|
755
|
+
|
|
756
|
+
new_bn = backend_node(new_child)
|
|
757
|
+
old_bn.add_previous_sibling(new_bn) if new_bn
|
|
758
|
+
old_bn.unlink
|
|
759
|
+
old_child
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
# Called by DocumentType#remove — the doctype is synthesized from the DTD, so
|
|
763
|
+
# remove the internal subset and mark it gone.
|
|
764
|
+
def __internal_remove_doctype__(_doctype)
|
|
765
|
+
@doctype_removed = true
|
|
766
|
+
@nokogiri_doc.internal_subset&.unlink
|
|
767
|
+
nil
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
# Called by DocumentType#before/#after — insert `nodes` before the doctype
|
|
771
|
+
# (at the document start) or after it (just before the document element).
|
|
772
|
+
def __internal_insert_at_doctype__(nodes, after:)
|
|
773
|
+
bns = nodes.filter_map { |n| backend_node(n) }
|
|
774
|
+
if after
|
|
775
|
+
root = @nokogiri_doc.root
|
|
776
|
+
root ? bns.each { |n| root.add_previous_sibling(n) } : bns.each { |n| @nokogiri_doc.add_child(n) }
|
|
777
|
+
else
|
|
778
|
+
first = @nokogiri_doc.children.first
|
|
779
|
+
first ? bns.reverse_each { |n| first.add_previous_sibling(n) } : bns.each { |n| @nokogiri_doc.add_child(n) }
|
|
780
|
+
end
|
|
781
|
+
nil
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
# `document.cloneNode(deep)` → a fresh Document over a (deep) copy of the
|
|
785
|
+
# Nokogiri tree, preserving the content type.
|
|
786
|
+
def clone_node(deep)
|
|
787
|
+
copy = deep ? @nokogiri_doc.dup : Backend.document_class.new
|
|
788
|
+
Document.new(nil, nokogiri_doc: copy).tap { |d| d.content_type = @content_type }
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
def backend_node(node)
|
|
792
|
+
node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__ : nil
|
|
352
793
|
end
|
|
353
794
|
|
|
354
795
|
# Delegate to CookieJar
|
|
@@ -374,6 +815,10 @@ module Dommy
|
|
|
374
815
|
@node_wrapper_cache.get_elements_by_name(name)
|
|
375
816
|
end
|
|
376
817
|
|
|
818
|
+
def get_elements_by_tag_name_ns(namespace, local_name)
|
|
819
|
+
HTMLCollection.elements_by_tag_name_ns(@nokogiri_doc, self, namespace, local_name)
|
|
820
|
+
end
|
|
821
|
+
|
|
377
822
|
# `document.write(html)` — legacy API. Appends parsed nodes to the
|
|
378
823
|
# body. Real browsers only re-stream the DOM during initial parse;
|
|
379
824
|
# this stub is enough for tests that fire write() during teardown.
|
|
@@ -382,8 +827,9 @@ module Dommy
|
|
|
382
827
|
fragment = Parser.fragment(html, owner_doc: @nokogiri_doc)
|
|
383
828
|
removed = []
|
|
384
829
|
added = fragment.children.to_a
|
|
385
|
-
|
|
386
|
-
|
|
830
|
+
body_node = body.__dommy_backend_node__
|
|
831
|
+
added.each { |node| body_node.add_child(node) }
|
|
832
|
+
notify_child_list_mutation(target_node: body_node, added_nodes: added, removed_nodes: removed)
|
|
387
833
|
nil
|
|
388
834
|
end
|
|
389
835
|
|
|
@@ -411,6 +857,10 @@ module Dommy
|
|
|
411
857
|
@node_wrapper_cache.create_comment(text)
|
|
412
858
|
end
|
|
413
859
|
|
|
860
|
+
def create_cdata_section(text)
|
|
861
|
+
@node_wrapper_cache.create_cdata_section(text)
|
|
862
|
+
end
|
|
863
|
+
|
|
414
864
|
def create_document_fragment
|
|
415
865
|
@node_wrapper_cache.create_document_fragment
|
|
416
866
|
end
|
|
@@ -422,11 +872,13 @@ module Dommy
|
|
|
422
872
|
def __js_get__(key)
|
|
423
873
|
case key
|
|
424
874
|
when "body"
|
|
425
|
-
|
|
875
|
+
body
|
|
426
876
|
when "head"
|
|
427
877
|
head
|
|
428
878
|
when "doctype"
|
|
429
879
|
doctype
|
|
880
|
+
when "implementation"
|
|
881
|
+
implementation
|
|
430
882
|
when "defaultView"
|
|
431
883
|
@default_view
|
|
432
884
|
when "fullscreenElement"
|
|
@@ -436,7 +888,8 @@ module Dommy
|
|
|
436
888
|
when "scrollingElement"
|
|
437
889
|
wrap_node(@nokogiri_doc.at_css("html"))
|
|
438
890
|
when "documentElement"
|
|
439
|
-
|
|
891
|
+
# The document's root element — `<html>` for HTML, the actual root for XML.
|
|
892
|
+
wrap_node(@nokogiri_doc.root)
|
|
440
893
|
when "title"
|
|
441
894
|
read_title
|
|
442
895
|
when "cookie"
|
|
@@ -455,6 +908,33 @@ module Dommy
|
|
|
455
908
|
origin
|
|
456
909
|
when "contentType"
|
|
457
910
|
content_type
|
|
911
|
+
when "location"
|
|
912
|
+
# document.location is the same Location object as window.location.
|
|
913
|
+
@default_view&.__js_get__("location")
|
|
914
|
+
when "characterSet", "charset", "inputEncoding"
|
|
915
|
+
# The DOM is held as Ruby strings (UTF-8); we don't model other encodings.
|
|
916
|
+
"UTF-8"
|
|
917
|
+
when "dir"
|
|
918
|
+
document_element&.get_attribute("dir") || ""
|
|
919
|
+
when "designMode"
|
|
920
|
+
@design_mode || "off"
|
|
921
|
+
when "lastModified"
|
|
922
|
+
@last_modified || "01/01/1970 00:00:00"
|
|
923
|
+
when "readyState"
|
|
924
|
+
# The document is fully parsed by the time scripts run against it (there
|
|
925
|
+
# is no incremental network parse), so it is always "complete". Code that
|
|
926
|
+
# gates on `document.readyState === "loading"` (e.g. Turbo's preloader)
|
|
927
|
+
# therefore takes the already-loaded path.
|
|
928
|
+
"complete"
|
|
929
|
+
when "visibilityState"
|
|
930
|
+
# There's no real viewport/tab; the document is treated as the visible,
|
|
931
|
+
# foreground page (so `nextRepaint`-style code uses requestAnimationFrame,
|
|
932
|
+
# and `=== "visible"` checks pass).
|
|
933
|
+
"visible"
|
|
934
|
+
when "hidden"
|
|
935
|
+
false
|
|
936
|
+
when "compatMode"
|
|
937
|
+
compat_mode
|
|
458
938
|
when "referrer"
|
|
459
939
|
referrer
|
|
460
940
|
when "links"
|
|
@@ -465,8 +945,28 @@ module Dommy
|
|
|
465
945
|
scripts
|
|
466
946
|
when "images"
|
|
467
947
|
images
|
|
948
|
+
when "embeds", "plugins"
|
|
949
|
+
# Both reflect the same list of <embed> elements.
|
|
950
|
+
HTMLCollection.new { @nokogiri_doc.css("embed").map { |n| wrap_node(n) }.compact }
|
|
951
|
+
when "anchors"
|
|
952
|
+
# Historically `<a name>` (with a name attribute), not every link.
|
|
953
|
+
HTMLCollection.new { @nokogiri_doc.css("a[name]").map { |n| wrap_node(n) }.compact }
|
|
954
|
+
when "styleSheets"
|
|
955
|
+
# No CSS engine; expose an empty (but present + iterable) StyleSheetList
|
|
956
|
+
# so `document.styleSheets.length` / iteration don't blow up.
|
|
957
|
+
NodeList.new
|
|
468
958
|
when "children"
|
|
469
959
|
children
|
|
960
|
+
when "childNodes"
|
|
961
|
+
child_nodes
|
|
962
|
+
when "firstChild"
|
|
963
|
+
child_nodes.to_a.first
|
|
964
|
+
when "lastChild"
|
|
965
|
+
child_nodes.to_a.last
|
|
966
|
+
when "parentNode", "parentElement", "nextSibling", "previousSibling", "ownerDocument"
|
|
967
|
+
# A document is the tree root: no parent or siblings, and its
|
|
968
|
+
# ownerDocument is null per spec.
|
|
969
|
+
nil
|
|
470
970
|
when "childElementCount"
|
|
471
971
|
child_element_count
|
|
472
972
|
when "firstElementChild"
|
|
@@ -486,26 +986,62 @@ module Dommy
|
|
|
486
986
|
write_title(value.to_s)
|
|
487
987
|
when "cookie"
|
|
488
988
|
self.cookie = value.to_s
|
|
989
|
+
when "dir"
|
|
990
|
+
document_element&.set_attribute("dir", value.to_s)
|
|
991
|
+
when "designMode"
|
|
992
|
+
# Enumerated: only "on"/"off" (case-insensitive), else ignored.
|
|
993
|
+
v = value.to_s.downcase
|
|
994
|
+
@design_mode = v if %w[on off].include?(v)
|
|
995
|
+
when "location"
|
|
996
|
+
# `document.location = url` navigates, same as `location.href = url`.
|
|
997
|
+
loc = @default_view&.__js_get__("location")
|
|
998
|
+
loc&.__js_set__("href", value)
|
|
999
|
+
else
|
|
1000
|
+
return Bridge::UNHANDLED
|
|
489
1001
|
end
|
|
490
1002
|
|
|
491
1003
|
nil
|
|
492
1004
|
end
|
|
493
1005
|
|
|
494
|
-
|
|
495
|
-
|
|
1006
|
+
include Bridge::Methods
|
|
1007
|
+
js_methods %w[
|
|
496
1008
|
exitFullscreen startViewTransition createElement createElementNS createTextNode
|
|
497
|
-
createComment createDocumentFragment querySelector querySelectorAll getElementById
|
|
498
|
-
getElementsByClassName getElementsByTagName getElementsByName createAttribute
|
|
499
|
-
createAttributeNS createTreeWalker createNodeIterator createEvent importNode
|
|
500
|
-
hasFocus getSelection elementFromPoint queryCommandSupported addEventListener
|
|
501
|
-
removeEventListener dispatchEvent write open close
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
end
|
|
506
|
-
|
|
1009
|
+
createComment createCDATASection createProcessingInstruction createDocumentFragment querySelector querySelectorAll getElementById
|
|
1010
|
+
getElementsByClassName getElementsByTagName getElementsByTagNameNS getElementsByName createAttribute
|
|
1011
|
+
createAttributeNS createTreeWalker createNodeIterator createRange createEvent importNode
|
|
1012
|
+
adoptNode hasFocus getSelection elementFromPoint queryCommandSupported addEventListener
|
|
1013
|
+
removeEventListener dispatchEvent write writeln open close isEqualNode appendChild
|
|
1014
|
+
hasChildNodes contains append prepend replaceChildren removeChild insertBefore replaceChild
|
|
1015
|
+
cloneNode normalize
|
|
1016
|
+
]
|
|
507
1017
|
def __js_call__(method, args)
|
|
508
1018
|
case method
|
|
1019
|
+
when "hasChildNodes"
|
|
1020
|
+
@nokogiri_doc.children.any?
|
|
1021
|
+
when "contains"
|
|
1022
|
+
contains?(args[0])
|
|
1023
|
+
when "isEqualNode"
|
|
1024
|
+
is_equal_node(args[0])
|
|
1025
|
+
when "appendChild"
|
|
1026
|
+
append_child(args[0])
|
|
1027
|
+
when "append"
|
|
1028
|
+
document_insert(args, prepend: false)
|
|
1029
|
+
when "prepend"
|
|
1030
|
+
document_insert(args, prepend: true)
|
|
1031
|
+
when "replaceChildren"
|
|
1032
|
+
document_replace_children(args)
|
|
1033
|
+
when "removeChild"
|
|
1034
|
+
document_remove_child(args[0])
|
|
1035
|
+
when "insertBefore"
|
|
1036
|
+
document_insert_before(args[0], args[1])
|
|
1037
|
+
when "replaceChild"
|
|
1038
|
+
document_replace_child(args[0], args[1])
|
|
1039
|
+
when "cloneNode"
|
|
1040
|
+
clone_node(args[0])
|
|
1041
|
+
when "normalize"
|
|
1042
|
+
nil # the document has no text children to merge
|
|
1043
|
+
when "writeln"
|
|
1044
|
+
write(*(args + ["\n"]))
|
|
509
1045
|
when "exitFullscreen"
|
|
510
1046
|
exit_fullscreen
|
|
511
1047
|
when "startViewTransition"
|
|
@@ -528,16 +1064,22 @@ module Dommy
|
|
|
528
1064
|
create_text_node(args[0])
|
|
529
1065
|
when "createComment"
|
|
530
1066
|
create_comment(args[0])
|
|
1067
|
+
when "createCDATASection"
|
|
1068
|
+
create_cdata_section(args[0])
|
|
1069
|
+
when "createProcessingInstruction"
|
|
1070
|
+
create_processing_instruction(args[0], args[1])
|
|
531
1071
|
when "createDocumentFragment"
|
|
532
1072
|
create_document_fragment
|
|
533
1073
|
when "querySelector"
|
|
534
|
-
query_selector(args
|
|
1074
|
+
query_selector(Internal.css_query_arg!(args))
|
|
535
1075
|
when "querySelectorAll"
|
|
536
|
-
query_selector_all(args
|
|
1076
|
+
query_selector_all(Internal.css_query_arg!(args))
|
|
537
1077
|
when "getElementById"
|
|
538
1078
|
get_element_by_id(args[0])
|
|
539
1079
|
when "getElementsByClassName"
|
|
540
1080
|
get_elements_by_class_name(args[0])
|
|
1081
|
+
when "getElementsByTagNameNS"
|
|
1082
|
+
get_elements_by_tag_name_ns(args[0], args[1])
|
|
541
1083
|
when "getElementsByTagName"
|
|
542
1084
|
get_elements_by_tag_name(args[0])
|
|
543
1085
|
when "getElementsByName"
|
|
@@ -547,9 +1089,11 @@ module Dommy
|
|
|
547
1089
|
when "createAttributeNS"
|
|
548
1090
|
create_attribute_ns(args[0], args[1])
|
|
549
1091
|
when "createTreeWalker"
|
|
550
|
-
create_tree_walker(args[0], args
|
|
1092
|
+
create_tree_walker(args[0], coerce_what_to_show(args, 1), normalize_filter(args[2]))
|
|
551
1093
|
when "createNodeIterator"
|
|
552
|
-
create_node_iterator(args[0], args
|
|
1094
|
+
create_node_iterator(args[0], coerce_what_to_show(args, 1), normalize_filter(args[2]))
|
|
1095
|
+
when "createRange"
|
|
1096
|
+
create_range
|
|
553
1097
|
when "createEvent"
|
|
554
1098
|
create_event(args[0])
|
|
555
1099
|
when "importNode"
|
|
@@ -567,7 +1111,7 @@ module Dommy
|
|
|
567
1111
|
when "addEventListener"
|
|
568
1112
|
add_event_listener(args[0], args[1], args[2])
|
|
569
1113
|
when "removeEventListener"
|
|
570
|
-
remove_event_listener(args[0], args[1])
|
|
1114
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
571
1115
|
when "dispatchEvent"
|
|
572
1116
|
dispatch_event(args[0])
|
|
573
1117
|
when "write"
|
|
@@ -664,11 +1208,43 @@ module Dommy
|
|
|
664
1208
|
)
|
|
665
1209
|
end
|
|
666
1210
|
|
|
667
|
-
|
|
1211
|
+
# Unlink a backend node from its parent and queue a childList removal record
|
|
1212
|
+
# capturing the node's position (previous/next sibling) BEFORE the unlink, so
|
|
1213
|
+
# the record's previousSibling/nextSibling are correct (the coordinator can't
|
|
1214
|
+
# recover them once the node is detached). Used by every remove path.
|
|
1215
|
+
def remove_node_with_notify(node)
|
|
1216
|
+
parent = node.parent
|
|
1217
|
+
return unless parent
|
|
1218
|
+
|
|
1219
|
+
prev_w = node.previous_sibling && wrap_node(node.previous_sibling)
|
|
1220
|
+
next_w = node.next_sibling && wrap_node(node.next_sibling)
|
|
1221
|
+
run_node_iterator_pre_remove(node)
|
|
1222
|
+
node.unlink
|
|
1223
|
+
notify_child_list_mutation(
|
|
1224
|
+
target_node: parent,
|
|
1225
|
+
added_nodes: [],
|
|
1226
|
+
removed_nodes: [node],
|
|
1227
|
+
previous_sibling: prev_w,
|
|
1228
|
+
next_sibling: next_w
|
|
1229
|
+
)
|
|
1230
|
+
end
|
|
1231
|
+
|
|
1232
|
+
# Run the "NodeIterator pre-removing steps" for every live iterator before
|
|
1233
|
+
# `backend_node` is detached, so referenceNode/pointerBeforeReferenceNode
|
|
1234
|
+
# stay valid. `backend_node` must still be attached (tree intact) here.
|
|
1235
|
+
def run_node_iterator_pre_remove(backend_node)
|
|
1236
|
+
return if @node_iterators.empty?
|
|
1237
|
+
|
|
1238
|
+
removed = wrap_node(backend_node)
|
|
1239
|
+
@node_iterators.each { |iter| iter.pre_remove(removed) }
|
|
1240
|
+
end
|
|
1241
|
+
|
|
1242
|
+
def notify_attribute_mutation(target_node:, attribute_name:, old_value:, namespace: nil)
|
|
668
1243
|
@mutation_coordinator.notify_attribute_mutation(
|
|
669
1244
|
target_node: target_node,
|
|
670
1245
|
attribute_name: attribute_name,
|
|
671
|
-
old_value: old_value
|
|
1246
|
+
old_value: old_value,
|
|
1247
|
+
namespace: namespace
|
|
672
1248
|
)
|
|
673
1249
|
end
|
|
674
1250
|
|
|
@@ -679,11 +1255,6 @@ module Dommy
|
|
|
679
1255
|
)
|
|
680
1256
|
end
|
|
681
1257
|
|
|
682
|
-
# Spec-permitted name pattern (XML "Name" production restricted to
|
|
683
|
-
# ASCII for practicality). Used by `createElement` and
|
|
684
|
-
# `createAttribute` to validate the argument.
|
|
685
|
-
NAME_RE = /\A[A-Za-z_][\w\-.:]*\z/.freeze
|
|
686
|
-
|
|
687
1258
|
# Delegate factory methods to NodeWrapperCache
|
|
688
1259
|
|
|
689
1260
|
def create_element(name)
|
|
@@ -736,7 +1307,7 @@ module Dommy
|
|
|
736
1307
|
def clone_into_doc(source, deep)
|
|
737
1308
|
copy = if source.element?
|
|
738
1309
|
new_el = Backend.create_element(source.name, @nokogiri_doc)
|
|
739
|
-
|
|
1310
|
+
Backend.attribute_nodes(source).each { |a| new_el[a.name] = a.value }
|
|
740
1311
|
new_el
|
|
741
1312
|
elsif source.text?
|
|
742
1313
|
Backend.create_text(source.content, @nokogiri_doc)
|
|
@@ -816,12 +1387,8 @@ module Dommy
|
|
|
816
1387
|
end
|
|
817
1388
|
end
|
|
818
1389
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
def __js_method_names__
|
|
822
|
-
JS_METHOD_NAMES
|
|
823
|
-
end
|
|
824
|
-
|
|
1390
|
+
include Bridge::Methods
|
|
1391
|
+
js_methods %w[skipTransition]
|
|
825
1392
|
def __js_call__(method, _args)
|
|
826
1393
|
case method
|
|
827
1394
|
when "skipTransition"
|