dommy 0.13.0 → 0.15.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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -1
  3. data/lib/dommy/attr.rb +56 -32
  4. data/lib/dommy/backend.rb +262 -197
  5. data/lib/dommy/blob.rb +7 -0
  6. data/lib/dommy/bridge/constructor.rb +72 -0
  7. data/lib/dommy/bridge/errors.rb +42 -0
  8. data/lib/dommy/bridge/methods.rb +31 -14
  9. data/lib/dommy/bridge/sentinels.rb +39 -0
  10. data/lib/dommy/bridge/values.rb +78 -0
  11. data/lib/dommy/{js → bridge}/wire_tags.rb +14 -11
  12. data/lib/dommy/bridge.rb +22 -250
  13. data/lib/dommy/browser.rb +115 -14
  14. data/lib/dommy/callable_invoker.rb +27 -31
  15. data/lib/dommy/character_data.rb +539 -0
  16. data/lib/dommy/class_list.rb +328 -0
  17. data/lib/dommy/crypto.rb +12 -1
  18. data/lib/dommy/css.rb +15 -0
  19. data/lib/dommy/custom_elements.rb +11 -15
  20. data/lib/dommy/data_transfer.rb +106 -0
  21. data/lib/dommy/document.rb +490 -511
  22. data/lib/dommy/dom_parser.rb +3 -0
  23. data/lib/dommy/element.rb +352 -2246
  24. data/lib/dommy/encodings/tables.rb +4 -4
  25. data/lib/dommy/encodings.rb +58 -27
  26. data/lib/dommy/event.rb +234 -30
  27. data/lib/dommy/event_source.rb +41 -0
  28. data/lib/dommy/fetch.rb +8 -12
  29. data/lib/dommy/form_data.rb +18 -73
  30. data/lib/dommy/form_entry_list.rb +227 -0
  31. data/lib/dommy/fragment.rb +317 -0
  32. data/lib/dommy/history.rb +3 -0
  33. data/lib/dommy/html_canvas_element.rb +1 -1
  34. data/lib/dommy/html_collection.rb +43 -26
  35. data/lib/dommy/html_elements/base.rb +602 -0
  36. data/lib/dommy/html_elements/embedded.rb +237 -0
  37. data/lib/dommy/html_elements/forms/form.rb +1090 -0
  38. data/lib/dommy/html_elements/forms/input.rb +645 -0
  39. data/lib/dommy/html_elements/forms/select.rb +536 -0
  40. data/lib/dommy/html_elements/forms.rb +8 -0
  41. data/lib/dommy/html_elements/interactive.rb +385 -0
  42. data/lib/dommy/html_elements/media.rb +329 -0
  43. data/lib/dommy/html_elements/metadata.rb +357 -0
  44. data/lib/dommy/html_elements/miscellaneous.rb +77 -0
  45. data/lib/dommy/html_elements/tables.rb +448 -0
  46. data/lib/dommy/html_elements.rb +16 -5324
  47. data/lib/dommy/hyperlink_utils.rb +171 -0
  48. data/lib/dommy/interaction/debug.rb +7 -6
  49. data/lib/dommy/interaction/dom_summary.rb +65 -40
  50. data/lib/dommy/interaction/driver.rb +29 -115
  51. data/lib/dommy/interaction/event_synthesis.rb +97 -4
  52. data/lib/dommy/interaction/field_interactor.rb +2 -14
  53. data/lib/dommy/interaction/form_submission.rb +90 -168
  54. data/lib/dommy/interaction/key_sender.rb +121 -0
  55. data/lib/dommy/interaction/mime_types.rb +25 -0
  56. data/lib/dommy/interaction.rb +2 -0
  57. data/lib/dommy/internal/accessibility_tree.rb +36 -13
  58. data/lib/dommy/internal/accessibility_visibility.rb +44 -0
  59. data/lib/dommy/internal/accessible_description.rb +2 -2
  60. data/lib/dommy/internal/accessible_name.rb +109 -94
  61. data/lib/dommy/internal/an_plus_b.rb +125 -0
  62. data/lib/dommy/internal/aria_role.rb +47 -23
  63. data/lib/dommy/internal/aria_snapshot.rb +5 -0
  64. data/lib/dommy/internal/aria_state.rb +31 -12
  65. data/lib/dommy/internal/autofill.rb +141 -0
  66. data/lib/dommy/internal/backend_prefilter.rb +177 -0
  67. data/lib/dommy/internal/bounded_cache.rb +50 -0
  68. data/lib/dommy/internal/child_list.rb +75 -0
  69. data/lib/dommy/internal/child_node.rb +9 -34
  70. data/lib/dommy/internal/complex_match.rb +127 -0
  71. data/lib/dommy/internal/css/calc.rb +143 -145
  72. data/lib/dommy/internal/css/cascade.rb +38 -393
  73. data/lib/dommy/internal/css/cascaded_declarations.rb +236 -0
  74. data/lib/dommy/internal/css/color.rb +61 -34
  75. data/lib/dommy/internal/css/computed_style_builder.rb +226 -0
  76. data/lib/dommy/internal/css/computed_style_declaration.rb +20 -4
  77. data/lib/dommy/internal/css/counters.rb +60 -37
  78. data/lib/dommy/internal/css/custom_properties.rb +97 -78
  79. data/lib/dommy/internal/css/media_query.rb +228 -203
  80. data/lib/dommy/internal/css/parser.rb +70 -41
  81. data/lib/dommy/internal/css/property_registry.rb +32 -56
  82. data/lib/dommy/internal/css/renderability.rb +61 -0
  83. data/lib/dommy/internal/css/rule_index.rb +40 -152
  84. data/lib/dommy/internal/css/selector_dependencies.rb +162 -0
  85. data/lib/dommy/internal/css/style_cache.rb +61 -0
  86. data/lib/dommy/internal/css/ua_stylesheet.rb +42 -2
  87. data/lib/dommy/internal/css/value_tokens.rb +60 -0
  88. data/lib/dommy/internal/css_source.rb +223 -0
  89. data/lib/dommy/internal/css_syntax.rb +153 -0
  90. data/lib/dommy/internal/directionality.rb +188 -0
  91. data/lib/dommy/internal/document_generations.rb +189 -0
  92. data/lib/dommy/internal/document_interaction_state.rb +47 -0
  93. data/lib/dommy/internal/document_live_ranges.rb +97 -0
  94. data/lib/dommy/internal/dom_matching.rb +32 -16
  95. data/lib/dommy/internal/element_aria.rb +199 -0
  96. data/lib/dommy/internal/element_css_inline_style.rb +26 -0
  97. data/lib/dommy/internal/element_editing.rb +41 -0
  98. data/lib/dommy/internal/element_geometry.rb +69 -0
  99. data/lib/dommy/internal/element_matching.rb +5 -12
  100. data/lib/dommy/internal/element_nonce.rb +28 -0
  101. data/lib/dommy/internal/element_popover.rb +109 -0
  102. data/lib/dommy/internal/element_shadow.rb +120 -0
  103. data/lib/dommy/internal/element_state.rb +244 -0
  104. data/lib/dommy/internal/element_tasks.rb +19 -0
  105. data/lib/dommy/internal/element_top_layer.rb +19 -0
  106. data/lib/dommy/internal/enumerated_keyword_sets.rb +53 -0
  107. data/lib/dommy/internal/exception_report.rb +1 -1
  108. data/lib/dommy/internal/html_or_svg_or_mathml_element.rb +75 -0
  109. data/lib/dommy/internal/infra.rb +27 -0
  110. data/lib/dommy/internal/input_type.rb +292 -0
  111. data/lib/dommy/internal/insertion_point.rb +62 -0
  112. data/lib/dommy/internal/literal_lookup.rb +103 -0
  113. data/lib/dommy/internal/mime_type.rb +74 -0
  114. data/lib/dommy/internal/mutation_coordinator.rb +60 -199
  115. data/lib/dommy/internal/namespaces.rb +41 -43
  116. data/lib/dommy/internal/node_adopter.rb +125 -98
  117. data/lib/dommy/internal/node_factory.rb +115 -0
  118. data/lib/dommy/internal/node_identity.rb +31 -0
  119. data/lib/dommy/internal/node_traversal.rb +12 -13
  120. data/lib/dommy/internal/node_wrapper_cache.rb +71 -299
  121. data/lib/dommy/internal/observable_callback.rb +2 -5
  122. data/lib/dommy/internal/observer_options.rb +93 -0
  123. data/lib/dommy/internal/parent_node.rb +29 -29
  124. data/lib/dommy/internal/post_insertion_steps.rb +213 -0
  125. data/lib/dommy/internal/reflected_attributes.rb +456 -24
  126. data/lib/dommy/internal/rendered_text/collector.rb +228 -0
  127. data/lib/dommy/internal/rendered_text/computed_box.rb +39 -0
  128. data/lib/dommy/internal/rendered_text/fragment.rb +55 -0
  129. data/lib/dommy/internal/rendered_text/items.rb +57 -0
  130. data/lib/dommy/internal/rendered_text/whitespace_collapser.rb +132 -0
  131. data/lib/dommy/internal/selector_arguments.rb +79 -0
  132. data/lib/dommy/internal/selector_index.rb +22 -11
  133. data/lib/dommy/internal/selector_match.rb +190 -0
  134. data/lib/dommy/internal/selector_matcher.rb +98 -628
  135. data/lib/dommy/internal/selector_parser.rb +164 -136
  136. data/lib/dommy/internal/shadow_root_registry.rb +7 -6
  137. data/lib/dommy/internal/sibling_positions.rb +59 -0
  138. data/lib/dommy/internal/svg_uri_reference.rb +33 -0
  139. data/lib/dommy/internal/template_content_registry.rb +112 -66
  140. data/lib/dommy/internal/text_flattening.rb +20 -0
  141. data/lib/dommy/internal/text_selection.rb +104 -0
  142. data/lib/dommy/internal/toggle_events.rb +56 -0
  143. data/lib/dommy/internal/toggle_task_tracker.rb +57 -0
  144. data/lib/dommy/internal/url_parser.rb +722 -569
  145. data/lib/dommy/internal/url_record_accessors.rb +90 -0
  146. data/lib/dommy/internal/web_idl.rb +11 -0
  147. data/lib/dommy/internal/window_constructors.rb +190 -0
  148. data/lib/dommy/internal/xml_serialization.rb +174 -122
  149. data/lib/dommy/js/crossing_profile.rb +75 -0
  150. data/lib/dommy/js/dom_interfaces.rb +23 -2
  151. data/lib/dommy/js/host_bridge.rb +43 -187
  152. data/lib/dommy/js/host_callbacks.rb +106 -0
  153. data/lib/dommy/js/host_runtime.js +863 -763
  154. data/lib/dommy/js/import_map.rb +10 -1
  155. data/lib/dommy/js/marshaller.rb +94 -65
  156. data/lib/dommy/js/module_loader.rb +12 -2
  157. data/lib/dommy/js/module_preload.rb +116 -0
  158. data/lib/dommy/js/observable_runtime.js +125 -146
  159. data/lib/dommy/js/runtime.rb +19 -43
  160. data/lib/dommy/js/runtime_registry.rb +46 -0
  161. data/lib/dommy/js/script_boot.rb +43 -74
  162. data/lib/dommy/js/webidl_tables.js +643 -0
  163. data/lib/dommy/js/window_wiring.rb +69 -0
  164. data/lib/dommy/location.rb +152 -103
  165. data/lib/dommy/mathml_elements.rb +13 -0
  166. data/lib/dommy/mutation_observer.rb +4 -64
  167. data/lib/dommy/navigation.rb +65 -12
  168. data/lib/dommy/navigator.rb +1 -5
  169. data/lib/dommy/node.rb +106 -51
  170. data/lib/dommy/notification.rb +1 -5
  171. data/lib/dommy/parser.rb +5 -16
  172. data/lib/dommy/promise.rb +55 -15
  173. data/lib/dommy/range.rb +15 -13
  174. data/lib/dommy/resources.rb +19 -1
  175. data/lib/dommy/shadow_root.rb +19 -11
  176. data/lib/dommy/streams.rb +2 -6
  177. data/lib/dommy/style_declaration.rb +300 -0
  178. data/lib/dommy/svg_elements.rb +26 -56
  179. data/lib/dommy/tree_walker.rb +2 -8
  180. data/lib/dommy/url.rb +21 -74
  181. data/lib/dommy/url_pattern/regexp_translator.rb +1 -6
  182. data/lib/dommy/url_pattern/tokenizer.rb +4 -2
  183. data/lib/dommy/url_pattern.rb +1 -9
  184. data/lib/dommy/validity_state.rb +383 -0
  185. data/lib/dommy/version.rb +1 -1
  186. data/lib/dommy/web_socket.rb +5 -5
  187. data/lib/dommy/window.rb +35 -171
  188. data/lib/dommy/xml_http_request.rb +35 -4
  189. data/lib/dommy.rb +31 -12
  190. metadata +91 -8
  191. data/lib/dommy/backend/makiri_adapter.rb +0 -374
  192. data/lib/dommy/bridge/constructor_registry.rb +0 -28
  193. data/lib/dommy/internal/css_pseudo_handlers.rb +0 -378
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64a22d9ba17102a03881baedbda66b5236b8c4499140d779e4c4acba86583241
4
- data.tar.gz: e111a114c5a7ae9485a3e99af339e70a59dce6b9a83a837639ac6564873a8d4e
3
+ metadata.gz: b0d28c900db6681f80910f285f05e95985648e6bf824990f23d4f4b4b47bdaf9
4
+ data.tar.gz: 509faf8cfe617d13f1ff69507254c3ef7987d77a9bdf796fa7ce44836d63c798
5
5
  SHA512:
