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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -13
  3. data/lib/dommy/animation.rb +288 -0
  4. data/lib/dommy/attr.rb +23 -11
  5. data/lib/dommy/backend/nokogiri_adapter.rb +51 -0
  6. data/lib/dommy/backend/nokolexbor_adapter.rb +80 -0
  7. data/lib/dommy/backend.rb +129 -0
  8. data/lib/dommy/blob.rb +2 -2
  9. data/lib/dommy/compression_streams.rb +147 -0
  10. data/lib/dommy/cookie_store.rb +128 -0
  11. data/lib/dommy/crypto.rb +396 -0
  12. data/lib/dommy/css.rb +7 -7
  13. data/lib/dommy/custom_elements.rb +6 -6
  14. data/lib/dommy/document.rb +190 -32
  15. data/lib/dommy/dom_parser.rb +5 -4
  16. data/lib/dommy/element.rb +356 -53
  17. data/lib/dommy/event.rb +431 -25
  18. data/lib/dommy/event_source.rb +131 -0
  19. data/lib/dommy/fetch.rb +76 -6
  20. data/lib/dommy/file_reader.rb +176 -0
  21. data/lib/dommy/form_data.rb +1 -3
  22. data/lib/dommy/history.rb +82 -0
  23. data/lib/dommy/html_collection.rb +4 -4
  24. data/lib/dommy/html_elements.rb +130 -67
  25. data/lib/dommy/internal/cookie_jar.rb +2 -0
  26. data/lib/dommy/internal/css_pseudo_handlers.rb +28 -0
  27. data/lib/dommy/internal/dom_matching.rb +4 -4
  28. data/lib/dommy/internal/idna.rb +443 -0
  29. data/lib/dommy/internal/idna_data.rb +10379 -0
  30. data/lib/dommy/internal/ipv4_parser.rb +78 -0
  31. data/lib/dommy/internal/node_traversal.rb +1 -1
  32. data/lib/dommy/internal/node_wrapper_cache.rb +23 -12
  33. data/lib/dommy/internal/observable_callback.rb +25 -0
  34. data/lib/dommy/internal/punycode.rb +202 -0
  35. data/lib/dommy/internal/range_text_serializer.rb +72 -0
  36. data/lib/dommy/internal/reflected_attributes.rb +45 -0
  37. data/lib/dommy/internal/template_content_registry.rb +6 -6
  38. data/lib/dommy/intersection_observer.rb +82 -0
  39. data/lib/dommy/{router.rb → location.rb} +8 -142
  40. data/lib/dommy/media_query_list.rb +118 -0
  41. data/lib/dommy/message_channel.rb +249 -0
  42. data/lib/dommy/{observer.rb → mutation_observer.rb} +21 -11
  43. data/lib/dommy/navigator.rb +365 -5
  44. data/lib/dommy/node.rb +12 -0
  45. data/lib/dommy/notification.rb +89 -0
  46. data/lib/dommy/parser.rb +13 -13
  47. data/lib/dommy/performance.rb +146 -0
  48. data/lib/dommy/performance_observer.rb +55 -0
  49. data/lib/dommy/range.rb +597 -0
  50. data/lib/dommy/resize_observer.rb +53 -0
  51. data/lib/dommy/shadow_root.rb +10 -8
  52. data/lib/dommy/streams.rb +386 -0
  53. data/lib/dommy/svg_elements.rb +3863 -0
  54. data/lib/dommy/text_codec.rb +175 -0
  55. data/lib/dommy/tree_walker.rb +21 -21
  56. data/lib/dommy/url.rb +274 -29
  57. data/lib/dommy/url_pattern.rb +144 -0
  58. data/lib/dommy/version.rb +1 -1
  59. data/lib/dommy/web_socket.rb +209 -0
  60. data/lib/dommy/window.rb +369 -0
  61. data/lib/dommy/worker.rb +143 -0
  62. data/lib/dommy/xml_http_request.rb +438 -0
  63. data/lib/dommy.rb +43 -5
  64. metadata +44 -29
  65. data/lib/dommy/world.rb +0 -209
@@ -1,30 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dommy
4
- # Base for specialized HTMLElement subclasses. Adds the reflected
5
- # IDL boolean / string attribute helpers each subclass uses.
4
+ # Base for specialized HTMLElement subclasses. Inherits reflection
5
+ # helpers from Internal::ReflectedAttributes (also shared with
6
+ # SVGElement).
6
7
  class HTMLElement < Element
