dommy 0.16.0 → 0.17.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dommy/animation.rb +1 -0
  3. data/lib/dommy/backend.rb +3 -0
  4. data/lib/dommy/blob.rb +6 -0
  5. data/lib/dommy/browser.rb +12 -4
  6. data/lib/dommy/character_data.rb +1 -13
  7. data/lib/dommy/cookie_store.rb +2 -1
  8. data/lib/dommy/document.rb +67 -30
  9. data/lib/dommy/element.rb +11 -0
  10. data/lib/dommy/element_internals.rb +12 -0
  11. data/lib/dommy/encodings/legacy_multibyte.rb +800 -0
  12. data/lib/dommy/encodings/multibyte_overrides.rb +76 -0
  13. data/lib/dommy/encodings/prescan.rb +234 -0
  14. data/lib/dommy/encodings.rb +121 -57
  15. data/lib/dommy/event.rb +107 -54
  16. data/lib/dommy/event_source.rb +1 -0
  17. data/lib/dommy/fetch.rb +244 -53
  18. data/lib/dommy/file_reader.rb +1 -0
  19. data/lib/dommy/form_data.rb +22 -9
  20. data/lib/dommy/form_entry_list.rb +3 -3
  21. data/lib/dommy/html_canvas_element.rb +85 -12
  22. data/lib/dommy/html_elements/base.rb +1 -0
  23. data/lib/dommy/html_elements/forms/input.rb +3 -0
  24. data/lib/dommy/html_elements/interactive.rb +56 -48
  25. data/lib/dommy/html_elements/metadata.rb +23 -0
  26. data/lib/dommy/interaction/form_submission.rb +24 -42
  27. data/lib/dommy/internal/accessibility_tree.rb +149 -162
  28. data/lib/dommy/internal/accessibility_visibility.rb +16 -17
  29. data/lib/dommy/internal/accessible_name.rb +231 -246
  30. data/lib/dommy/internal/aria_role.rb +182 -195
  31. data/lib/dommy/internal/aria_snapshot.rb +35 -38
  32. data/lib/dommy/internal/aria_state.rb +102 -118
  33. data/lib/dommy/internal/autofill.rb +75 -75
  34. data/lib/dommy/internal/backend_prefilter.rb +127 -130
  35. data/lib/dommy/internal/child_navigable.rb +86 -83
  36. data/lib/dommy/internal/close_watcher_manager.rb +1 -0
  37. data/lib/dommy/internal/css/ancestor_filter.rb +64 -64
  38. data/lib/dommy/internal/css/calc.rb +90 -71
  39. data/lib/dommy/internal/css/cascade.rb +69 -67
  40. data/lib/dommy/internal/css/color.rb +249 -187
  41. data/lib/dommy/internal/css/computed_style_builder.rb +34 -0
  42. data/lib/dommy/internal/css/computed_style_declaration.rb +50 -11
  43. data/lib/dommy/internal/css/counters.rb +67 -67
  44. data/lib/dommy/internal/css/custom_properties.rb +114 -114
  45. data/lib/dommy/internal/css/parser.rb +277 -239
  46. data/lib/dommy/internal/css/property_registry.rb +321 -302
  47. data/lib/dommy/internal/css/renderability.rb +37 -35
  48. data/lib/dommy/internal/css/rule_index.rb +2 -2
  49. data/lib/dommy/internal/css/rule_index_inputs.rb +2 -2
  50. data/lib/dommy/internal/css/supports.rb +115 -113
  51. data/lib/dommy/internal/css/ua_stylesheet.rb +55 -53
  52. data/lib/dommy/internal/css_rule_text.rb +121 -119
  53. data/lib/dommy/internal/css_source.rb +1 -3
  54. data/lib/dommy/internal/css_syntax.rb +157 -92
  55. data/lib/dommy/internal/declarative_shadow_roots.rb +1 -0
  56. data/lib/dommy/internal/directionality.rb +120 -123
  57. data/lib/dommy/internal/document_live_ranges.rb +26 -1
  58. data/lib/dommy/internal/dom_matching.rb +185 -184
  59. data/lib/dommy/internal/element_geometry.rb +8 -1
  60. data/lib/dommy/internal/element_matching.rb +71 -69
  61. data/lib/dommy/internal/element_shadow.rb +1 -13
  62. data/lib/dommy/internal/element_state.rb +255 -265
  63. data/lib/dommy/internal/event_handlers.rb +66 -64
  64. data/lib/dommy/internal/exception_report.rb +88 -86
  65. data/lib/dommy/internal/focusability.rb +205 -205
  66. data/lib/dommy/internal/global_functions.rb +49 -47
  67. data/lib/dommy/internal/idl_reflection.rb +99 -97
  68. data/lib/dommy/internal/js_number.rb +35 -33
  69. data/lib/dommy/internal/light_dismiss.rb +130 -132
  70. data/lib/dommy/internal/literal_lookup.rb +75 -75
  71. data/lib/dommy/internal/mime_type.rb +54 -49
  72. data/lib/dommy/internal/mutation_coordinator.rb +46 -0
  73. data/lib/dommy/internal/mutation_observer_microtask.rb +60 -0
  74. data/lib/dommy/internal/namespaces.rb +51 -49
  75. data/lib/dommy/internal/node_adopter.rb +25 -1
  76. data/lib/dommy/internal/node_equality.rb +61 -59
  77. data/lib/dommy/internal/post_insertion_steps.rb +1 -0
  78. data/lib/dommy/internal/referrer.rb +144 -0
  79. data/lib/dommy/internal/rendered_text/collector.rb +22 -3
  80. data/lib/dommy/internal/rendered_text/computed_box.rb +3 -2
  81. data/lib/dommy/internal/rendered_text/fragment.rb +37 -38
  82. data/lib/dommy/internal/rendered_text/items.rb +7 -5
  83. data/lib/dommy/internal/selector_matcher.rb +361 -359
  84. data/lib/dommy/internal/selector_parser.rb +74 -62
  85. data/lib/dommy/internal/sequential_focus_navigation.rb +150 -151
  86. data/lib/dommy/internal/shadow_root_registry.rb +7 -0
  87. data/lib/dommy/internal/slot_assignment.rb +117 -0
  88. data/lib/dommy/internal/slot_signals.rb +125 -0
  89. data/lib/dommy/internal/unsafe_html.rb +79 -77
  90. data/lib/dommy/internal/url_parser.rb +5 -29
  91. data/lib/dommy/internal/web_idl.rb +158 -153
  92. data/lib/dommy/internal/window_bound.rb +14 -0
  93. data/lib/dommy/internal/window_constructors.rb +3 -0
  94. data/lib/dommy/internal/xml_serialization.rb +298 -296
  95. data/lib/dommy/js/dom_interfaces.rb +13 -1
  96. data/lib/dommy/js/host_bridge.rb +23 -1
  97. data/lib/dommy/js/host_callbacks.rb +7 -0
  98. data/lib/dommy/js/host_runtime.js +625 -61
  99. data/lib/dommy/js/import_map.rb +1 -1
  100. data/lib/dommy/js/observable_runtime.js +295 -128
  101. data/lib/dommy/js/webidl_members.js +1 -1
  102. data/lib/dommy/js/webidl_signatures.js +35 -6
  103. data/lib/dommy/media_query_list.rb +1 -0
  104. data/lib/dommy/message_channel.rb +2 -0
  105. data/lib/dommy/mutation_observer.rb +25 -29
  106. data/lib/dommy/navigator.rb +4 -1
  107. data/lib/dommy/node.rb +4 -0
  108. data/lib/dommy/notification.rb +1 -0
  109. data/lib/dommy/promise.rb +12 -0
  110. data/lib/dommy/range.rb +20 -3
  111. data/lib/dommy/shadow_root.rb +25 -1
  112. data/lib/dommy/streams.rb +18 -0
  113. data/lib/dommy/style_declaration.rb +15 -4
  114. data/lib/dommy/text_codec.rb +7 -5
  115. data/lib/dommy/tree_walker.rb +68 -61
  116. data/lib/dommy/version.rb +1 -1
  117. data/lib/dommy/web_socket.rb +1 -0
  118. data/lib/dommy/window.rb +22 -4
  119. data/lib/dommy/worker.rb +1 -0
  120. data/lib/dommy/xml_http_request.rb +12 -2
  121. data/lib/dommy.rb +17 -2
  122. metadata +11 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d948ccbb7ca08b92aedc5222fe46bf219060f0e2ae2903bc49b8de3401321cb2