6
- metadata.gz: 67c651bd499d2cd0d6cf1fb862b11a9f0f6b68ba1814abe40732b990e5b66b9b5f0494fd33cb989e4b7a5f64faa3a8a152e8979231f4c082f32187999188030a
7
- data.tar.gz: e7e8ee9194687b9fe6be28646e9f80d32631f3cf78e5b9fefbab5fd913b9a1d6116ab819bae3a54030f2fefc6e4fa8e478c8ab2667cc086e009c5ebb1a7c9481
6
+ metadata.gz: d52a0dec8dca36a172c0ff93e89ab75126049a6b85eeea2a7527c99a162cc087b98cdf11afa7a98d0cd5024bce642ac612f955babf15a3b02af63cbddc336981
7
+ data.tar.gz: f8de49a82844eee7f75e5b5db13a798b7210b70a9bc1e902b85c37819fc79e98c71a2dd8d60abeeaa4f16de9a2679c15f27d48d57f7005b2345ac5a1e0fce7b5
data/README.md CHANGED
@@ -79,7 +79,7 @@ file = Dommy::File.new(["pdf body"], "doc.pdf", "type" => "application/pdf")
79
79
 
80
80
  # Seed a file input for tests
81
81
  input = win.document.query_selector("input[type='file']")
82
- input.__driver_set_files__([file])
82
+ input.files = [file]
83
83
 