7
- private
8
-
9
- def reflected_boolean(name)
10
- @__node__.key?(name.to_s.downcase)
11
- end
12
-
13
- def set_reflected_boolean(name, value)
14
- key = name.to_s.downcase
15
- if value
16
- set_attribute(key, "")
17
- elsif @__node__.key?(key)
18
- remove_attribute(key)
19
- end
20
- end
21
-
22
- def reflected_string(name)
23
- @__node__[name.to_s.downcase].to_s
24
- end
25
-
26
- def set_reflected_string(name, value)
27
- set_attribute(name.to_s.downcase, value.to_s)
8
+ include Internal::ReflectedAttributes
9
+
10
+ # HTML attribute names are case-insensitive — the browser DOM
11
+ # lowercases everything. Override to make this explicit at the
12
+ # HTMLElement level (Element's default would already pick this up
13
+ # via namespace inspection, but spelling it out shortcuts the
14
+ # namespace check for HTML's hot path).
15
+ def case_sensitive_attribute_names?
16
+ false
28
17
  end
29
18
  end
30
19
 
@@ -165,6 +154,12 @@ module Dommy
165
154
  # `<form>` — element collection, submit/reset, and a stubbed
166
155
  # validation surface.
167
156
  class HTMLFormElement < HTMLElement
157
+ # Own __js_call__ methods, on top of Element's.
158
+ JS_METHOD_NAMES = %w[submit reset requestSubmit checkValidity reportValidity].freeze
159
+ def __js_method_names__
160
+ super + JS_METHOD_NAMES
161
+ end
162
+
168
163
  def name
169
164
  reflected_string("name")
170
165
  end
@@ -222,7 +217,7 @@ module Dommy
222
217
  el = self
223
218
  HTMLCollection.new do
224
219
  el
225
- .__node__
220
+ .__dommy_backend_node__
226
221
  .css("input, select, textarea, button, output, fieldset")
227
222
  .map do |n|
228
223
  el.document.wrap_node(n)
@@ -256,7 +251,7 @@ module Dommy
256
251
  # `submitter` (if given) must be a button inside this form.
257
252
  def request_submit(submitter = nil)
258
253
  if submitter
259
- unless submitter.respond_to?(:__node__) && submitter.__node__.ancestors.include?(@__node__)
254
+ unless submitter.respond_to?(:__dommy_backend_node__) && submitter.__dommy_backend_node__.ancestors.include?(@__node__)
260
255
  raise DOMException::NotFoundError, "submitter is not a descendant of this form"
261
256
  end
262
257
 
@@ -357,6 +352,15 @@ module Dommy
357
352
 
358
353
  # `<input>` — covers the most-used form control surface.
359
354
  class HTMLInputElement < HTMLElement
355
+ # Own __js_call__ methods, on top of Element's.
356
+ JS_METHOD_NAMES = %w[
357
+ select setSelectionRange setRangeText stepUp stepDown
358
+ checkValidity reportValidity setCustomValidity
359
+ ].freeze
360
+ def __js_method_names__
361
+ super + JS_METHOD_NAMES
362
+ end
363
+
360
364
  def type
361
365
  raw = @__node__["type"].to_s
362
366
  raw.empty? ? "text" : raw.downcase
@@ -433,14 +437,14 @@ module Dommy
433
437
  end
434
438
 
435
439
  # `files` — for `<input type="file">`. Browsers populate this via
436
- # user interaction; in tests, code uses `__set_files__` to seed it.
440
+ # user interaction; in tests, code uses `__driver_set_files__` to seed it.
437
441
  def files
438
442
  @__files ||= FileList.new
439
443
  end
440
444
 
441
445
  # Test-only seam: set the input's file list directly.
442
446
  # Accepts an array (wrapped in a FileList) or a FileList itself.
443
- def __set_files__(files_input)
447
+ def __driver_set_files__(files_input)
444
448
  @__files = files_input.is_a?(FileList) ? files_input : FileList.new(Array(files_input))
445
449
  end
446
450
 
@@ -1392,13 +1396,13 @@ module Dommy
1392
1396
  def host_attr_value(name)
1393
1397
  return "" unless @host
1394
1398
 
1395
- @host.__node__[name].to_s
1399
+ @host.__dommy_backend_node__[name].to_s
1396
1400
  end