4
- data.tar.gz: 621b638bd22e4decae148605015885c49ef93b0c44f2fbadcb0d0b542b489a0d
3
+ metadata.gz: 2063a6335f55bcddf4a9db6b162b0ea69ccce3aaa4c8b7bd45069850992c98ca
4
+ data.tar.gz: 725b6c8395b377fcddbda372ecb55709f169e0143b5a0bc404f78036db18e243
5
5
  SHA512:
6
- metadata.gz: 89eec9a0136a58ce216bee43e800b584b42a9e41702ebf172d5f4938d937707ab8209e43ca4d5d314eee39aeefbb8c3097317aca6aba7fa6d5fee3f38c95952c
7
- data.tar.gz: cac802773d9c86b06ecd2e50057741521c6bfea1d7e1f86a8cbbf520f73827207cb4b9f3bef12c19dc0e699850a042487c0704f4f6c290a7af2f53e03f50199a
6
+ metadata.gz: 7a4af1e30b2e3efe5de479b05c67d770e7ca6e11b71db0fc932c5ba382cee6e52a65ebebfb63be3aa2ac8e431efec5e1da9f97fcbc85fd3fea7e12926b3f4350
7
+ data.tar.gz: 0dab9637f563f76636614616d79b2422e618ac92aff8cc39a5b8f3c64730ee1af68b48f09b76b37d5b9c2b85bcd38131549863862ee598f4c5b7b0b0d449b42e
@@ -88,6 +88,7 @@ module Dommy
88
88
  # Spec: https://drafts.csswg.org/web-animations/#animation
