dommy 0.14.0 → 0.16.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 +15 -1
- data/lib/dommy/attr.rb +56 -32
- data/lib/dommy/backend.rb +251 -208
- data/lib/dommy/blob.rb +24 -0
- data/lib/dommy/bridge/methods.rb +12 -2
- data/lib/dommy/bridge/values.rb +29 -2
- data/lib/dommy/bridge/wire_tags.rb +8 -4
- data/lib/dommy/browser.rb +328 -53
- data/lib/dommy/callable_invoker.rb +27 -31
- data/lib/dommy/character_data.rb +21 -28
- data/lib/dommy/class_list.rb +55 -23
- data/lib/dommy/cookie_jar.rb +408 -0
- data/lib/dommy/cookie_store.rb +104 -49
- data/lib/dommy/css.rb +37 -8
- data/lib/dommy/custom_elements.rb +360 -116
- data/lib/dommy/data_transfer.rb +22 -2
- data/lib/dommy/document.rb +1258 -346
- data/lib/dommy/dom_exception.rb +6 -0
- data/lib/dommy/dom_parser.rb +46 -14
- data/lib/dommy/dom_string_list.rb +36 -0
- data/lib/dommy/element.rb +408 -569
- data/lib/dommy/element_internals.rb +215 -0
- data/lib/dommy/encodings.rb +10 -0
- data/lib/dommy/event.rb +382 -148
- data/lib/dommy/event_source.rb +8 -34
- data/lib/dommy/fetch.rb +8 -12
- data/lib/dommy/file_reader.rb +5 -29
- data/lib/dommy/form_data.rb +53 -16
- data/lib/dommy/form_entry_list.rb +107 -138
- data/lib/dommy/fragment.rb +14 -16
- data/lib/dommy/history.rb +247 -90
- data/lib/dommy/html_canvas_element.rb +15 -35
- data/lib/dommy/html_collection.rb +76 -50
- data/lib/dommy/html_elements/base.rb +377 -71
- data/lib/dommy/html_elements/embedded.rb +350 -106
- data/lib/dommy/html_elements/forms/form.rb +424 -383
- data/lib/dommy/html_elements/forms/input.rb +351 -189
- data/lib/dommy/html_elements/forms/select.rb +97 -81
- data/lib/dommy/html_elements/interactive.rb +285 -72
- data/lib/dommy/html_elements/media.rb +73 -32
- data/lib/dommy/html_elements/metadata.rb +226 -78
- data/lib/dommy/html_elements/miscellaneous.rb +58 -14
- data/lib/dommy/html_elements/tables.rb +20 -42
- data/lib/dommy/html_elements.rb +28 -3
- data/lib/dommy/hyperlink_utils.rb +19 -8
- data/lib/dommy/interaction/drag_and_drop.rb +187 -0
- data/lib/dommy/interaction/event_synthesis.rb +182 -24
- data/lib/dommy/interaction/field_interactor.rb +18 -6
- data/lib/dommy/interaction/form_submission.rb +12 -6
- data/lib/dommy/interaction/key_sender.rb +102 -17
- data/lib/dommy/interaction.rb +1 -0
- data/lib/dommy/internal/accessibility_tree.rb +19 -7
- data/lib/dommy/internal/accessibility_visibility.rb +2 -2
- data/lib/dommy/internal/accessible_description.rb +3 -3
- data/lib/dommy/internal/accessible_name.rb +33 -45
- data/lib/dommy/internal/an_plus_b.rb +125 -0
- data/lib/dommy/internal/aria_role.rb +27 -18
- data/lib/dommy/internal/aria_state.rb +7 -7
- data/lib/dommy/internal/autofill.rb +2 -2
- data/lib/dommy/internal/backend_prefilter.rb +21 -18
- data/lib/dommy/internal/ce_reactions.rb +285 -0
- data/lib/dommy/internal/ce_reactions_table.rb +296 -0
- data/lib/dommy/internal/child_list.rb +39 -0
- data/lib/dommy/internal/child_navigable.rb +155 -0
- data/lib/dommy/internal/child_node.rb +44 -41
- data/lib/dommy/internal/close_watcher_manager.rb +238 -0
- data/lib/dommy/internal/complex_match.rb +129 -0
- data/lib/dommy/internal/constraint_validation.rb +130 -0
- data/lib/dommy/internal/css/ancestor_filter.rb +107 -0
- data/lib/dommy/internal/css/cascade.rb +7 -7
- data/lib/dommy/internal/css/cascaded_declarations.rb +14 -20
- data/lib/dommy/internal/css/computed_style_builder.rb +30 -3
- data/lib/dommy/internal/css/custom_properties.rb +63 -80
- data/lib/dommy/internal/css/parser.rb +53 -63
- data/lib/dommy/internal/css/renderability.rb +6 -0
- data/lib/dommy/internal/css/rule_index.rb +170 -108
- data/lib/dommy/internal/css/rule_index_inputs.rb +135 -0
- data/lib/dommy/internal/css/selector_dependencies.rb +8 -2
- data/lib/dommy/internal/css/style_cache.rb +9 -6
- data/lib/dommy/internal/css/ua_stylesheet.rb +37 -2
- data/lib/dommy/internal/css_source.rb +223 -0
- data/lib/dommy/internal/css_syntax.rb +153 -0
- data/lib/dommy/internal/declarative_shadow_roots.rb +182 -0
- data/lib/dommy/internal/directionality.rb +2 -2
- data/lib/dommy/internal/document_generations.rb +91 -18
- data/lib/dommy/internal/document_interaction_state.rb +293 -9
- data/lib/dommy/internal/dom_matching.rb +7 -7
- data/lib/dommy/internal/element_aria.rb +65 -23
- data/lib/dommy/internal/element_css_inline_style.rb +26 -0
- data/lib/dommy/internal/element_editing.rb +41 -0
- data/lib/dommy/internal/element_nonce.rb +28 -0
- data/lib/dommy/internal/element_popover.rb +310 -0
- data/lib/dommy/internal/element_shadow.rb +107 -33
- data/lib/dommy/internal/element_state.rb +131 -26
- data/lib/dommy/internal/element_top_layer.rb +21 -51
- data/lib/dommy/internal/enumerated_keyword_sets.rb +40 -0
- data/lib/dommy/internal/event_handler_tables.rb +241 -0
- data/lib/dommy/internal/event_handlers.rb +149 -0
- data/lib/dommy/internal/exception_report.rb +1 -1
- data/lib/dommy/internal/focusability.rb +267 -0
- data/lib/dommy/internal/form_associated_custom_elements.rb +127 -0
- data/lib/dommy/internal/html_or_svg_or_mathml_element.rb +86 -0
- data/lib/dommy/internal/html_serialization.rb +292 -0
- data/lib/dommy/internal/idl_reflection.rb +202 -0
- data/lib/dommy/internal/idl_reflection_table.rb +589 -0
- data/lib/dommy/internal/infra.rb +36 -0
- data/lib/dommy/internal/input_type.rb +90 -20
- data/lib/dommy/internal/insertion_point.rb +27 -3
- data/lib/dommy/internal/js_number.rb +60 -0
- data/lib/dommy/internal/light_dismiss.rb +178 -0
- data/lib/dommy/internal/literal_lookup.rb +103 -0
- data/lib/dommy/internal/mime_type.rb +8 -0
- data/lib/dommy/internal/mutation_coordinator.rb +94 -60
- data/lib/dommy/internal/node_adopter.rb +165 -97
- data/lib/dommy/internal/node_factory.rb +111 -49
- data/lib/dommy/internal/node_identity.rb +1 -1
- data/lib/dommy/internal/node_traversal.rb +30 -13
- data/lib/dommy/internal/node_wrapper_cache.rb +34 -148
- data/lib/dommy/internal/observable_callback.rb +2 -5
- data/lib/dommy/internal/observer_options.rb +24 -14
- data/lib/dommy/internal/origin.rb +86 -0
- data/lib/dommy/internal/parent_node.rb +12 -10
- data/lib/dommy/internal/popover_invoker_element.rb +67 -0
- data/lib/dommy/internal/popover_stack.rb +120 -0
- data/lib/dommy/internal/post_insertion_steps.rb +146 -77
- data/lib/dommy/internal/reflected_attributes.rb +173 -43
- data/lib/dommy/internal/rendered_text/collector.rb +12 -9
- data/lib/dommy/internal/rendered_text/fragment.rb +6 -2
- data/lib/dommy/internal/selector_arguments.rb +2 -16
- data/lib/dommy/internal/selector_index.rb +19 -10
- data/lib/dommy/internal/selector_match.rb +190 -0
- data/lib/dommy/internal/selector_matcher.rb +91 -256
- data/lib/dommy/internal/selector_parser.rb +176 -100
- data/lib/dommy/internal/sequential_focus_navigation.rb +210 -0
- data/lib/dommy/internal/shadow_root_registry.rb +14 -6
- data/lib/dommy/internal/sibling_positions.rb +59 -0
- data/lib/dommy/internal/svg_uri_reference.rb +33 -0
- data/lib/dommy/internal/template_content_registry.rb +11 -5
- data/lib/dommy/internal/text_selection.rb +185 -32
- data/lib/dommy/internal/toggle_events.rb +6 -4
- data/lib/dommy/internal/unsafe_html.rb +107 -0
- data/lib/dommy/internal/url_parser.rb +84 -8
- data/lib/dommy/internal/user_activation.rb +192 -0
- data/lib/dommy/internal/web_idl.rb +160 -16
- data/lib/dommy/internal/window_constructors.rb +34 -14
- data/lib/dommy/internal/xml_serialization.rb +9 -45
- data/lib/dommy/js/constructor_resolver.rb +18 -0
- data/lib/dommy/js/custom_element_bridge.rb +57 -82
- data/lib/dommy/js/dom_interfaces.rb +51 -15
- data/lib/dommy/js/host_bridge.rb +189 -40
- data/lib/dommy/js/host_callbacks.rb +10 -0
- data/lib/dommy/js/host_runtime.js +1921 -416
- data/lib/dommy/js/marshaller.rb +13 -4
- data/lib/dommy/js/module_loader.rb +46 -5
- data/lib/dommy/js/module_preload.rb +194 -0
- data/lib/dommy/js/runtime.rb +9 -0
- data/lib/dommy/js/script_boot.rb +200 -114
- data/lib/dommy/js/serialized_record.rb +32 -0
- data/lib/dommy/js/webidl_event_handlers.js +45 -0
- data/lib/dommy/js/webidl_members.js +196 -0
- data/lib/dommy/js/webidl_signatures.js +2055 -0
- data/lib/dommy/js/webidl_tables.js +72 -101
- data/lib/dommy/js/window_wiring.rb +9 -5
- data/lib/dommy/location.rb +129 -76
- data/lib/dommy/mathml_elements.rb +13 -0
- data/lib/dommy/media_query_list.rb +4 -13
- data/lib/dommy/message_channel.rb +137 -64
- data/lib/dommy/mutation_observer.rb +3 -7
- data/lib/dommy/navigation.rb +61 -11
- data/lib/dommy/navigator.rb +132 -10
- data/lib/dommy/node.rb +81 -44
- data/lib/dommy/notification.rb +10 -6
- data/lib/dommy/parser.rb +5 -16
- data/lib/dommy/promise.rb +20 -26
- data/lib/dommy/range.rb +20 -47
- data/lib/dommy/resources.rb +38 -3
- data/lib/dommy/scheduler.rb +176 -48
- data/lib/dommy/shadow_root.rb +125 -25
- data/lib/dommy/storage.rb +135 -25
- data/lib/dommy/streams.rb +2 -6
- data/lib/dommy/structured_data.rb +45 -0
- data/lib/dommy/style_declaration.rb +38 -6
- data/lib/dommy/svg_elements.rb +25 -11
- data/lib/dommy/text_codec.rb +2 -14
- data/lib/dommy/tree_walker.rb +12 -26
- data/lib/dommy/url.rb +12 -9
- data/lib/dommy/url_pattern.rb +1 -9
- data/lib/dommy/validity_state.rb +109 -107
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +10 -33
- data/lib/dommy/window.rb +817 -105
- data/lib/dommy/worker.rb +4 -28
- data/lib/dommy/xml_http_request.rb +32 -45
- data/lib/dommy.rb +43 -10
- metadata +52 -5
- data/lib/dommy/backend/makiri_adapter.rb +0 -408
- data/lib/dommy/internal/cookie_jar.rb +0 -29
|
@@ -7,14 +7,20 @@ module Dommy
|
|
|
7
7
|
module CallableInvoker
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
def
|
|
14
|
-
return if callback.nil?
|
|
10
|
+
# A callback the page passed from JS — a function, or a NodeFilter
|
|
11
|
+
# object. Told by type, not by answering __js_call__: every host object
|
|
12
|
+
# answers that, for its own methods.
|
|
13
|
+
def js_callable?(callback) = callback.is_a?(Js::Callable)
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
# Something #invoke can call: a JS callable, or a Ruby one.
|
|
16
|
+
def callable?(callback) = js_callable?(callback) || callback.respond_to?(:call)
|
|
17
|
+
|
|
18
|
+
# Invoke `callback` with `args`, a JS throw swallowed by the bridge. `this:`
|
|
19
|
+
# is the receiver a JS function sees (a Ruby callable has none). A nil or
|
|
20
|
+
# non-callable callback is a no-op (returns nil).
|
|
21
|
+
def invoke(callback, *args, this: nil)
|
|
22
|
+
if js_callable?(callback)
|
|
23
|
+
callback.__js_invoke__(args, this: this)
|
|
18
24
|
elsif callback.respond_to?(:call)
|
|
19
25
|
callback.call(*args)
|
|
20
26
|
end
|
|
@@ -23,41 +29,31 @@ module Dommy
|
|
|
23
29
|
# Invoke `callback` so a JS throw SURFACES as a Ruby exception instead of
|
|
24
30
|
# being swallowed by the bridge. WHATWG requires a task's exception to be
|
|
25
31
|
# reported at the global ("report an exception") rather than silently
|
|
26
|
-
# dropped, so the entry points that own a task — a timer / rAF callback
|
|
27
|
-
# invoke this form and
|
|
28
|
-
#
|
|
29
|
-
def invoke_raising(callback, *args)
|
|
30
|
-
return if callback
|
|
32
|
+
# dropped, so the entry points that own a task — a timer / rAF callback, a
|
|
33
|
+
# promise reaction, a NodeFilter — invoke this form and handle whatever
|
|
34
|
+
# comes out. A Ruby callable raises naturally.
|
|
35
|
+
def invoke_raising(callback, *args, this: nil)
|
|
36
|
+
return callback.__js_invoke__(args, this: this, raising: true) if js_callable?(callback)
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
callback.__js_invoke__(args, raising: true)
|
|
34
|
-
else
|
|
35
|
-
invoke(callback, *args)
|
|
36
|
-
end
|
|
38
|
+
invoke(callback, *args)
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
# Invoke a DOM event listener per the EventTarget rule: an object with
|
|
40
|
-
# `handle_event`, else a Ruby callable, else a JS
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# handleEvent always receives the event.
|
|
42
|
+
# `handle_event`, else a Ruby callable, else a JS function (tried in that
|
|
43
|
+
# order). A JS function listener's `this` is the event's currentTarget,
|
|
44
|
+
# and its throw surfaces so the dispatch reports it as a window `error`
|
|
45
|
+
# event. `args:` overrides the single-event argument list — the special
|
|
46
|
+
# error event handler (`window.onerror`) is called with (message,
|
|
47
|
+
# filename, lineno, colno, error) instead of the event. An EventListener
|
|
48
|
+
# object's handleEvent always receives the event.
|
|
48
49
|
def invoke_listener(listener, event, current_target = nil, args: nil)
|
|
49
50
|
args ||= [event]
|
|
50
51
|
if listener.respond_to?(:handle_event)
|
|
51
52
|
listener.handle_event(event)
|
|
52
53
|
elsif listener.respond_to?(:call) && !listener.is_a?(Module)
|
|
53
54
|
listener.call(*args)
|
|
54
|
-
elsif
|
|
55
|
-
# A JS function listener: `this` is the currentTarget, and a thrown value
|
|
56
|
-
# surfaces (as a ThrowValue) so the dispatch reports it as a window
|
|
57
|
-
# `error` event instead of swallowing it.
|
|
55
|
+
elsif js_callable?(listener)
|
|
58
56
|
listener.__js_invoke__(args, this: current_target, raising: true)
|
|
59
|
-
elsif listener.respond_to?(:__js_call__)
|
|
60
|
-
listener.__js_call__("call", args)
|
|
61
57
|
end
|
|
62
58
|
end
|
|
63
59
|
end
|
data/lib/dommy/character_data.rb
CHANGED
|
@@ -89,9 +89,10 @@ module Dommy
|
|
|
89
89
|
@__node__.content
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
# Assigning `data`, `nodeValue` or `textContent` is "replace data" over
|
|
93
|
+
# the whole node, so a live range boundary inside it clamps to the start
|
|
94
|
+
# rather than dangling past the end.
|
|
92
95
|
def data=(value)
|
|
93
|
-
# Assigning `data` is "replace data" over the whole node, so a live range
|
|
94
|
-
# boundary inside it clamps to the start rather than dangling past the end.
|
|
95
96
|
old_length = utf16_length(@__node__.content)
|
|
96
97
|
write_data(value)
|
|
97
98
|
@document.__internal_ranges_replaced_data__(self, 0, old_length, utf16_length(@__node__.content))
|
|
@@ -102,7 +103,7 @@ module Dommy
|
|
|
102
103
|
end
|
|
103
104
|
|
|
104
105
|
def node_value=(value)
|
|
105
|
-
|
|
106
|
+
self.data = value
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
def text_content
|
|
@@ -110,9 +111,16 @@ module Dommy
|
|
|
110
111
|
end
|
|
111
112
|
|
|
112
113
|
def text_content=(value)
|
|
113
|
-
|
|
114
|
+
self.data = value
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Node.isConnected: whether the shadow-including root is a Document.
|
|
118
|
+
def is_connected?
|
|
119
|
+
Internal::NodeTraversal.connected?(@__node__, @document)
|
|
114
120
|
end
|
|
115
121
|
|
|
122
|
+
alias connected? is_connected?
|
|
123
|
+
|
|
116
124
|
def remove
|
|
117
125
|
@document.remove_node_with_notify(@__node__)
|
|
118
126
|
nil
|
|
@@ -178,29 +186,13 @@ module Dommy
|
|
|
178
186
|
def substring_data(offset, count)
|
|
179
187
|
s = @__node__.content
|
|
180
188
|
len = utf16_length(s)
|
|
181
|
-
o =
|
|
189
|
+
o = Internal::WebIDL.unsigned_long(offset)
|
|
182
190
|
raise DOMException::IndexSizeError, "offset out of bounds" if o > len
|
|
183
191
|
|
|
184
|
-
c = [
|
|
192
|
+
c = [Internal::WebIDL.unsigned_long(count), len - o].min
|
|
185
193
|
utf16_slice(s, o, c)
|
|
186
194
|
end
|
|
187
195
|
|
|
188
|
-
# ECMAScript ToUint32 — WebIDL `unsigned long` conversion for a data offset
|
|
189
|
-
# or count: ToNumber (a non-numeric string is NaN), truncate toward zero,
|
|
190
|
-
# then take modulo 2**32 (so -1 wraps to 4294967295, 0x100000000+2 to 2).
|
|
191
|
-
def to_uint32(value)
|
|
192
|
-
num =
|
|
193
|
-
case value
|
|
194
|
-
when Integer then value
|
|
195
|
-
when Numeric then value
|
|
196
|
-
when nil then 0 # JS null -> 0
|
|
197
|
-
else Float(value.to_s) rescue Float::NAN
|
|
198
|
-
end
|
|
199
|
-
return 0 unless num.respond_to?(:finite?) ? num.finite? : true
|
|
200
|
-
|
|
201
|
-
num.to_i % (2**32)
|
|
202
|
-
end
|
|
203
|
-
|
|
204
196
|
def append_data(value)
|
|
205
197
|
write_data(@__node__.content + dom_string(value))
|
|
206
198
|
end
|
|
@@ -222,10 +214,10 @@ module Dommy
|
|
|
222
214
|
def replace_data(offset, count, value)
|
|
223
215
|
s = @__node__.content
|
|
224
216
|
len = utf16_length(s)
|
|
225
|
-
o =
|
|
217
|
+
o = Internal::WebIDL.unsigned_long(offset)
|
|
226
218
|
raise DOMException::IndexSizeError, "offset out of bounds" if o > len
|
|
227
219
|
|
|
228
|
-
c = [
|
|
220
|
+
c = [Internal::WebIDL.unsigned_long(count), len - o].min
|
|
229
221
|
inserted = dom_string(value)
|
|
230
222
|
write_data(utf16_slice(s, 0, o) + inserted + utf16_slice(s, o + c, len - (o + c)))
|
|
231
223
|
# Live ranges whose boundary sits in (or past) the replaced run follow it.
|
|
@@ -256,6 +248,8 @@ module Dommy
|
|
|
256
248
|
next_sibling
|
|
257
249
|
when "previousSibling"
|
|
258
250
|
previous_sibling
|
|
251
|
+
when "isConnected"
|
|
252
|
+
is_connected?
|
|
259
253
|
when "childNodes"
|
|
260
254
|
# CharacterData is a leaf node: childNodes is always an empty (but
|
|
261
255
|
# present and iterable) NodeList, and firstChild/lastChild are null.
|
|
@@ -276,7 +270,7 @@ module Dommy
|
|
|
276
270
|
# element's shadow tree; a closed shadow tree hides the assignment (null).
|
|
277
271
|
def assigned_slot
|
|
278
272
|
parent = @__node__.parent
|
|
279
|
-
return nil unless parent
|
|
273
|
+
return nil unless parent&.element?
|
|
280
274
|
|
|
281
275
|
host = @document.wrap_node(parent)
|
|
282
276
|
return nil unless host.respond_to?(:shadow_root)
|
|
@@ -293,7 +287,7 @@ module Dommy
|
|
|
293
287
|
def __js_set__(key, value)
|
|
294
288
|
case key
|
|
295
289
|
when "textContent", "data", "nodeValue"
|
|
296
|
-
|
|
290
|
+
self.data = value
|
|
297
291
|
end
|
|
298
292
|
|
|
299
293
|
nil
|
|
@@ -312,8 +306,7 @@ module Dommy
|
|
|
312
306
|
false
|
|
313
307
|
when "contains"
|
|
314
308
|
# A leaf node contains only itself (no descendants).
|
|
315
|
-
args[0].
|
|
316
|
-
args[0].__dommy_backend_node__ == @__node__
|
|
309
|
+
args[0].is_a?(Node) && args[0].__dommy_backend_node__ == @__node__
|
|
317
310
|
when "appendChild"
|
|
318
311
|
append_child(args[0])
|
|
319
312
|
when "insertBefore"
|
data/lib/dommy/class_list.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "internal/infra"
|
|
4
|
+
|
|
3
5
|
module Dommy
|
|
4
6
|
# DOMTokenList (`classList`, `relList`) and DOMStringMap (`dataset`) —
|
|
5
7
|
# the two live views over one attribute's text.
|
|
@@ -10,9 +12,13 @@ module Dommy
|
|
|
10
12
|
|
|
11
13
|
# `attribute` is the content attribute this token list reflects ("class" for
|
|
12
14
|
# `classList`, "rel" for `relList`, "sandbox", "sizes", "for", …).
|
|
13
|
-
|
|
15
|
+
# `supported_tokens` is what DOM calls the element and attribute name's
|
|
16
|
+
# supported tokens (lowercase), or nil when they define none — see
|
|
17
|
+
# Internal::SupportedTokens.
|
|
18
|
+
def initialize(element, attribute = "class", supported_tokens = nil)
|
|
14
19
|
@element = element
|
|
15
20
|
@attribute = attribute
|
|
21
|
+
@supported_tokens = supported_tokens
|
|
16
22
|
end
|
|
17
23
|
|
|
18
24
|
def length
|
|
@@ -29,11 +35,11 @@ module Dommy
|
|
|
29
35
|
end
|
|
30
36
|
|
|
31
37
|
def value
|
|
32
|
-
|
|
38
|
+
associated_value.to_s
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
def value=(new_value)
|
|
36
|
-
|
|
42
|
+
write(new_value.to_s)
|
|
37
43
|
end
|
|
38
44
|
|
|
39
45
|
# Spec: contains() does NOT validate (no SyntaxError on empty).
|
|
@@ -49,6 +55,16 @@ module Dommy
|
|
|
49
55
|
class_tokens.include?(token.to_s)
|
|
50
56
|
end
|
|
51
57
|
|
|
58
|
+
# DOMTokenList's supports(token), which runs the validation steps: a
|
|
59
|
+
# TypeError when the element and attribute define no supported tokens
|
|
60
|
+
# (`classList.supports("x")`), else whether the token, ASCII-lowercased,
|
|
61
|
+
# is one of them (`a.relList.supports("NoOpener")` is true).
|
|
62
|
+
def supports?(token)
|
|
63
|
+
raise Bridge::TypeError, "DOMTokenList has no supported tokens" if @supported_tokens.nil?
|
|
64
|
+
|
|
65
|
+
@supported_tokens.include?(stringify_token(token).downcase(:ascii))
|
|
66
|
+
end
|
|
67
|
+
|
|
52
68
|
def add(*tokens)
|
|
53
69
|
update_tokens { |existing| existing | normalize_tokens(tokens) }
|
|
54
70
|
nil
|
|
@@ -79,7 +95,7 @@ module Dommy
|
|
|
79
95
|
# raw attribute string, which would then hand stale tokens to later reads).
|
|
80
96
|
updated = tokens.dup
|
|
81
97
|
updated[idx] = new_s
|
|
82
|
-
|
|
98
|
+
write(updated.uniq.join(" "))
|
|
83
99
|
true
|
|
84
100
|
end
|
|
85
101
|
|
|
@@ -130,10 +146,7 @@ module Dommy
|
|
|
130
146
|
end
|
|
131
147
|
|
|
132
148
|
include Bridge::Methods
|
|
133
|
-
|
|
134
|
-
# list it must throw a TypeError, which `list.supports(...)` (not a function)
|
|
135
|
-
# already does.
|
|
136
|
-
js_methods %w[add remove contains toggle replace item toString]
|
|
149
|
+
js_methods %w[add remove contains toggle replace item supports toString]
|
|
137
150
|
def __js_call__(method, args)
|
|
138
151
|
case method
|
|
139
152
|
when "add"
|
|
@@ -146,11 +159,13 @@ module Dommy
|
|
|
146
159
|
# contains() does not validate; null coerces to the string "null".
|
|
147
160
|
class_tokens.include?(stringify_token(args[0]))
|
|
148
161
|
when "toggle"
|
|
149
|
-
toggle(args[0], args
|
|
162
|
+
toggle(args[0], args.fetch(1, Bridge::UNDEFINED))
|
|
150
163
|
when "replace"
|
|
151
164
|
replace(args[0], args[1])
|
|
152
165
|
when "item"
|
|
153
166
|
item(args[0])
|
|
167
|
+
when "supports"
|
|
168
|
+
supports?(args[0])
|
|
154
169
|
when "toString"
|
|
155
170
|
value
|
|
156
171
|
else
|
|
@@ -160,16 +175,18 @@ module Dommy
|
|
|
160
175
|
|
|
161
176
|
private
|
|
162
177
|
|
|
163
|
-
|
|
178
|
+
# `optional boolean force`: missing (or undefined) toggles; anything
|
|
179
|
+
# else, null included, is a boolean read with ToBoolean.
|
|
180
|
+
def toggle(token, force = Bridge::UNDEFINED)
|
|
164
181
|
name = validate_token(token)
|
|
165
182
|
present = class_tokens.include?(name)
|
|
166
|
-
force_given = !
|
|
183
|
+
force_given = !force.equal?(Bridge::UNDEFINED)
|
|
167
184
|
|
|
168
185
|
# Spec: toggle runs the update steps only when it actually adds or removes.
|
|
169
186
|
# With an explicit force that already matches the current state it's a
|
|
170
187
|
# no-op — the attribute is left byte-for-byte untouched (no re-serialize).
|
|
171
188
|
if force_given
|
|
172
|
-
want =
|
|
189
|
+
want = Internal::WebIDL.boolean(force)
|
|
173
190
|
return want if want == present
|
|
174
191
|
|
|
175
192
|
update_tokens { |tokens| want ? tokens | [name] : tokens - [name] }
|
|
@@ -207,7 +224,7 @@ module Dommy
|
|
|
207
224
|
# item, iteration, and contains all operate on this set; `value`/`toString`
|
|
208
225
|
# return the raw attribute. ASCII whitespace per the spec is space/tab/LF/FF/CR.
|
|
209
226
|
def class_tokens
|
|
210
|
-
raw =
|
|
227
|
+
raw = associated_value.to_s
|
|
211
228
|
# Cache the parsed token list keyed by the raw attribute string: a class
|
|
212
229
|
# selector match re-reads this for every element on every querySelector,
|
|
213
230
|
# and the split/reject/uniq dominated heavy-SPA load profiles. The key is
|
|
@@ -216,7 +233,7 @@ module Dommy
|
|
|
216
233
|
cached = @token_cache
|
|
217
234
|
return cached[1] if cached && cached[0] == raw
|
|
218
235
|
|
|
219
|
-
tokens =
|
|
236
|
+
tokens = Internal::Infra.split_on_ascii_whitespace(raw).uniq
|
|
220
237
|
@token_cache = [raw, tokens]
|
|
221
238
|
tokens
|
|
222
239
|
end
|
|
@@ -227,9 +244,22 @@ module Dommy
|
|
|
227
244
|
# (per spec) is an empty set with no existing attribute — don't create one.
|
|
228
245
|
def update_tokens
|
|
229
246
|
tokens = yield(class_tokens)
|
|
230
|
-
return if tokens.empty? &&
|
|
247
|
+
return if tokens.empty? && associated_value.nil?
|
|
248
|
+
|
|
249
|
+
write(tokens.join(" "))
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# The associated attribute is the one of that local name in no namespace
|
|
253
|
+
# (DOM's "get an attribute by namespace and local name"): a `class` set
|
|
254
|
+
# with setAttributeNS("urn:x", "class") is not the element's classes.
|
|
255
|
+
def associated_value
|
|
256
|
+
Backend.no_namespace_attribute_value(@element.__dommy_backend_node__, @attribute)
|
|
257
|
+
end
|
|
231
258
|
|
|
232
|
-
|
|
259
|
+
# The same attribute, written as every other reflection writes one: its
|
|
260
|
+
# name is a fixed, valid local name, so there is nothing to validate.
|
|
261
|
+
def write(value)
|
|
262
|
+
@element.__internal_set_attribute_value__(@attribute, value)
|
|
233
263
|
end
|
|
234
264
|
end
|
|
235
265
|
|
|
@@ -247,7 +277,7 @@ module Dommy
|
|
|
247
277
|
|
|
248
278
|
# A missing data-* attribute reads as JS `undefined` (and `"foo" in dataset`
|
|
249
279
|
# is false), per DOMStringMap semantics.
|
|
250
|
-
value = @element.
|
|
280
|
+
value = @element.__internal_attribute_value__(attr_name(name))
|
|
251
281
|
value.nil? ? Bridge::ABSENT : value
|
|
252
282
|
end
|
|
253
283
|
|
|
@@ -260,7 +290,7 @@ module Dommy
|
|
|
260
290
|
raise DOMException::InvalidCharacterError, "#{attribute.inspect} is not a valid attribute name"
|
|
261
291
|
end
|
|
262
292
|
|
|
263
|
-
@element.
|
|
293
|
+
@element.__internal_set_attribute_value__(attribute, value.to_s)
|
|
264
294
|
nil
|
|
265
295
|
end
|
|
266
296
|
|
|
@@ -271,7 +301,7 @@ module Dommy
|
|
|
271
301
|
name = key.to_s
|
|
272
302
|
return true unless supported_name?(name)
|
|
273
303
|
|
|
274
|
-
@element.
|
|
304
|
+
@element.remove_attribute_ns(nil, attr_name(name))
|
|
275
305
|
true
|
|
276
306
|
end
|
|
277
307
|
|
|
@@ -280,12 +310,14 @@ module Dommy
|
|
|
280
310
|
end
|
|
281
311
|
|
|
282
312
|
# WebIDL "supported property names" for DOMStringMap: each `data-*`
|
|
283
|
-
# attribute
|
|
284
|
-
# camel-cased (`data-date-of-birth` → `dateOfBirth`,
|
|
313
|
+
# attribute in no namespace, its name with the `data-` prefix stripped
|
|
314
|
+
# and `-x` sequences camel-cased (`data-date-of-birth` → `dateOfBirth`,
|
|
315
|
+
# `data-` → ``).
|
|
285
316
|
def __js_named_props__
|
|
286
317
|
Backend.attribute_nodes(@element.__dommy_backend_node__).filter_map do |a|
|
|
287
|
-
|
|
288
|
-
|
|
318
|
+
info = Backend.attribute_ns_info(a)
|
|
319
|
+
name = info[:local_name]
|
|
320
|
+
next unless info[:namespace_uri].nil? && name.start_with?("data-")
|
|
289
321
|
|
|
290
322
|
name.sub(/\Adata-/, "").gsub(/-([a-z])/) { ::Regexp.last_match(1).upcase }
|
|
291
323
|
end
|