1397
1401
 
1398
1402
  def host_attr_present?(name)
1399
1403
  return false unless @host
1400
1404
 
1401
- @host.__node__.key?(name.to_s)
1405
+ @host.__dommy_backend_node__.key?(name.to_s)
1402
1406
  end
1403
1407
 
1404
1408
  def host_type
@@ -1493,7 +1497,7 @@ module Dommy
1493
1497
  sel = closest("select")
1494
1498
  return 0 unless sel
1495
1499
 
1496
- sel.options.find_index { |o| o.__node__ == @__node__ } || 0
1500
+ sel.options.find_index { |o| o.__dommy_backend_node__ == @__node__ } || 0
1497
1501
  end
1498
1502
 
1499
1503
  def __js_get__(key)
@@ -1580,6 +1584,14 @@ module Dommy
1580
1584
 
1581
1585
  # `<textarea>` — multi-line text input.
1582
1586
  class HTMLTextAreaElement < HTMLElement
1587
+ # Own __js_call__ methods, on top of Element's.
1588
+ JS_METHOD_NAMES = %w[
1589
+ select setSelectionRange setRangeText checkValidity reportValidity setCustomValidity
1590
+ ].freeze
1591
+ def __js_method_names__
1592
+ super + JS_METHOD_NAMES
1593
+ end
1594
+
1583
1595
  def value
1584
1596
  @__node__["value"] || text_content
1585
1597
  end
@@ -1876,7 +1888,7 @@ module Dommy
1876
1888
  el = self
1877
1889
  HTMLCollection.new do
1878
1890
  el
1879
- .__node__
1891
+ .__dommy_backend_node__
1880
1892
  .css("input, select, textarea, button, output, fieldset")
1881
1893
  .map do |n|
1882
1894
  el.document.wrap_node(n)
@@ -2042,6 +2054,12 @@ module Dommy
2042
2054
  # `slot` attribute go to the unnamed default slot. If nothing is
2043
2055
  # assigned, the slot's own children render as fallback content.
2044
2056
  class HTMLSlotElement < HTMLElement
2057
+ # Own __js_call__ methods, on top of Element's.
2058
+ JS_METHOD_NAMES = %w[assignedNodes assignedElements assign].freeze
2059
+ def __js_method_names__
2060
+ super + JS_METHOD_NAMES
2061
+ end
2062
+
2045
2063
  def name
2046
2064
  reflected_string("name")
2047
2065
  end
@@ -2073,7 +2091,7 @@ module Dommy
2073
2091
  # call and fire `slotchange` in both modes; named mode simply
2074
2092
  # ignores the override.
2075
2093
  def assign(*nodes)
2076
- @__manual_assignment = nodes.flatten.select { |n| n.respond_to?(:__node__) }
2094
+ @__manual_assignment = nodes.flatten.select { |n| n.respond_to?(:__dommy_backend_node__) }
2077
2095
  dispatch_event(Event.new("slotchange", "bubbles" => true))
2078
2096
  nil
2079
2097
  end
@@ -2116,7 +2134,7 @@ module Dommy
2116
2134
  private
2117
2135
 
2118
2136
  def matching_light_nodes
2119
- sr = @document.__shadow_root_containing__(@__node__)
2137
+ sr = @document.__internal_shadow_root_containing__(@__node__)
2120
2138
  return [] unless sr
2121
2139
 
2122
2140
  host = sr.host
@@ -2129,7 +2147,7 @@ module Dommy
2129
2147
  end
2130
2148
 
2131
2149
  host
2132
- .__node__
2150
+ .__dommy_backend_node__
2133
2151
  .children
2134
2152
  .map do |child|
2135
2153
  wrapped = @document.wrap_node(child)
@@ -2150,6 +2168,12 @@ module Dommy
2150
2168
  # `selectedIndex`, and dispatches change events. Minimal compared to
2151
2169
  # happy-dom's full HTMLSelectElement, but covers common test cases.
2152
2170
  class HTMLSelectElement < HTMLElement
2171
+ # Own __js_call__ methods, on top of Element's.
2172
+ JS_METHOD_NAMES = %w[item add checkValidity reportValidity setCustomValidity].freeze
2173
+ def __js_method_names__
2174
+ super + JS_METHOD_NAMES
2175
+ end
2176
+
2153
2177
  def name