89
89
  class Animation
90
90
  include EventTarget
91
+ include Internal::WindowBound
91
92
 
92
93
  attr_accessor :id
93
94
  attr_reader :effect, :timeline, :play_state
data/lib/dommy/backend.rb CHANGED
@@ -91,6 +91,9 @@ module Dommy
91
91
  doc.is_a?(::Makiri::XML::Document) ? empty_xml_document : empty_document
92
92
  end
93
93
 
94
+ # Whether backend node `node` belongs to an XML document's tree.
95
+ def xml_node?(node) = node.is_a?(::Makiri::XML::NodeMethods)
96
+
94
97
  # Bring `node` (already detached from its old tree) into `target_doc`,
95
98
  # returning the backend node now owned by `target_doc`. Lexbor's arenas
96
99
  # can't move a node between documents, so adoption imports a detached
data/lib/dommy/blob.rb CHANGED
@@ -187,6 +187,12 @@ module Dommy
187
187
  @last_modified = (raw_lm || (Time.now.to_f * 1000)).to_i
188
188
  end
189
189
 
190
+ # A File under another name keeps its lastModified (XHR's "create an
191
+ # entry"); only a Blob gets a new one.
192
+ def __internal_file_named__(name)
193
+ File.new([@data], name, {"type" => @type, "lastModified" => @last_modified}, @window)
194
+ end
195
+
190
196
  # File's serialization + deserialization steps: bytes, type, name and
191
197
  # lastModified.
192
198
  def __internal_structured_clone__(_for_storage = false)
data/lib/dommy/browser.rb CHANGED
@@ -58,8 +58,11 @@ module Dommy
58
58
  browser
59
59
  end
60
60
 
61
+ # `encoding` is the encoding the page's bytes were decoded with
62
+ # (Encodings.decode_document), when it came from bytes; HTML given as a
63
+ # string makes a UTF-8 document.
61
64
  def initialize(html, url: "http://localhost/", resources: nil, execute_scripts: true, strict: true, settle: true,
62
- wasm_memory_shim: false, backend: nil, navigable: false, same_origin: false)
65
+ wasm_memory_shim: false, backend: nil, navigable: false, same_origin: false, encoding: nil)
63
66
  @resources = resources
