dommy 0.5.0 → 0.7.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/README.md +31 -13
- data/lib/dommy/animation.rb +288 -0
- data/lib/dommy/attr.rb +23 -11
- data/lib/dommy/backend/nokogiri_adapter.rb +51 -0
- data/lib/dommy/backend/nokolexbor_adapter.rb +80 -0
- data/lib/dommy/backend.rb +129 -0
- data/lib/dommy/blob.rb +2 -2
- data/lib/dommy/compression_streams.rb +147 -0
- data/lib/dommy/cookie_store.rb +128 -0
- data/lib/dommy/crypto.rb +396 -0
- data/lib/dommy/css.rb +7 -7
- data/lib/dommy/custom_elements.rb +6 -6
- data/lib/dommy/document.rb +190 -32
- data/lib/dommy/dom_parser.rb +5 -4
- data/lib/dommy/element.rb +356 -53
- data/lib/dommy/event.rb +431 -25
- data/lib/dommy/event_source.rb +131 -0
- data/lib/dommy/fetch.rb +76 -6
- data/lib/dommy/file_reader.rb +176 -0
- data/lib/dommy/form_data.rb +1 -3
- data/lib/dommy/history.rb +82 -0
- data/lib/dommy/html_collection.rb +4 -4
- data/lib/dommy/html_elements.rb +130 -67
- data/lib/dommy/internal/cookie_jar.rb +2 -0
- data/lib/dommy/internal/css_pseudo_handlers.rb +28 -0
- data/lib/dommy/internal/dom_matching.rb +4 -4
- data/lib/dommy/internal/idna.rb +443 -0
- data/lib/dommy/internal/idna_data.rb +10379 -0
- data/lib/dommy/internal/ipv4_parser.rb +78 -0
- data/lib/dommy/internal/node_traversal.rb +1 -1
- data/lib/dommy/internal/node_wrapper_cache.rb +23 -12
- data/lib/dommy/internal/observable_callback.rb +25 -0
- data/lib/dommy/internal/punycode.rb +202 -0
- data/lib/dommy/internal/range_text_serializer.rb +72 -0
- data/lib/dommy/internal/reflected_attributes.rb +45 -0
- data/lib/dommy/internal/template_content_registry.rb +6 -6
- data/lib/dommy/intersection_observer.rb +82 -0
- data/lib/dommy/{router.rb → location.rb} +8 -142
- data/lib/dommy/media_query_list.rb +118 -0
- data/lib/dommy/message_channel.rb +249 -0
- data/lib/dommy/{observer.rb → mutation_observer.rb} +21 -11
- data/lib/dommy/navigator.rb +365 -5
- data/lib/dommy/node.rb +12 -0
- data/lib/dommy/notification.rb +89 -0
- data/lib/dommy/parser.rb +13 -13
- data/lib/dommy/performance.rb +146 -0
- data/lib/dommy/performance_observer.rb +55 -0
- data/lib/dommy/range.rb +597 -0
- data/lib/dommy/resize_observer.rb +53 -0
- data/lib/dommy/shadow_root.rb +10 -8
- data/lib/dommy/streams.rb +386 -0
- data/lib/dommy/svg_elements.rb +3863 -0
- data/lib/dommy/text_codec.rb +175 -0
- data/lib/dommy/tree_walker.rb +21 -21
- data/lib/dommy/url.rb +274 -29
- data/lib/dommy/url_pattern.rb +144 -0
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +209 -0
- data/lib/dommy/window.rb +369 -0
- data/lib/dommy/worker.rb +143 -0
- data/lib/dommy/xml_http_request.rb +438 -0
- data/lib/dommy.rb +43 -5
- metadata +44 -29
- data/lib/dommy/world.rb +0 -209
data/lib/dommy/element.rb
CHANGED
|
@@ -9,7 +9,9 @@ module Dommy
|
|
|
9
9
|
include EventTarget
|
|
10
10
|
include Node
|
|
11
11
|
|
|
12
|
-
attr_reader :
|
|
12
|
+
attr_reader :document
|
|
13
|
+
|
|
14
|
+
def __dommy_backend_node__ = @__node__
|
|
13
15
|
|
|
14
16
|
def initialize(document, nokogiri_node)
|
|
15
17
|
@document = document
|
|
@@ -98,6 +100,12 @@ module Dommy
|
|
|
98
100
|
end
|
|
99
101
|
end
|
|
100
102
|
|
|
103
|
+
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
104
|
+
JS_METHOD_NAMES = %w[cloneNode querySelector querySelectorAll getElementById appendChild].freeze
|
|
105
|
+
def __js_method_names__
|
|
106
|
+
JS_METHOD_NAMES
|
|
107
|
+
end
|
|
108
|
+
|
|
101
109
|
def __js_call__(method, args)
|
|
102
110
|
case method
|
|
103
111
|
when "cloneNode"
|
|
@@ -128,9 +136,9 @@ module Dommy
|
|
|
128
136
|
def detach_dom_nodes(value)
|
|
129
137
|
case value
|
|
130
138
|
when String
|
|
131
|
-
[@document.create_text_node(value).
|
|
139
|
+
[@document.create_text_node(value).__dommy_backend_node__]
|
|
132
140
|
else
|
|
133
|
-
node = value.respond_to?(:
|
|
141
|
+
node = value.respond_to?(:__dommy_backend_node__) ? value.__dommy_backend_node__ : nil
|
|
134
142
|
return [] unless node
|
|
135
143
|
|
|
136
144
|
node.unlink if node.parent
|
|
@@ -147,7 +155,7 @@ module Dommy
|
|
|
147
155
|
|
|
148
156
|
# Fragments aren't part of the bubble chain; nil terminates
|
|
149
157
|
# bubbling at the boundary (shadow root, detached fragment, etc.).
|
|
150
|
-
def
|
|
158
|
+
def __internal_event_parent__
|
|
151
159
|
nil
|
|
152
160
|
end
|
|
153
161
|
end
|
|
@@ -157,7 +165,7 @@ module Dommy
|
|
|
157
165
|
class CharacterDataNode
|
|
158
166
|
include Node
|
|
159
167
|
|
|
160
|
-
|
|
168
|
+
def __dommy_backend_node__ = @__node__
|
|
161
169
|
|
|
162
170
|
def initialize(document, nokogiri_node)
|
|
163
171
|
@document = document
|
|
@@ -191,7 +199,19 @@ module Dommy
|
|
|
191
199
|
end
|
|
192
200
|
|
|
193
201
|
def remove
|
|
202
|
+
parent = @__node__.parent
|
|
203
|
+
removed = @__node__
|
|
194
204
|
@__node__.unlink
|
|
205
|
+
# Mirror Element#remove_child: notify with the Nokogiri::Node
|
|
206
|
+
# (not the Dommy wrapper) so MutationCoordinator's wrap_node
|
|
207
|
+
# cache keys consistently.
|
|
208
|
+
if parent
|
|
209
|
+
@document.notify_child_list_mutation(
|
|
210
|
+
target_node: parent,
|
|
211
|
+
added_nodes: [],
|
|
212
|
+
removed_nodes: [removed]
|
|
213
|
+
)
|
|
214
|
+
end
|
|
195
215
|
nil
|
|
196
216
|
end
|
|
197
217
|
|
|
@@ -243,16 +263,105 @@ module Dommy
|
|
|
243
263
|
nil
|
|
244
264
|
end
|
|
245
265
|
|
|
246
|
-
|
|
266
|
+
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
267
|
+
JS_METHOD_NAMES = %w[remove before after replaceWith].freeze
|
|
268
|
+
def __js_method_names__
|
|
269
|
+
JS_METHOD_NAMES
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def __js_call__(method, args)
|
|
247
273
|
case method
|
|
248
274
|
when "remove"
|
|
249
|
-
|
|
250
|
-
|
|
275
|
+
remove
|
|
276
|
+
when "before"
|
|
277
|
+
before(*args)
|
|
278
|
+
when "after"
|
|
279
|
+
after(*args)
|
|
280
|
+
when "replaceWith"
|
|
281
|
+
replace_with(*args)
|
|
251
282
|
end
|
|
252
283
|
end
|
|
253
284
|
|
|
285
|
+
# ChildNode mixin: WHATWG DOM defines `before`, `after`,
|
|
286
|
+
# `replaceWith` on all child nodes, including Text and Comment.
|
|
287
|
+
# Implementations operate on the Nokogiri layer and notify the
|
|
288
|
+
# MutationObserver with the underlying nodes (mirroring
|
|
289
|
+
# Element#remove_child / replace_child).
|
|
290
|
+
|
|
291
|
+
def before(*args)
|
|
292
|
+
parent = @__node__.parent
|
|
293
|
+
return nil unless parent
|
|
294
|
+
|
|
295
|
+
added = args.map { |arg| coerce_node(arg) }.compact
|
|
296
|
+
added.reverse_each { |node| @__node__.add_previous_sibling(node) }
|
|
297
|
+
notify_child_list_added(parent, added)
|
|
298
|
+
nil
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def after(*args)
|
|
302
|
+
parent = @__node__.parent
|
|
303
|
+
return nil unless parent
|
|
304
|
+
|
|
305
|
+
added = args.map { |arg| coerce_node(arg) }.compact
|
|
306
|
+
anchor = @__node__.next_sibling
|
|
307
|
+
if anchor
|
|
308
|
+
added.reverse_each { |node| anchor.add_previous_sibling(node) }
|
|
309
|
+
else
|
|
310
|
+
added.each { |node| parent.add_child(node) }
|
|
311
|
+
end
|
|
312
|
+
notify_child_list_added(parent, added)
|
|
313
|
+
nil
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def replace_with(*args)
|
|
317
|
+
parent = @__node__.parent
|
|
318
|
+
return nil unless parent
|
|
319
|
+
|
|
320
|
+
added = args.map { |arg| coerce_node(arg) }.compact
|
|
321
|
+
removed = @__node__
|
|
322
|
+
anchor = @__node__.next_sibling
|
|
323
|
+
@__node__.unlink
|
|
324
|
+
if anchor
|
|
325
|
+
added.reverse_each { |node| anchor.add_previous_sibling(node) }
|
|
326
|
+
else
|
|
327
|
+
added.each { |node| parent.add_child(node) }
|
|
328
|
+
end
|
|
329
|
+
@document.notify_child_list_mutation(
|
|
330
|
+
target_node: parent,
|
|
331
|
+
added_nodes: added,
|
|
332
|
+
removed_nodes: [removed]
|
|
333
|
+
)
|
|
334
|
+
nil
|
|
335
|
+
end
|
|
336
|
+
|
|
254
337
|
private
|
|
255
338
|
|
|
339
|
+
# Coerce a `before` / `after` / `replaceWith` argument into a raw
|
|
340
|
+
# Nokogiri node, ready to be linked into a parent. Strings become
|
|
341
|
+
# fresh text nodes; existing nodes are detached from their current
|
|
342
|
+
# parent first (matching Element#detach_dom_nodes minus the
|
|
343
|
+
# Fragment branch which is rarely needed off a text/comment node).
|
|
344
|
+
def coerce_node(arg)
|
|
345
|
+
case arg
|
|
346
|
+
when String
|
|
347
|
+
@document.create_text_node(arg).__dommy_backend_node__
|
|
348
|
+
else
|
|
349
|
+
node = arg.respond_to?(:__dommy_backend_node__) ? arg.__dommy_backend_node__ : nil
|
|
350
|
+
node.unlink if node && node.parent
|
|
351
|
+
node
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def notify_child_list_added(parent, added)
|
|
356
|
+
return if added.empty?
|
|
357
|
+
|
|
358
|
+
@document.notify_child_list_mutation(
|
|
359
|
+
target_node: parent,
|
|
360
|
+
added_nodes: added,
|
|
361
|
+
removed_nodes: []
|
|
362
|
+
)
|
|
363
|
+
end
|
|
364
|
+
|
|
256
365
|
def write_data(value)
|
|
257
366
|
old = @__node__.content
|
|
258
367
|
@__node__.content = value.to_s
|
|
@@ -265,6 +374,12 @@ module Dommy
|
|
|
265
374
|
3
|
|
266
375
|
end
|
|
267
376
|
|
|
377
|
+
# Own __js_call__ methods, on top of CharacterDataNode's.
|
|
378
|
+
JS_METHOD_NAMES = %w[cloneNode].freeze
|
|
379
|
+
def __js_method_names__
|
|
380
|
+
super + JS_METHOD_NAMES
|
|
381
|
+
end
|
|
382
|
+
|
|
268
383
|
def __js_call__(method, args)
|
|
269
384
|
case method
|
|
270
385
|
when "cloneNode"
|
|
@@ -280,6 +395,12 @@ module Dommy
|
|
|
280
395
|
8
|
|
281
396
|
end
|
|
282
397
|
|
|
398
|
+
# Own __js_call__ methods, on top of CharacterDataNode's.
|
|
399
|
+
JS_METHOD_NAMES = %w[cloneNode].freeze
|
|
400
|
+
def __js_method_names__
|
|
401
|
+
super + JS_METHOD_NAMES
|
|
402
|
+
end
|
|
403
|
+
|
|
283
404
|
def __js_call__(method, args)
|
|
284
405
|
case method
|
|
285
406
|
when "cloneNode"
|
|
@@ -311,7 +432,7 @@ module Dommy
|
|
|
311
432
|
end
|
|
312
433
|
|
|
313
434
|
def value
|
|
314
|
-
@element.
|
|
435
|
+
@element.__dommy_backend_node__["class"].to_s
|
|
315
436
|
end
|
|
316
437
|
|
|
317
438
|
def value=(new_value)
|
|
@@ -383,6 +504,12 @@ module Dommy
|
|
|
383
504
|
nil
|
|
384
505
|
end
|
|
385
506
|
|
|
507
|
+
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
508
|
+
JS_METHOD_NAMES = %w[add remove contains toggle replace item].freeze
|
|
509
|
+
def __js_method_names__
|
|
510
|
+
JS_METHOD_NAMES
|
|
511
|
+
end
|
|
512
|
+
|
|
386
513
|
def __js_call__(method, args)
|
|
387
514
|
case method
|
|
388
515
|
when "add"
|
|
@@ -444,14 +571,14 @@ module Dommy
|
|
|
444
571
|
end
|
|
445
572
|
|
|
446
573
|
def class_tokens
|
|
447
|
-
raw = @element.
|
|
574
|
+
raw = @element.__dommy_backend_node__["class"].to_s
|
|
448
575
|
raw.split(/\s+/).reject(&:empty?)
|
|
449
576
|
end
|
|
450
577
|
|
|
451
578
|
def update_tokens
|
|
452
579
|
tokens = yield(class_tokens)
|
|
453
580
|
if tokens.empty?
|
|
454
|
-
@element.remove_attribute("class") if @element.
|
|
581
|
+
@element.remove_attribute("class") if @element.__dommy_backend_node__.key?("class")
|
|
455
582
|
else
|
|
456
583
|
@element.set_attribute("class", tokens.join(" "))
|
|
457
584
|
end
|
|
@@ -467,7 +594,7 @@ module Dommy
|
|
|
467
594
|
end
|
|
468
595
|
|
|
469
596
|
def __js_get__(key)
|
|
470
|
-
@element.
|
|
597
|
+
@element.__dommy_backend_node__[attr_name(key)]
|
|
471
598
|
end
|
|
472
599
|
|
|
473
600
|
def __js_set__(key, value)
|
|
@@ -491,6 +618,8 @@ module Dommy
|
|
|
491
618
|
# positioning sees zeroed values; absolute layout assertions need
|
|
492
619
|
# the real browser.
|
|
493
620
|
class DOMRect
|
|
621
|
+
attr_reader :x, :y, :width, :height
|
|
622
|
+
|
|
494
623
|
def initialize(x: 0, y: 0, width: 0, height: 0)
|
|
495
624
|
@x = x
|
|
496
625
|
@y = y
|
|
@@ -498,6 +627,22 @@ module Dommy
|
|
|
498
627
|
@height = height
|
|
499
628
|
end
|
|
500
629
|
|
|
630
|
+
def top
|
|
631
|
+
@y
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
def left
|
|
635
|
+
@x
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
def right
|
|
639
|
+
@x + @width
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
def bottom
|
|
643
|
+
@y + @height
|
|
644
|
+
end
|
|
645
|
+
|
|
501
646
|
def __js_get__(key)
|
|
502
647
|
case key
|
|
503
648
|
when "x", "left"
|
|
@@ -611,6 +756,12 @@ module Dommy
|
|
|
611
756
|
nil
|
|
612
757
|
end
|
|
613
758
|
|
|
759
|
+
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
760
|
+
JS_METHOD_NAMES = %w[setProperty removeProperty getPropertyValue item].freeze
|
|
761
|
+
def __js_method_names__
|
|
762
|
+
JS_METHOD_NAMES
|
|
763
|
+
end
|
|
764
|
+
|
|
614
765
|
def __js_call__(method, args)
|
|
615
766
|
case method
|
|
616
767
|
when "setProperty"
|
|
@@ -656,7 +807,7 @@ module Dommy
|
|
|
656
807
|
end
|
|
657
808
|
|
|
658
809
|
def properties
|
|
659
|
-
raw = @element.
|
|
810
|
+
raw = @element.__dommy_backend_node__["style"].to_s
|
|
660
811
|
raw.split(";").each_with_object({}) do |entry, out|
|
|
661
812
|
key, value = entry.split(":", 2)
|
|
662
813
|
next unless key && value
|
|
@@ -667,7 +818,7 @@ module Dommy
|
|
|
667
818
|
|
|
668
819
|
def write_properties(props)
|
|
669
820
|
if props.empty?
|
|
670
|
-
@element.remove_attribute("style") if @element.
|
|
821
|
+
@element.remove_attribute("style") if @element.__dommy_backend_node__.key?("style")
|
|
671
822
|
else
|
|
672
823
|
@element.set_attribute("style", props.map { |k, v| "#{k}:#{v}" }.join(";"))
|
|
673
824
|
end
|
|
@@ -678,7 +829,9 @@ module Dommy
|
|
|
678
829
|
include EventTarget
|
|
679
830
|
include Node
|
|
680
831
|
|
|
681
|
-
attr_reader :
|
|
832
|
+
attr_reader :document
|
|
833
|
+
|
|
834
|
+
def __dommy_backend_node__ = @__node__
|
|
682
835
|
|
|
683
836
|
def initialize(document, nokogiri_node)
|
|
684
837
|
@document = document
|
|
@@ -842,7 +995,7 @@ module Dommy
|
|
|
842
995
|
parent = @__node__.parent
|
|
843
996
|
return unless parent
|
|
844
997
|
|
|
845
|
-
if parent.is_a?(
|
|
998
|
+
if parent.is_a?(Backend.document_class)
|
|
846
999
|
raise(
|
|
847
1000
|
DOMException::NoModificationAllowedError,
|
|
848
1001
|
"outerHTML setter not allowed on the document element"
|
|
@@ -866,9 +1019,9 @@ module Dommy
|
|
|
866
1019
|
# `el.contains(other)` — true if `other` is `el` itself or any
|
|
867
1020
|
# descendant. Per spec, returns false for null/non-Node.
|
|
868
1021
|
def contains?(other)
|
|
869
|
-
return false unless other.respond_to?(:
|
|
1022
|
+
return false unless other.respond_to?(:__dommy_backend_node__)
|
|
870
1023
|
|
|
871
|
-
other_node = other.
|
|
1024
|
+
other_node = other.__dommy_backend_node__
|
|
872
1025
|
return true if other_node == @__node__
|
|
873
1026
|
|
|
874
1027
|
Internal::NodeTraversal.ancestor_of?(@__node__, other_node)
|
|
@@ -879,7 +1032,7 @@ module Dommy
|
|
|
879
1032
|
# inside a shadow tree, returns that ShadowRoot. Otherwise walks
|
|
880
1033
|
# until we hit the Nokogiri Document (then returns the Document).
|
|
881
1034
|
def root_node
|
|
882
|
-
sr = @document.
|
|
1035
|
+
sr = @document.__internal_shadow_root_containing__(@__node__)
|
|
883
1036
|
return sr if sr
|
|
884
1037
|
|
|
885
1038
|
current = @__node__
|
|
@@ -887,7 +1040,7 @@ module Dommy
|
|
|
887
1040
|
loop do
|
|
888
1041
|
parent = current.respond_to?(:parent) ? current.parent : nil
|
|
889
1042
|
break unless parent
|
|
890
|
-
if parent.is_a?(
|
|
1043
|
+
if parent.is_a?(Backend.document_class)
|
|
891
1044
|
attached = true
|
|
892
1045
|
break
|
|
893
1046
|
end
|
|
@@ -1036,14 +1189,14 @@ module Dommy
|
|
|
1036
1189
|
|
|
1037
1190
|
parent = current.respond_to?(:parent) ? current.parent : nil
|
|
1038
1191
|
return false unless parent
|
|
1039
|
-
return true if parent.is_a?(
|
|
1192
|
+
return true if parent.is_a?(Backend.document_class)
|
|
1040
1193
|
|
|
1041
|
-
sr = @document.
|
|
1194
|
+
sr = @document.__internal_shadow_root_for_fragment__(parent)
|
|
1042
1195
|
if sr
|
|
1043
1196
|
host = sr.host
|
|
1044
1197
|
return false unless host
|
|
1045
1198
|
|
|
1046
|
-
current = host.
|
|
1199
|
+
current = host.__dommy_backend_node__
|
|
1047
1200
|
else
|
|
1048
1201
|
current = parent
|
|
1049
1202
|
end
|
|
@@ -1056,12 +1209,12 @@ module Dommy
|
|
|
1056
1209
|
# tests rely on `document.activeElement` updating. Track the most
|
|
1057
1210
|
# recently focused element on the document.
|
|
1058
1211
|
def focus
|
|
1059
|
-
@document.
|
|
1212
|
+
@document.__internal_set_active_element__(self)
|
|
1060
1213
|
nil
|
|
1061
1214
|
end
|
|
1062
1215
|
|
|
1063
1216
|
def blur
|
|
1064
|
-
@document.
|
|
1217
|
+
@document.__internal_set_active_element__(nil)
|
|
1065
1218
|
nil
|
|
1066
1219
|
end
|
|
1067
1220
|
|
|
@@ -1131,7 +1284,7 @@ module Dommy
|
|
|
1131
1284
|
|
|
1132
1285
|
# Internal — gives access to the shadow root regardless of mode.
|
|
1133
1286
|
# Used by event composition / `composedPath()`.
|
|
1134
|
-
def
|
|
1287
|
+
def __internal_shadow_root__
|
|
1135
1288
|
@__shadow_root
|
|
1136
1289
|
end
|
|
1137
1290
|
|
|
@@ -1139,7 +1292,7 @@ module Dommy
|
|
|
1139
1292
|
# "beforebegin", "afterbegin", "beforeend", "afterend". Returns the
|
|
1140
1293
|
# inserted element or nil if position has no anchor (root cases).
|
|
1141
1294
|
def insert_adjacent_element(position, element)
|
|
1142
|
-
return nil unless element.respond_to?(:
|
|
1295
|
+
return nil unless element.respond_to?(:__dommy_backend_node__)
|
|
1143
1296
|
|
|
1144
1297
|
case position.to_s
|
|
1145
1298
|
when "beforebegin"
|
|
@@ -1238,10 +1391,10 @@ module Dommy
|
|
|
1238
1391
|
# nodes).
|
|
1239
1392
|
def compare_document_position(other)
|
|
1240
1393
|
return 0 if equal?(other)
|
|
1241
|
-
return DOCUMENT_POSITION_DISCONNECTED unless other.respond_to?(:
|
|
1394
|
+
return DOCUMENT_POSITION_DISCONNECTED unless other.respond_to?(:__dommy_backend_node__)
|
|
1242
1395
|
|
|
1243
1396
|
self_node = @__node__
|
|
1244
|
-
other_node = other.
|
|
1397
|
+
other_node = other.__dommy_backend_node__
|
|
1245
1398
|
|
|
1246
1399
|
self_ancestors = ancestor_chain(self_node)
|
|
1247
1400
|
other_ancestors = ancestor_chain(other_node)
|
|
@@ -1291,11 +1444,11 @@ module Dommy
|
|
|
1291
1444
|
# suite and standard DOM Node.isEqualNode.
|
|
1292
1445
|
def equal_node?(other)
|
|
1293
1446
|
return false unless other.is_a?(Element)
|
|
1294
|
-
return false unless @__node__.name == other.
|
|
1447
|
+
return false unless @__node__.name == other.__dommy_backend_node__.name
|
|
1295
1448
|
return false unless attribute_signature == other.send(:attribute_signature)
|
|
1296
|
-
return false unless @__node__.children.size == other.
|
|
1449
|
+
return false unless @__node__.children.size == other.__dommy_backend_node__.children.size
|
|
1297
1450
|
|
|
1298
|
-
@__node__.children.zip(other.
|
|
1451
|
+
@__node__.children.zip(other.__dommy_backend_node__.children).all? do |a, b|
|
|
1299
1452
|
wa = @document.wrap_node(a)
|
|
1300
1453
|
wb = @document.wrap_node(b)
|
|
1301
1454
|
wa.respond_to?(:equal_node?) ? wa.equal_node?(wb) : a.content == b.content
|
|
@@ -1404,6 +1557,26 @@ module Dommy
|
|
|
1404
1557
|
1
|
|
1405
1558
|
when "isConnected"
|
|
1406
1559
|
is_connected?
|
|
1560
|
+
when
|
|
1561
|
+
"scrollTop",
|
|
1562
|
+
"scrollLeft",
|
|
1563
|
+
"scrollWidth",
|
|
1564
|
+
"scrollHeight",
|
|
1565
|
+
"clientWidth",
|
|
1566
|
+
"clientHeight",
|
|
1567
|
+
"clientTop",
|
|
1568
|
+
"clientLeft",
|
|
1569
|
+
"offsetWidth",
|
|
1570
|
+
"offsetHeight",
|
|
1571
|
+
"offsetTop",
|
|
1572
|
+
"offsetLeft"
|
|
1573
|
+
# No layout engine — zeroed values match what real browsers
|
|
1574
|
+
# report for hidden / pre-paint elements.
|
|
1575
|
+
0
|
|
1576
|
+
when "offsetParent"
|
|
1577
|
+
nil
|
|
1578
|
+
when "popover"
|
|
1579
|
+
get_attribute("popover")
|
|
1407
1580
|
when "children"
|
|
1408
1581
|
@live_children
|
|
1409
1582
|
when "firstElementChild"
|
|
@@ -1503,7 +1676,20 @@ module Dommy
|
|
|
1503
1676
|
def __js_set__(key, value)
|
|
1504
1677
|
case key
|
|
1505
1678
|
when "textContent"
|
|
1679
|
+
# `node.content =` removes all existing children and (if
|
|
1680
|
+
# value is non-empty) appends a single text node. Capture
|
|
1681
|
+
# before/after to feed MutationObserver — mirrors the
|
|
1682
|
+
# innerHTML branch below.
|
|
1683
|
+
removed = @__node__.children.to_a
|
|
1506
1684
|
@__node__.content = value.to_s
|
|
1685
|
+
added = @__node__.children.to_a
|
|
1686
|
+
if removed.any? || added.any?
|
|
1687
|
+
@document.notify_child_list_mutation(
|
|
1688
|
+
target_node: @__node__,
|
|
1689
|
+
added_nodes: added,
|
|
1690
|
+
removed_nodes: removed
|
|
1691
|
+
)
|
|
1692
|
+
end
|
|
1507
1693
|
when "innerHTML"
|
|
1508
1694
|
removed = @__node__.children.to_a
|
|
1509
1695
|
if @__node__.name == "template"
|
|
@@ -1573,6 +1759,21 @@ module Dommy
|
|
|
1573
1759
|
|
|
1574
1760
|
public
|
|
1575
1761
|
|
|
1762
|
+
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
1763
|
+
JS_METHOD_NAMES = %w[
|
|
1764
|
+
getAttribute setAttribute hasAttribute removeAttribute getAttributeNames closest
|
|
1765
|
+
querySelector querySelectorAll getElementsByClassName getElementsByTagName
|
|
1766
|
+
insertAdjacentElement insertAdjacentHTML insertAdjacentText toggleAttribute matches
|
|
1767
|
+
toString getAttributeNode setAttributeNode removeAttributeNode focus blur attachShadow
|
|
1768
|
+
addEventListener removeEventListener dispatchEvent appendChild insertBefore removeChild
|
|
1769
|
+
replaceChild cloneNode append prepend replaceChildren before after getInnerHTML getHTML
|
|
1770
|
+
remove replaceWith click getBoundingClientRect getClientRects scrollIntoView scroll
|
|
1771
|
+
scrollTo scrollBy requestFullscreen showPopover hidePopover togglePopover
|
|
1772
|
+
].freeze
|
|
1773
|
+
def __js_method_names__
|
|
1774
|
+
JS_METHOD_NAMES
|
|
1775
|
+
end
|
|
1776
|
+
|
|
1576
1777
|
def __js_call__(method, args)
|
|
1577
1778
|
case method
|
|
1578
1779
|
when "getAttribute"
|
|
@@ -1658,6 +1859,26 @@ module Dommy
|
|
|
1658
1859
|
dispatch_event(MouseEvent.new("click", "bubbles" => true, "cancelable" => true, "button" => 0))
|
|
1659
1860
|
when "getBoundingClientRect"
|
|
1660
1861
|
DOMRect.new
|
|
1862
|
+
when "getClientRects"
|
|
1863
|
+
[]
|
|
1864
|
+
when "scrollIntoView", "scroll", "scrollTo", "scrollBy"
|
|
1865
|
+
# No layout — record the request for tests to assert against.
|
|
1866
|
+
@scroll_log ||= []
|
|
1867
|
+
@scroll_log << [method, args]
|
|
1868
|
+
nil
|
|
1869
|
+
when "requestFullscreen"
|
|
1870
|
+
@document.__internal_set_fullscreen_element__(self)
|
|
1871
|
+
PromiseValue.resolve(@document.default_view, nil)
|
|
1872
|
+
when "showPopover"
|
|
1873
|
+
toggle_popover_state(true)
|
|
1874
|
+
nil
|
|
1875
|
+
when "hidePopover"
|
|
1876
|
+
toggle_popover_state(false)
|
|
1877
|
+
nil
|
|
1878
|
+
when "togglePopover"
|
|
1879
|
+
new_state = !@__popover_open__
|
|
1880
|
+
toggle_popover_state(new_state)
|
|
1881
|
+
new_state
|
|
1661
1882
|
else
|
|
1662
1883
|
nil
|
|
1663
1884
|
end
|
|
@@ -1665,6 +1886,11 @@ module Dommy
|
|
|
1665
1886
|
|
|
1666
1887
|
private
|
|
1667
1888
|
|
|
1889
|
+
def normalize_attr_key(name)
|
|
1890
|
+
s = name.to_s
|
|
1891
|
+
case_sensitive_attribute_names? ? s : s.downcase
|
|
1892
|
+
end
|
|
1893
|
+
|
|
1668
1894
|
def element_children
|
|
1669
1895
|
@__node__.element_children.each_with_object([]) do |node, out|
|
|
1670
1896
|
wrapped = @document.wrap_node(node)
|
|
@@ -1676,14 +1902,14 @@ module Dommy
|
|
|
1676
1902
|
@document.wrap_node(node)
|
|
1677
1903
|
end
|
|
1678
1904
|
|
|
1679
|
-
def
|
|
1905
|
+
def __internal_event_parent__
|
|
1680
1906
|
parent_node = @__node__.parent
|
|
1681
1907
|
# If our Nokogiri parent is a shadow tree's backing fragment,
|
|
1682
1908
|
# the bubble path's next stop is the ShadowRoot itself — not
|
|
1683
|
-
# the bare Fragment wrapper. The ShadowRoot's
|
|
1909
|
+
# the bare Fragment wrapper. The ShadowRoot's __internal_event_parent__
|
|
1684
1910
|
# will return nil (composed events route to host explicitly).
|
|
1685
|
-
if parent_node.is_a?(
|
|
1686
|
-
sr = @document.
|
|
1911
|
+
if parent_node.is_a?(Backend.document_fragment_class)
|
|
1912
|
+
sr = @document.__internal_shadow_root_for_fragment__(parent_node)
|
|
1687
1913
|
return sr if sr
|
|
1688
1914
|
end
|
|
1689
1915
|
|
|
@@ -1697,20 +1923,27 @@ module Dommy
|
|
|
1697
1923
|
@document.template_content_fragment(self)
|
|
1698
1924
|
end
|
|
1699
1925
|
|
|
1700
|
-
#
|
|
1701
|
-
#
|
|
1702
|
-
#
|
|
1703
|
-
#
|
|
1926
|
+
# Attribute name handling depends on the element's namespace:
|
|
1927
|
+
# - HTML: case-insensitive (browser DOM stores everything lowercase).
|
|
1928
|
+
# - SVG / other XML: case-sensitive (`viewBox` ≠ `viewbox`).
|
|
1929
|
+
# Subclasses with a known namespace override `case_sensitive_attribute_names?`
|
|
1930
|
+
# to flip the behavior. Generic Element nodes inspect the namespace
|
|
1931
|
+
# URI directly.
|
|
1932
|
+
def case_sensitive_attribute_names?
|
|
1933
|
+
ns = namespace_uri
|
|
1934
|
+
!ns.nil? && ns != "http://www.w3.org/1999/xhtml"
|
|
1935
|
+
end
|
|
1936
|
+
|
|
1704
1937
|
def get_attribute(name)
|
|
1705
1938
|
return nil if name.nil?
|
|
1706
1939
|
|
|
1707
|
-
@__node__[name
|
|
1940
|
+
@__node__[normalize_attr_key(name)]
|
|
1708
1941
|
end
|
|
1709
1942
|
|
|
1710
1943
|
def set_attribute(name, value)
|
|
1711
1944
|
return nil if name.nil?
|
|
1712
1945
|
|
|
1713
|
-
key = name
|
|
1946
|
+
key = normalize_attr_key(name)
|
|
1714
1947
|
old = @__node__[key]
|
|
1715
1948
|
@__node__[key] = value.to_s
|
|
1716
1949
|
@document.notify_attribute_mutation(target_node: @__node__, attribute_name: key, old_value: old)
|
|
@@ -1720,13 +1953,13 @@ module Dommy
|
|
|
1720
1953
|
def has_attribute?(name)
|
|
1721
1954
|
return false if name.nil?
|
|
1722
1955
|
|
|
1723
|
-
@__node__.key?(name
|
|
1956
|
+
@__node__.key?(normalize_attr_key(name))
|
|
1724
1957
|
end
|
|
1725
1958
|
|
|
1726
1959
|
def remove_attribute(name)
|
|
1727
1960
|
return nil if name.nil?
|
|
1728
1961
|
|
|
1729
|
-
key = name
|
|
1962
|
+
key = normalize_attr_key(name)
|
|
1730
1963
|
return nil unless @__node__.key?(key)
|
|
1731
1964
|
|
|
1732
1965
|
old = @__node__[key]
|
|
@@ -1748,16 +1981,51 @@ module Dommy
|
|
|
1748
1981
|
nil
|
|
1749
1982
|
end
|
|
1750
1983
|
|
|
1984
|
+
# Web Animations: start an animation on this element.
|
|
1985
|
+
# Returns the new Animation. Dommy doesn't interpolate; the
|
|
1986
|
+
# animation simply transitions through the `playState` lifecycle,
|
|
1987
|
+
# finishing via `scheduler.advance_time(duration)` or an
|
|
1988
|
+
# explicit `animation.finish`.
|
|
1989
|
+
def animate(keyframes, options = nil)
|
|
1990
|
+
effect = KeyframeEffect.new(self, keyframes, options)
|
|
1991
|
+
animation = Animation.new(effect, nil, window: @document.default_view)
|
|
1992
|
+
@__animations ||= []
|
|
1993
|
+
@__animations << animation
|
|
1994
|
+
animation.play
|
|
1995
|
+
animation
|
|
1996
|
+
end
|
|
1997
|
+
|
|
1998
|
+
def get_animations(_options = nil)
|
|
1999
|
+
(@__animations ||= []).dup
|
|
2000
|
+
end
|
|
2001
|
+
|
|
2002
|
+
alias getAnimations get_animations
|
|
2003
|
+
|
|
1751
2004
|
def query_selector(selector)
|
|
1752
2005
|
return nil if selector.nil? || selector.to_s.empty?
|
|
1753
2006
|
|
|
1754
|
-
@document.wrap_node(@__node__.at_css(selector.to_s))
|
|
2007
|
+
@document.wrap_node(@__node__.at_css(selector.to_s, Internal::CSS_PSEUDO_HANDLERS))
|
|
1755
2008
|
end
|
|
1756
2009
|
|
|
1757
2010
|
def query_selector_all(selector)
|
|
1758
2011
|
return NodeList.new if selector.nil? || selector.to_s.empty?
|
|
1759
2012
|
|
|
1760
|
-
NodeList.new(@__node__.css(selector.to_s).map { |node| @document.wrap_node(node) }.compact)
|
|
2013
|
+
NodeList.new(@__node__.css(selector.to_s, Internal::CSS_PSEUDO_HANDLERS).map { |node| @document.wrap_node(node) }.compact)
|
|
2014
|
+
end
|
|
2015
|
+
|
|
2016
|
+
# XPath queries scoped to this element, returning wrapped nodes.
|
|
2017
|
+
def at_xpath(expression)
|
|
2018
|
+
node = @__node__.at_xpath(expression)
|
|
2019
|
+
node && @document.wrap_node(node)
|
|
2020
|
+
end
|
|
2021
|
+
|
|
2022
|
+
def xpath(expression)
|
|
2023
|
+
@__node__.xpath(expression).map { |node| @document.wrap_node(node) }
|
|
2024
|
+
end
|
|
2025
|
+
|
|
2026
|
+
# The XPath string locating this element in its document.
|
|
2027
|
+
def path
|
|
2028
|
+
@__node__.path
|
|
1761
2029
|
end
|
|
1762
2030
|
|
|
1763
2031
|
def append_child(child)
|
|
@@ -1903,10 +2171,10 @@ module Dommy
|
|
|
1903
2171
|
# produce a cycle (inserting an ancestor as a descendant of
|
|
1904
2172
|
# itself). Strings and Fragments are always safe.
|
|
1905
2173
|
def check_hierarchy!(child)
|
|
1906
|
-
return unless child.respond_to?(:
|
|
2174
|
+
return unless child.respond_to?(:__dommy_backend_node__)
|
|
1907
2175
|
|
|
1908
|
-
node = child.
|
|
1909
|
-
return unless node.is_a?(
|
|
2176
|
+
node = child.__dommy_backend_node__
|
|
2177
|
+
return unless node.is_a?(Backend.node_class)
|
|
1910
2178
|
|
|
1911
2179
|
if node == @__node__ || @__node__.ancestors.any? { |a| a == node }
|
|
1912
2180
|
raise(
|
|
@@ -1923,13 +2191,13 @@ module Dommy
|
|
|
1923
2191
|
def detach_dom_nodes(value)
|
|
1924
2192
|
case value
|
|
1925
2193
|
when Element, TextNode, CommentNode
|
|
1926
|
-
node = value.
|
|
2194
|
+
node = value.__dommy_backend_node__
|
|
1927
2195
|
node.unlink if node.parent
|
|
1928
2196
|
[node]
|
|
1929
2197
|
when Fragment
|
|
1930
2198
|
value.extract_children
|
|
1931
2199
|
when String
|
|
1932
|
-
[@document.create_text_node(value).
|
|
2200
|
+
[@document.create_text_node(value).__dommy_backend_node__]
|
|
1933
2201
|
else
|
|
1934
2202
|
node = unwrap_dom_node(value)
|
|
1935
2203
|
return [] unless node
|
|
@@ -1940,7 +2208,7 @@ module Dommy
|
|
|
1940
2208
|
end
|
|
1941
2209
|
|
|
1942
2210
|
def unwrap_dom_node(value)
|
|
1943
|
-
return value.
|
|
2211
|
+
return value.__dommy_backend_node__ if value.respond_to?(:__dommy_backend_node__)
|
|
1944
2212
|
|
|
1945
2213
|
nil
|
|
1946
2214
|
end
|
|
@@ -1953,6 +2221,35 @@ module Dommy
|
|
|
1953
2221
|
end
|
|
1954
2222
|
end
|
|
1955
2223
|
|
|
2224
|
+
# Popover state — modern HTML pattern. `show`/`hide`/`toggle`
|
|
2225
|
+
# fire `beforetoggle` and `toggle` events (no real visual change).
|
|
2226
|
+
def toggle_popover_state(open)
|
|
2227
|
+
old_state = @__popover_open__ ? "open" : "closed"
|
|
2228
|
+
new_state = open ? "open" : "closed"
|
|
2229
|
+
return if old_state == new_state
|
|
2230
|
+
|
|
2231
|
+
dispatch_event(
|
|
2232
|
+
CustomEvent.new(
|
|
2233
|
+
"beforetoggle",
|
|
2234
|
+
"detail" => {"oldState" => old_state, "newState" => new_state}
|
|
2235
|
+
)
|
|
2236
|
+
)
|
|
2237
|
+
@__popover_open__ = open
|
|
2238
|
+
dispatch_event(
|
|
2239
|
+
CustomEvent.new(
|
|
2240
|
+
"toggle",
|
|
2241
|
+
"detail" => {"oldState" => old_state, "newState" => new_state}
|
|
2242
|
+
)
|
|
2243
|
+
)
|
|
2244
|
+
end
|
|
2245
|
+
|
|
2246
|
+
# Test inspector for scroll calls (no real layout to scroll).
|
|
2247
|
+
def __test_scroll_log__
|
|
2248
|
+
@scroll_log ||= []
|
|
2249
|
+
end
|
|
2250
|
+
|
|
2251
|
+
public :__test_scroll_log__
|
|
2252
|
+
|
|
1956
2253
|
# Re-expose snake_case methods that the JS bridge dispatch routes
|
|
1957
2254
|
# to. Defined as private originally so internal helpers (element_children,
|
|
1958
2255
|
# detach_dom_nodes, etc.) stay encapsulated; CRuby users call these
|
|
@@ -1969,7 +2266,13 @@ module Dommy
|
|
|
1969
2266
|
:clone_node,
|
|
1970
2267
|
:query_selector,
|
|
1971
2268
|
:query_selector_all,
|
|
1972
|
-
:
|
|
2269
|
+
:at_xpath,
|
|
2270
|
+
:xpath,
|
|
2271
|
+
:path,
|
|
2272
|
+
:closest,
|
|
2273
|
+
:animate,
|
|
2274
|
+
:get_animations,
|
|
2275
|
+
:getAnimations
|
|
1973
2276
|
)
|
|
1974
2277
|
end
|
|
1975
2278
|
end
|