2154
2178
  reflected_string("name")
2155
2179
  end
@@ -2176,7 +2200,7 @@ module Dommy
2176
2200
  def options
2177
2201
  el = self
2178
2202
  HTMLOptionsCollection.new(self) do
2179
- el.__node__.css("option").map { |n| el.document.wrap_node(n) }.compact
2203
+ el.__dommy_backend_node__.css("option").map { |n| el.document.wrap_node(n) }.compact
2180
2204
  end
2181
2205
  end
2182
2206
 
@@ -2186,8 +2210,8 @@ module Dommy
2186
2210
  def selected_options
2187
2211
  el = self
2188
2212
  HTMLCollection.new do
2189
- opts = el.__node__.css("option").map { |n| el.document.wrap_node(n) }.compact
2190
- chosen = opts.select { |o| o.__node__.key?("selected") }
2213
+ opts = el.__dommy_backend_node__.css("option").map { |n| el.document.wrap_node(n) }.compact
2214
+ chosen = opts.select { |o| o.__dommy_backend_node__.key?("selected") }
2191
2215
  next chosen unless chosen.empty?
2192
2216
  next [] if el.multiple
2193
2217
 
@@ -2207,7 +2231,7 @@ module Dommy
2207
2231
  # not multiple, or -1 if multiple and none.
2208
2232
  def selected_index
2209
2233
  opts = options
2210
- idx = opts.find_index { |o| o.__node__.key?("selected") }
2234
+ idx = opts.find_index { |o| o.__dommy_backend_node__.key?("selected") }
2211
2235
  return idx if idx
2212
2236
 
2213
2237
  multiple ? -1 : (opts.empty? ? -1 : 0)
@@ -2218,7 +2242,7 @@ module Dommy
2218
2242
  opts.each_with_index do |o, idx|
2219
2243
  if idx == i.to_i
2220
2244
  o.set_attribute("selected", "")
2221
- elsif o.__node__.key?("selected")
2245
+ elsif o.__dommy_backend_node__.key?("selected")
2222
2246
  o.remove_attribute("selected")
2223
2247
  end
2224
2248
  end
@@ -2227,15 +2251,15 @@ module Dommy
2227
2251
  # `value` of the select = value of the selected option, or "".
2228
2252
  def value
2229
2253
  opts = options
2230
- sel = opts.find { |o| o.__node__.key?("selected") } || opts.first
2231
- sel ? (sel.__node__["value"] || sel.text_content).to_s : ""
2254
+ sel = opts.find { |o| o.__dommy_backend_node__.key?("selected") } || opts.first
2255
+ sel ? (sel.__dommy_backend_node__["value"] || sel.text_content).to_s : ""
2232
2256
  end
2233
2257
 
2234
2258
  def value=(new_value)
2235
- target = options.find { |o| (o.__node__["value"] || o.text_content).to_s == new_value.to_s }
2259
+ target = options.find { |o| (o.__dommy_backend_node__["value"] || o.text_content).to_s == new_value.to_s }
2236
2260
  return unless target
2237
2261
 
2238
- options.each { |o| o.remove_attribute("selected") if o.__node__.key?("selected") }
2262
+ options.each { |o| o.remove_attribute("selected") if o.__dommy_backend_node__.key?("selected") }
2239
2263
  target.set_attribute("selected", "")
2240
2264
  end
2241
2265
 
@@ -2246,9 +2270,9 @@ module Dommy
2246
2270
 
2247
2271
  # `select.add(option, before)` — appends or inserts before `before`.
2248
2272
  def add(option, before = nil)
2249
- return nil unless option.respond_to?(:__node__)
2273
+ return nil unless option.respond_to?(:__dommy_backend_node__)
2250
2274
 
2251
- if before.respond_to?(:__node__)
2275
+ if before.respond_to?(:__dommy_backend_node__)
2252
2276
  insert_before(option, before)
2253
2277
  else
2254
2278
  append_child(option)
@@ -2378,6 +2402,12 @@ module Dommy
2378
2402
  # `close(returnValue?)`. Dommy has no modal stack, so showModal is
2379
2403
  # functionally identical to show (no backdrop, no escape-to-close).
2380
2404
  class HTMLDialogElement < HTMLElement
2405
+ # Own __js_call__ methods, on top of Element's.
2406
+ JS_METHOD_NAMES = %w[show showModal close].freeze
2407
+ def __js_method_names__
2408
+ super + JS_METHOD_NAMES
2409
+ end
2410
+
2381
2411
  def open