84
84
  # FormData picks it up
85
85
  fd = Dommy::FormData.new(win.document.query_selector("form"))
@@ -106,6 +106,20 @@ response = win.__js_call__("fetch", ["/api"]).await
106
106
  > [!WARNING]
107
107
  > Most Dommy accessors (`Blob#text`, `localStorage.get_item`) return synchronous Ruby values — not Promises. `.await` is only for the JS-bridged async surface (e.g., `fetch()`, `window.__js_call__`). Methods like `Response#text()` are Promise-returning and require `.await`.
108
108
 
109
+ ### Preloading ES modules across page loads
110
+
111
+ With JavaScript on (dommy-js-quickjs), a test suite boots the same bundles — Turbo, Stimulus, an app's vendored libraries — page after page, and parsing them is most of what booting costs. Turn preloading on, and a page's ES modules of 10 KB or more are compiled to bytecode once per process and read as bytecode by every later page of the same origin:
112
+
113
+ ```ruby
114
+ # spec/rails_helper.rb or test/test_helper.rb
115
+ Dommy::Js::ModulePreload.enabled = true
116
+ ```
117
+
118
+ With Turbo served as an ES module (importmap-rails), a page boots in about 11 ms instead of 24 ms from the third load on: the first registers the module, the second compiles it.
119
+
120
+ > [!NOTE]
121
+ > A preloaded module is not fetched again for the rest of the process. Turn it on when your module URLs carry a digest (importmap-rails, Propshaft, jsbundling with digested assets); a URL whose content changes without its name changing would keep running what it served first.
122
+
109
123
  ## Test helpers