64
67
  @same_origin = same_origin
65
68
  @backend = backend
@@ -81,7 +84,7 @@ module Dommy
81
84
  @cookie_jar = CookieJar.new
82
85
  @before_unload_handler = nil
83
86
 
84
- @window = Dommy.parse(html)
87
+ @window = Dommy.parse(html, encoding: encoding)
85
88
  @window.location.__internal_set_url__(url) if url
86
89
  @window.storage_provider = @storage_provider
87
90
  @window.cookie_jar = @cookie_jar
@@ -558,7 +561,12 @@ module Dommy
558
561
  fire_unload(old_window)
559
562
  check_js_errors!
560
563
 
561
- new_window = Dommy.parse(Encodings.decode_document(response.body, response_header(response, "content-type")))
564
+ # HTML's <meta charset> prescan is for an HTML document (one that names
565
+ # no type is taken for one); an XML type's encoding is not a <meta>'s.
566
+ content_type = response_header(response, "content-type")
567
+ type = Internal::MimeType.essence(content_type)
568
+ text, encoding = Encodings.decode_document(response.body, content_type, html: type.empty? || type == "text/html")
569
+ new_window = Dommy.parse(text, encoding: encoding)
562
570
  new_window.location.__internal_set_url__(final_url)
563
571
  new_window.document.__internal_set_last_modified__(response_header(response, "last-modified"))
564
572
  new_window.document.__internal_referrer__ = referrer if referrer
@@ -763,7 +771,7 @@ module Dommy
763
771
  document = window.document
764
772
  window.dispatch_event(document.__internal_page_transition_event__("pagehide"))
765
773
  unload = Dommy::Event.new("unload")
766
- unload.__internal_set_target__(document)
774
+ unload.__internal_set_legacy_target_override__(document)
767
775
  window.dispatch_event(unload.__internal_mark_trusted__)
768
776
  end
769
777
 
@@ -269,19 +269,7 @@ module Dommy
269
269
  # no `slot` attribute, so it targets the default (unnamed) slot of its parent
270
270
  # element's shadow tree; a closed shadow tree hides the assignment (null).
271
271
  def assigned_slot
272
- parent = @__node__.parent
273
- return nil unless parent&.element?
274
-
275
- host = @document.wrap_node(parent)
276
- return nil unless host.respond_to?(:shadow_root)
277
-
278
- sr = host.shadow_root
279
- return nil unless sr
280
- return nil if sr.__js_get__("mode") == "closed"
281
-
282
- sr.query_selector_all("slot").find do |slot|
283
- (slot.respond_to?(:name) ? slot.name.to_s : "") == ""
284
- end
272
+ Internal::SlotAssignment.find_slot(@document, @__node__, open: true)
285
273
  end
286
274
 
287
275
  def __js_set__(key, value)
@@ -10,6 +10,7 @@ module Dommy
10
10
  # Spec: https://cookiestore.spec.whatwg.org/
11
11
  class CookieStore
12
12
  include EventTarget
13
+ include Internal::WindowBound
13
14
 
14
15
  def initialize(window)
15
16
  @window = window
@@ -95,7 +96,7 @@ module Dommy
95
96
  opts = arg.transform_keys(&:to_s)
96
97
  url = document_url
97
98
  if opts["url"]
98
- parsed = @window.__internal_parse_url__(opts["url"].to_s)
99
+ parsed = @window.__internal_parse_url__(opts["url"].to_s, utf8: true)
99
100
  raise Bridge::TypeError, "invalid url" unless parsed && Internal::Origin.of_url(parsed) == @window.origin
100
101
 
101
102
  url = parsed
@@ -693,12 +693,18 @@ module Dommy
693
693
  @quirks_mode = value
694
694
  end
695
695
 