2382
2412
  reflected_boolean("open")
2383
2413
  end
@@ -2663,7 +2693,7 @@ module Dommy
2663
2693
  row = closest("tr")
2664
2694
  return -1 unless row
2665
2695
 
2666
- row.cells.find_index { |c| c.__node__ == @__node__ } || -1
2696
+ row.cells.find_index { |c| c.__dommy_backend_node__ == @__node__ } || -1
2667
2697
  end
2668
2698
 
2669
2699
  def col_span
@@ -2749,11 +2779,17 @@ module Dommy
2749
2779
  # `rowIndex` walks the enclosing table; `sectionRowIndex` walks
2750
2780
  # the enclosing thead/tbody/tfoot.
2751
2781
  class HTMLTableRowElement < HTMLElement
2782
+ # Own __js_call__ methods, on top of Element's.
2783
+ JS_METHOD_NAMES = %w[insertCell deleteCell].freeze
2784
+ def __js_method_names__
2785
+ super + JS_METHOD_NAMES
2786
+ end
2787
+
2752
2788
  def cells
2753
2789
  el = self
2754
2790
  HTMLCollection.new do
2755
2791
  el
2756
- .__node__
2792
+ .__dommy_backend_node__
2757
2793
  .element_children
2758
2794
  .select { |n| %w[td th].include?(n.name) }
2759
2795
  .map { |n| el.document.wrap_node(n) }
@@ -2765,7 +2801,7 @@ module Dommy
2765
2801
  table = closest("table")
2766
2802
  return -1 unless table
2767
2803
 
2768
- table.rows.find_index { |r| r.__node__ == @__node__ } || -1
2804
+ table.rows.find_index { |r| r.__dommy_backend_node__ == @__node__ } || -1
2769
2805
  end
2770
2806
 
2771
2807
  def section_row_index
@@ -2823,11 +2859,17 @@ module Dommy
2823
2859
  # `<thead>` / `<tbody>` / `<tfoot>` — share section-level row
2824
2860
  # collection + insertRow / deleteRow.
2825
2861
  class HTMLTableSectionElement < HTMLElement
2862
+ # Own __js_call__ methods, on top of Element's.
2863
+ JS_METHOD_NAMES = %w[insertRow deleteRow].freeze
2864
+ def __js_method_names__
2865
+ super + JS_METHOD_NAMES
2866
+ end
2867
+
2826
2868
  def rows
2827
2869
  el = self
2828
2870
  HTMLCollection.new do
2829
2871
  el
2830
- .__node__
2872
+ .__dommy_backend_node__
2831
2873
  .element_children
2832
2874
  .select { |n| n.name == "tr" }
2833
2875
  .map { |n| el.document.wrap_node(n) }
@@ -2877,16 +2919,25 @@ module Dommy
2877
2919
  # tbody elements. `insertRow(-1)` appends to the last tbody (or
2878
2920
  # creates one); `deleteRow` works against the merged `rows` list.
2879
2921
  class HTMLTableElement < HTMLElement
2922
+ # Own __js_call__ methods, on top of Element's.
2923
+ JS_METHOD_NAMES = %w[
2924
+ insertRow deleteRow createCaption deleteCaption createTHead deleteTHead
2925
+ createTFoot deleteTFoot createTBody
2926
+ ].freeze
2927
+ def __js_method_names__
2928
+ super + JS_METHOD_NAMES
2929
+ end
2930
+
2880
2931
  def caption
2881
2932
  @__node__.element_children.find { |n| n.name == "caption" }&.then { |n| @document.wrap_node(n) }
2882
2933
  end
2883
2934
 
2884
2935
  def caption=(new_caption)
2885
2936
  delete_caption
2886
- return unless new_caption.respond_to?(:__node__)
2937
+ return unless new_caption.respond_to?(:__dommy_backend_node__)
2887
2938
 
2888
2939
  first = @__node__.children.first
2889
- first ? first.add_previous_sibling(new_caption.__node__) : @__node__.add_child(new_caption.__node__)
2940
+ first ? first.add_previous_sibling(new_caption.__dommy_backend_node__) : @__node__.add_child(new_caption.__dommy_backend_node__)
2890
2941
  end
2891
2942
 
2892
2943
  def t_head