110
124
 
111
125
  Dommy ships test-side modules you can `include` into RSpec / Minitest. Matchers accept a `Dommy::Document` / element or a raw HTML string (auto-parsed), matching Capybara's `expect(rendered).to ...` ergonomics.
data/lib/dommy/attr.rb CHANGED
@@ -9,9 +9,13 @@ module Dommy
9
9
  # We represent two states:
10
10
  # - "owned" — the Attr is attached to an Element. value reads/writes
11
11
  # go through the element's Makiri attribute slot.
12
- # - "detached" — created via `document.createAttribute(name)` but
13
- # not yet attached. Value is stored locally; `setAttributeNode`
12
+ # - "detached" — created via `document.createAttribute(name)` or removed
13
+ # from its element. Value is stored locally; `setAttributeNode`
14
14
  # transfers it to an element.
15
+ #
16
+ # Its node document is its own in either state, as the DOM has it: set
17
+ # when it is created, appended or adopted (with its element or by itself),
18
+ # and kept when it is removed.
15
19
  class Attr
16
20
  include Node
17
21
  include EventTarget
@@ -25,9 +29,7 @@ module Dommy
25
29
  def initialize(name, owner: nil, value: "", namespace_uri: nil, prefix: nil, local_name: nil, document: nil)
26
30
  qname = name.to_s