696
- # The document's character encoding, as an Encoding Standard name. Dommy
697
- # holds the DOM as UTF-8 and models no other encoding; this is the one place
698
- # HTML's "encoding-parse a URL" reads it from (see Element#resolve_url), so a
699
- # future non-UTF-8 document has a single place to teach.
696
+ # The document's character encoding, as an Encoding Standard name: what
697
+ # its bytes were decoded with (Encodings.decode_document, which reads a
698
+ # `<meta charset>` from an HTML document's bytes); a document parsed from a
699
+ # string, which is text already, is UTF-8. The DOM is held as UTF-8
700
+ # either way; the encoding is what HTML's "encoding-parse a URL" (a link's
701
+ # query) and form submission encode with.
700
702
  def character_encoding
701
- "UTF-8"
703
+ @character_encoding || "UTF-8"
704
+ end
705
+
706
+ def __internal_character_encoding__=(name)
707
+ @character_encoding = name
702
708
  end
703
709
 
704
710
  # ----- Public Ruby API (snake_case) -----
@@ -845,6 +851,13 @@ module Dommy
845
851
  @creator_url || "about:blank"
846
852
  end
847
853
 
854
+ # The window whose event loop runs this document's tasks and microtasks:
855
+ # its own, or for a document with none, the window of the document whose
856
+ # API made it.
857
+ def __internal_event_loop_window__
858
+ @default_view || @origin_document&.__internal_event_loop_window__
859
+ end
860
+
848
861
  # A document made by a parsing or creation API (DOMParser,
849
862
  # DOMImplementation) takes its origin from `document` — the relevant
850
863
  # global object's associated Document — and, for DOMParser, its URL too.
@@ -1092,41 +1105,28 @@ module Dommy
1092
1105
  # wrapper is owned by `this`. Per spec, the source node is left
1093
1106
  # in place. `deep: true` copies the entire subtree.
1094
1107
  def import_node(node, deep = false)
1108
+ # WebIDL converts `options` before the method steps run, so a bad
1109
+ # customElementRegistry is a TypeError whatever `node` is — an Attr, or
1110
+ # even a Document.
1111
+ deep, registry = import_node_options(deep)
1112
+
1095
1113
  # Step 1: "If node is a document or shadow root, throw a
1096
1114
  # NotSupportedError." Neither has anywhere to go in another document.
1097
1115
  if node.is_a?(Document) || node.is_a?(ShadowRoot)
1098
1116
  raise DOMException::NotSupportedError, "a #{node.is_a?(Document) ? "document" : "shadow root"} cannot be imported"
1099
1117
  end
1100
1118
 
1119
+ # Step 5.3: a global registry stands only for its own document.
1120
+ if registry && !registry.scoped? && !registry.equal?(__internal_custom_element_registry__)
1121
+ raise DOMException::NotSupportedError, "a global registry other than the document's"
1122
+ end
1123
+
1101
1124
  # An Attr is a Node but not a backend-tree node: it is copied by rebuilding
1102
1125
  # it here with the same qualified name, namespace, prefix and value, owned
1103
1126
  # by no element (importNode never attaches the copy to anything).
1104
1127
  return import_attribute(node) if node.is_a?(Attr)
1105
1128
  return nil unless node.is_a?(Node) && node.__dommy_backend_node__
1106
1129
 
1107
- # `(boolean or ImportNodeOptions) options = false`: missing or undefined
1108
- # is the default, false. Null and an object convert to the dictionary,
1109
- # whose `selfOnly` negates `deep` (so null clones deep) and whose
1110
- # `customElementRegistry` (a scoped one, or this document's) is the
1111
- # registry the copies fall back to. Anything else is a boolean, read
1112
- # with ToBoolean.
1113
- registry = nil
1114
- deep = false if deep.equal?(Bridge::UNDEFINED)
1115
- if deep.nil? || deep.is_a?(Hash)
1116
- options = deep || {}
1117
- deep = !Internal::WebIDL.boolean(options["selfOnly"])
1118
- given = options.fetch("customElementRegistry", Bridge::UNDEFINED)
1119
- unless given.equal?(Bridge::UNDEFINED)
1120
- raise Bridge::TypeError, "customElementRegistry is not a CustomElementRegistry" unless given.is_a?(CustomElementRegistry)
1121
- if !given.scoped? && !given.equal?(__internal_custom_element_registry__)
1122
- raise DOMException::NotSupportedError, "a global registry other than the document's"
1123
- end
1124
-
1125
- registry = given
1126
- end
1127
- else
1128
- deep = Internal::WebIDL.boolean(deep)
1129
- end
1130
1130
  # "Clone a single node": the node's own registry — a global one standing