@@ -2901,7 +2952,7 @@ module Dommy
2901
2952
  el = self
2902
2953
  HTMLCollection.new do
2903
2954
  el
2904
- .__node__
2955
+ .__dommy_backend_node__
2905
2956
  .element_children
2906
2957
  .select { |n| n.name == "tbody" }
2907
2958
  .map { |n| el.document.wrap_node(n) }
@@ -2913,10 +2964,10 @@ module Dommy
2913
2964
  el = self
2914
2965
  HTMLCollection.new do
2915
2966
  ordered = []
2916
- head = el.__node__.element_children.find { |n| n.name == "thead" }
2917
- bodies = el.__node__.element_children.select { |n| n.name == "tbody" }
2918
- direct = el.__node__.element_children.select { |n| n.name == "tr" }
2919
- foot = el.__node__.element_children.find { |n| n.name == "tfoot" }
2967
+ head = el.__dommy_backend_node__.element_children.find { |n| n.name == "thead" }
2968
+ bodies = el.__dommy_backend_node__.element_children.select { |n| n.name == "tbody" }
2969
+ direct = el.__dommy_backend_node__.element_children.select { |n| n.name == "tr" }
2970
+ foot = el.__dommy_backend_node__.element_children.find { |n| n.name == "tfoot" }
2920
2971
  [head, *bodies, foot].compact.each do |sec|
2921
2972
  sec.element_children.select { |n| n.name == "tr" }.each { |n| ordered << n }
2922
2973
  end
@@ -2932,7 +2983,7 @@ module Dommy
2932
2983
 
2933
2984
  cap = @document.create_element("caption")
2934
2985
  first = @__node__.children.first
2935
- first ? first.add_previous_sibling(cap.__node__) : @__node__.add_child(cap.__node__)
2986
+ first ? first.add_previous_sibling(cap.__dommy_backend_node__) : @__node__.add_child(cap.__dommy_backend_node__)
2936
2987
  cap
2937
2988
  end
2938
2989
 
@@ -2949,10 +3000,10 @@ module Dommy
2949
3000
  head = @document.create_element("thead")
2950
3001
  cap = caption
2951
3002
  if cap
2952
- cap.__node__.add_next_sibling(head.__node__)
3003
+ cap.__dommy_backend_node__.add_next_sibling(head.__dommy_backend_node__)
2953
3004
  else
2954
3005
  first = @__node__.children.first
2955
- first ? first.add_previous_sibling(head.__node__) : @__node__.add_child(head.__node__)
3006
+ first ? first.add_previous_sibling(head.__dommy_backend_node__) : @__node__.add_child(head.__dommy_backend_node__)
2956
3007
  end
2957
3008
 
2958
3009
  head
@@ -2968,7 +3019,7 @@ module Dommy
2968
3019
  return existing if existing
2969
3020
 
2970
3021
  foot = @document.create_element("tfoot")
2971
- @__node__.add_child(foot.__node__)
3022
+ @__node__.add_child(foot.__dommy_backend_node__)
2972
3023
  foot
2973
3024
  end
2974
3025
 
@@ -2981,9 +3032,9 @@ module Dommy
2981
3032
  tb = @document.create_element("tbody")
2982
3033
  last_tbody = t_bodies.last
2983
3034
  if last_tbody
2984
- last_tbody.__node__.add_next_sibling(tb.__node__)
3035
+ last_tbody.__dommy_backend_node__.add_next_sibling(tb.__dommy_backend_node__)
2985
3036
  else
2986
- @__node__.add_child(tb.__node__)
3037
+ @__node__.add_child(tb.__dommy_backend_node__)
2987
3038
  end
2988
3039
 
2989
3040
  tb
@@ -3006,10 +3057,10 @@ module Dommy
3006
3057
  target_section.append_child(tr)
3007
3058
  else
3008
3059
  anchor = list[idx]
3009
- section = anchor.__node__.parent
3060
+ section = anchor.__dommy_backend_node__.parent
3010
3061
  if section
3011
- anchor.__node__.add_previous_sibling(tr.__node__)
3012
- @document.notify_child_list_mutation(target_node: section, added_nodes: [tr.__node__], removed_nodes: [])
3062
+ anchor.__dommy_backend_node__.add_previous_sibling(tr.__dommy_backend_node__)
3063
+ @document.notify_child_list_mutation(target_node: section, added_nodes: [tr.__dommy_backend_node__], removed_nodes: [])
3013
3064
  end