27
31
  @owner = owner
28
- # The node document (for baseURI/ownerDocument when detached from an
29
- # element). Owned attrs derive it from their owner instead.
30
- @document = document
32
+ @document = document || owner&.document
31
33
  @detached_value = value.to_s
32
34
  if namespace_uri && !namespace_uri.to_s.empty?
33
35
  # Namespaced attributes preserve case and carry prefix / localName.
@@ -53,19 +55,13 @@ module Dommy
53
55
  @owner
54
56
  end
55
57
 
56
- # Node.baseURI — the node document's base URL. Derived from the owner
57
- # element when attached, else the document the attr was created in.
58
+ # Node.baseURI — the node document's base URL.
58
59
  def base_uri
59
- return @owner.base_uri if @owner.respond_to?(:base_uri)
60
-
61
60
  @document&.base_uri
62
61
  end
63
62
 
64
- # Node.ownerDocument — the owner element's current document when attached (so
65
- # it follows the element across adoptNode), else the creation document.
63
+ # Node.ownerDocument — the node document.
66
64
  def owner_document
67
- return @owner.document if @owner.respond_to?(:document)
68
-
69
65
  @document
70
66
  end
71
67
 
@@ -85,7 +81,9 @@ module Dommy
85
81
  if @namespace_uri
86
82
  @owner.set_attribute_ns(@namespace_uri, @name, new_value.to_s)
87
83
  else
88
- @owner.set_attribute(@name, new_value.to_s)
84
+ # This attribute, in no namespace — not the first with its
85
+ # qualified name, which may be another namespace's.
86
+ @owner.__internal_set_attribute_value__(@local_name, new_value.to_s)
89
87
  end
90
88
  else
91
89
  @detached_value = new_value.to_s
@@ -122,6 +120,15 @@ module Dommy
122
120
  when "specified"
123
121
  # Legacy/useless attribute — always true (WHATWG DOM).
124
122
  true
123
+ when "parentNode", "parentElement", "firstChild", "lastChild", "previousSibling", "nextSibling"
124
+ # An Attr is no part of a tree: it has no parent, children or
125
+ # siblings, and its element is no parent of it.
126
+ nil
127
+ when "childNodes"
128
+ NodeList.new
129
+ when "isConnected"
130
+ # Its root is itself, never a document.
131
+ false
125
132
  else
126
133
  Bridge::ABSENT
127
134
  end
@@ -139,8 +146,8 @@ module Dommy
139
146
  end
140
147
 
141
148
  include Bridge::Methods
142
- js_methods %w[cloneNode isSameNode getRootNode hasChildNodes normalize compareDocumentPosition
143
- appendChild insertBefore removeChild replaceChild
149
+ js_methods %w[cloneNode isSameNode isEqualNode contains getRootNode hasChildNodes normalize
150
+ compareDocumentPosition appendChild insertBefore removeChild replaceChild
144
151
  lookupNamespaceURI lookupPrefix isDefaultNamespace
145
152
  addEventListener removeEventListener dispatchEvent]
146
153
  def __js_call__(method, args)
@@ -154,9 +161,13 @@ module Dommy
154
161
  when "cloneNode"
155
162
  Attr.new(@name, owner: nil, value: value,
156
163
  namespace_uri: @namespace_uri, prefix: @prefix, local_name: @local_name,
157
- document: @document || (@owner.respond_to?(:document) ? @owner.document : nil))
164
+ document: @document)
158
165
  when "isSameNode"
159
166
  is_same_node(args[0])
167
+ when "isEqualNode"
168
+ is_equal_node(args[0])
169
+ when "contains"
170
+ contains?(args[0])
160
171
  when "getRootNode"