1131
1131
  # for this document's — and the fallback only for a node without one.
1132
1132
  fallback = registry || __internal_custom_element_registry__
@@ -2530,7 +2530,7 @@ module Dommy
2530
2530
  view = @default_view
2531
2531
  if view
2532
2532
  load = Event.new("load")
2533
- load.__internal_set_target__(self)
2533
+ load.__internal_set_legacy_target_override__(self)
2534
2534
  view.dispatch_event(load.__internal_mark_trusted__)
2535
2535
  view.dispatch_event(__internal_page_transition_event__("pageshow"))
2536
2536
  end
@@ -2569,7 +2569,7 @@ module Dommy
2569
2569
  # back/forward cache), targeted at the document (legacy target override).
2570
2570
  def __internal_page_transition_event__(type, persisted: false)
2571
2571
  event = PageTransitionEvent.new(type, "persisted" => persisted, "bubbles" => true, "cancelable" => true)
2572
- event.__internal_set_target__(self)
2572
+ event.__internal_set_legacy_target_override__(self)
2573
2573
  event.__internal_mark_trusted__
2574
2574
  end
2575
2575
 
@@ -2635,6 +2635,7 @@ module Dommy
2635
2635
  script.__internal_mark_parser_document__
2636
2636
  end
2637
2637
  parsed_css("meta[http-equiv]").each { |node| __internal_html_element_wrapper__(node)&.__internal_run_pragma__ }
2638
+ parsed_css("meta[name][content]").each { |node| __internal_html_element_wrapper__(node)&.__internal_run_referrer_metadata__ }
2638
2639
  # An open dialog the parser inserted runs its dialog setup steps.
2639
2640
  parsed_css("dialog[open]").each do |node|
2640
2641
  dialog = __internal_html_element_wrapper__(node)
@@ -2667,6 +2668,12 @@ module Dommy
2667
2668
  # content=…>`): nil until such a pragma has been processed.
2668
2669
  attr_accessor :__internal_pragma_default_language__
2669
2670
 
2671
+ # The document's referrer policy as `<meta name=referrer>` sets it
2672
+ # (HTMLMetaElement#__internal_run_referrer_metadata__): "" (the default)
2673
+ # until one does.
2674
+ def __internal_referrer_policy__ = @__internal_referrer_policy__ || ""
2675
+ attr_writer :__internal_referrer_policy__
2676
+
2670
2677
  # DOMParser parses with scripting disabled (HTML) or XML scripting support
2671
2678
  # disabled, so every script it makes is "already started": moved or cloned
2672
2679
  # into a document that runs scripts, it still does not run. Found by local
@@ -2730,6 +2737,10 @@ module Dommy
2730
2737
  # node back to its shadow boundary.
2731
2738
  # Delegate to ShadowRootRegistry
2732
2739
 
2740
+ def __internal_unregister_shadow_root__(fragment_node, host_node)
2741
+ @shadow_registry.unregister(fragment_node, host_node)
2742
+ end
2743
+
2733
2744
  def __internal_register_shadow_fragment__(fragment_node, shadow_root)
2734
2745
  @shadow_registry.register(fragment_node, shadow_root)
2735
2746
  end
@@ -2820,12 +2831,20 @@ module Dommy
2820
2831
  @shadow_registry.find_enclosing(node)
2821
2832
  end
2822
2833
 
2834
+ # Whether any shadow root is attached in this document.
2835
+ def __internal_any_shadow_roots__ = @shadow_registry.any?
2836
+
2823
2837
  # Every ShadowRoot attached in this document — the cascade collects each
2824
2838
  # one's <style> sheets and scopes them to that shadow tree.
2825
2839
  def __internal_all_shadow_roots__