3014
3065
  end
3015
3066
 
@@ -3077,6 +3128,12 @@ module Dommy
3077
3128
  # absent in Dommy — getters return inert values, `play()` returns
3078
3129
  # a resolved Promise, and `pause()` flips `paused` back to true.
3079
3130
  class HTMLMediaElement < HTMLElement
3131
+ # Own __js_call__ methods, on top of Element's.
3132
+ JS_METHOD_NAMES = %w[play pause load canPlayType].freeze
3133
+ def __js_method_names__
3134
+ super + JS_METHOD_NAMES
3135
+ end
3136
+
3080
3137
  NETWORK_EMPTY = 0
3081
3138
  NETWORK_IDLE = 1
3082
3139
  NETWORK_LOADING = 2
@@ -4449,7 +4506,13 @@ module Dommy
4449
4506
  "html" => HTMLHtmlElement
4450
4507
  }.freeze
4451
4508
 
4452
- def self.element_class_for(tag_name)
4453
- HTML_ELEMENT_CLASSES[tag_name.to_s.downcase] || Element
4509
+ SVG_NAMESPACE_URI = "http://www.w3.org/2000/svg"
4510
+
4511
+ def self.element_class_for(tag_name, namespace_uri = nil)
4512
+ if namespace_uri == SVG_NAMESPACE_URI
4513
+ SVG_ELEMENT_CLASSES[tag_name.to_s.downcase] || SVGElement
4514
+ else
4515
+ HTML_ELEMENT_CLASSES[tag_name.to_s.downcase] || Element
4516
+ end
4454
4517
  end
4455
4518
  end
@@ -5,6 +5,8 @@ module Dommy
5
5
  # Manages document cookie storage (in-memory, not persisted).
6
6
  # Implements the simple document.cookie key=value; key=value interface.
7
7
  class CookieJar
8
+ attr_reader :cookies
9
+
8
10
  def initialize
9
11
  @cookies = {}
10
12
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dommy
4
+ module Internal
5
+ # Custom Nokogiri pseudo-class handlers so CSS selectors like
6
+ # `:disabled` / `:enabled` / `:checked` work in query_selector(_all).
7
+ # Nokogiri calls the method named after the pseudo-class with the current
8
+ # node list and expects the filtered list back. Receives raw Nokogiri
9
+ # nodes (not Dommy wrappers).
10
+ class CSSPseudoHandlers < BasicObject
11
+ include ::Kernel
12
+
13
+ def disabled(list)
14
+ list.find_all { |node| node.has_attribute?("disabled") }
15
+ end
16
+
17
+ def enabled(list)
18
+ list.find_all { |node| !node.has_attribute?("disabled") }
19
+ end
20
+
21
+ def checked(list)
22
+ list.find_all { |node| node.has_attribute?("checked") }
23
+ end
24
+ end
25
+
26
+ CSS_PSEUDO_HANDLERS = CSSPseudoHandlers.new
27
+ end
28
+ end
@@ -44,7 +44,7 @@ module Dommy
44
44
  actual.positive?
45
45
  when Integer
46
46
  actual == expected
47
- when Range
47
+ when ::Range
48
48
  expected.cover?(actual)
49
49
  else
50
50
  false
@@ -57,7 +57,7 @@ module Dommy
57
57
  #
58
58
  # @param html [String]
59
59
  def normalize_html(html)
60
- Nokogiri::HTML5.fragment(html.to_s).to_html.gsub(/\s+/, " ").strip
60
+ Backend.fragment(html.to_s, owner_doc: nil).to_html.gsub(/\s+/, " ").strip
61
61
  end
62
62
 
63
63
  # Get the text_content of a scope, handling Document (which has
@@ -91,9 +91,9 @@ module Dommy
91
91
  # ancestors (head/script/style/template), inline `display:none` /
92
92
  # `visibility:hidden` on element or any ancestor.
93
93
  def visible?(element)
94
- return true unless element.respond_to?(:__node__)
94
+ return true unless element.respond_to?(:__dommy_backend_node__)
95
95
 
96
- node = element.__node__
96
+ node = element.__dommy_backend_node__
97
97
  return false if node_invisible_self?(node)
98
98
 
99
99
  NodeTraversal.each_ancestor(node) do |ancestor|