161
172
  get_root_node(args[0])
162
173
  when "compareDocumentPosition"
@@ -178,14 +189,29 @@ module Dommy
178
189
  end
179
190
  end
180
191
 
192
+ # Node.contains — an Attr has no descendants, so only itself.
193
+ def contains?(other)
194
+ equal?(other)
195
+ end
196
+
181
197
  # Internal: called by Element when the attr is being transferred
182
- # to (or detached from) an Element.
198
+ # to (or detached from) an Element. Appending it sets its node document
199
+ # to the element's; removing it leaves the node document as it is.
183
200
  def __internal_attach__(element)
184
201
  @owner = element
202
+ @document = element.document
185
203
  @detached_value = ""
186
204
  nil
187
205
  end
188
206
 
207
+ # Internal: "adopt" (DOM §4.5) sets the node document — of the Attr
208
+ # adopted by itself, which stays on its element, or of each attribute of
209
+ # an adopted element.
210
+ def __internal_adopt__(document)
211
+ @document = document
212
+ self
213
+ end
214
+
189
215
  def __internal_detach__
190
216
  cached = value
191
217
  @owner = nil
@@ -238,13 +264,17 @@ module Dommy
238
264
  attr
239
265
  end
240
266
 
267
+ # The element was adopted into `document`, and its attributes with it.
268
+ def __internal_adopt__(document)
269
+ @attrs.each_value { |attr| attr.__internal_adopt__(document) if attr.owner_element.equal?(@element) }
270
+ nil
271
+ end
272
+
241
273
  def get_named_item(name)
242
274
  # getNamedItem / getAttribute lowercase the qualified name only for an HTML
243
275
  # element in an HTML document; other elements match case-sensitively.
244
276
  key = @element.__internal_normalize_attr_key__(name)
245
- node = Backend.attribute_nodes(@element.__dommy_backend_node__).find do |a|
246
- Backend.attribute_ns_info(a)[:qualified_name] == key
247
- end
277
+ node = Backend.attr_by_qualified_name(@element.__dommy_backend_node__, key)
248
278
  node && attr_for(node)
249
279
  end
250
280
 
@@ -254,10 +284,8 @@ module Dommy
254
284
 
255
285
  def remove_named_item(name)
256
286
  key = @element.__internal_normalize_attr_key__(name)
257
- node = Backend.attribute_nodes(@element.__dommy_backend_node__).find do |a|
258
- Backend.attribute_ns_info(a)[:qualified_name] == key
259
- end
260
- return nil unless node
287
+ node = Backend.attr_by_qualified_name(@element.__dommy_backend_node__, key)
288
+ raise DOMException::NotFoundError, "no attribute named #{key.inspect}" unless node
261
289
 
262
290
  removed = attr_for(node)
263
291
  @element.remove_attribute(key)
@@ -297,7 +325,7 @@ module Dommy
297
325
  if ns
298
326
  @element.set_attribute_ns(ns, attr.name, value)
299
327
  else
300
- @element.set_attribute(attr.name, value)
328
+ @element.__internal_set_attribute_value__(local, value)
301
329
  end
302
330
  @attrs[key] = attr
303
331
  old
@@ -316,11 +344,7 @@ module Dommy
316
344
  # ----- Namespaced named-item access (getNamedItemNS etc.) -----
317
345
 
318
346
  def get_named_item_ns(namespace, local_name)
319
- node = Backend.attribute_nodes(@element.__dommy_backend_node__).find do |a|
320
- info = Backend.attribute_ns_info(a)
321
- info[:local_name] == local_name.to_s &&
322
- (info[:namespace_uri] || nil) == (namespace.to_s.empty? ? nil : namespace.to_s)
323
- end
347
+ node = Backend.attr_by_ns(@element.__dommy_backend_node__, namespace, local_name)
324
348
  node && attr_for(node)
325
349
  end
326
350
 
@@ -331,7 +355,7 @@ module Dommy
331
355
 
332
356
  def remove_named_item_ns(namespace, local_name)
333
357
  existing = get_named_item_ns(namespace, local_name)
334
- return nil unless existing
358
+ raise DOMException::NotFoundError, "no attribute #{local_name.inspect} in that namespace" unless existing
335
359
 
336
360
  @element.remove_attribute_ns(namespace, local_name)
337
361
  existing