2826
2840
  @shadow_registry.all
2827
2841
  end
2828
2842
 
2843
+ # The slots whose slotchange is pending (DOM "signal slots").
2844
+ def __internal_slot_signals__
2845
+ @slot_signals ||= Internal::SlotSignals.new(self)
2846
+ end
2847
+
2829
2848
  # Lifecycle callback dispatchers. Errors raised inside user
2830
2849
  # callbacks are swallowed so a single buggy custom element can't
2831
2850
  # break the whole mutation pipeline.
@@ -3423,6 +3442,24 @@ module Dommy
3423
3442
 
3424
3443
  private
3425
3444
 
3445
+ # `(boolean or ImportNodeOptions) options = false`, as [deep, registry]:
3446
+ # missing or undefined is the default, false. Null and an object convert
3447
+ # to the dictionary, whose `selfOnly` negates `deep` (so null clones deep)
3448
+ # and whose `customElementRegistry` has to be a CustomElementRegistry.
3449
+ # Anything else is a boolean, read with ToBoolean.
3450
+ def import_node_options(options)
3451
+ return [false, nil] if options.equal?(Bridge::UNDEFINED)
3452
+ return [Internal::WebIDL.boolean(options), nil] unless options.nil? || options.is_a?(Hash)
3453
+
3454
+ options ||= {}
3455
+ deep = !Internal::WebIDL.boolean(options["selfOnly"])
3456
+ registry = options.fetch("customElementRegistry", Bridge::UNDEFINED)
3457
+ return [deep, nil] if registry.equal?(Bridge::UNDEFINED)
3458
+ raise Bridge::TypeError, "customElementRegistry is not a CustomElementRegistry" unless registry.is_a?(CustomElementRegistry)
3459
+
3460
+ [deep, registry]
3461
+ end
3462
+
3426
3463
  # HTML "is a registrable domain suffix of or is equal to" (a single label
3427
3464
  # stands in for the public suffix; see #domain=).
3428
3465
  def registrable_domain_suffix_or_equal?(suffix_string, original_host)
data/lib/dommy/element.rb CHANGED
@@ -1398,6 +1398,17 @@ module Dommy
1398
1398
  Backend.no_namespace_attribute_value(@__node__, local_name)
1399
1399
  end
1400
1400
 
1401
+ # An ARIA attribute (`role`, `aria-*`) as accessibility reads it: the
1402
+ # content attribute, else a custom element's default ARIA semantics from
1403
+ # its ElementInternals (ARIA §"default implicit ARIA semantics").
1404
+ def __internal_aria_value__(attr)
1405
+ value = __internal_attribute_value__(attr)
1406
+ return value unless value.nil?
1407
+
1408
+ internals = __internal_element_internals__ if respond_to?(:__internal_element_internals__)
1409
+ internals&.__internal_default_aria__(attr)
1410
+ end
1411
+
1401
1412
  # HTML's cloning steps, for the state an element keeps beside its
1402
1413
  # attributes: what a copy takes over, as a Hash, or nil when there is
1403
1414
  # nothing. Each interface and mixin with such state adds its own to what
@@ -35,6 +35,18 @@ module Dommy
35
35
  @validity = nil
36
36
  end
37
37
 
38
+ # The element's default ARIA value for content attribute `attr` ("role",
39
+ # "aria-label"), as set through these internals; nil when unset.
40
+ def __internal_default_aria__(attr)
41
+ value = @aria[ARIA_IDL_BY_ATTRIBUTE[attr]]
42
+ value&.to_s
43
+ end
44
+
45
+ ARIA_IDL_BY_ATTRIBUTE = Internal::IdlReflection::TABLE.fetch("Element")
46
+ .select { |name, _| ARIA_ATTRIBUTES.include?(name) }
47
+ .to_h { |name, spec| [spec[:attr] || name, name] }.freeze
48
+ private_constant :ARIA_IDL_BY_ATTRIBUTE
49
+
38
50
  # Whether the custom state `name` is set (`:state(name)`).
39
51
  def state?(name) = @states.include?(name)
40
52