dommy 0.13.0 → 0.14.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/lib/dommy/backend/makiri_adapter.rb +63 -29
- data/lib/dommy/backend.rb +32 -9
- data/lib/dommy/bridge/constructor.rb +72 -0
- data/lib/dommy/bridge/errors.rb +42 -0
- data/lib/dommy/bridge/methods.rb +21 -14
- data/lib/dommy/bridge/sentinels.rb +39 -0
- data/lib/dommy/bridge/values.rb +78 -0
- data/lib/dommy/{js → bridge}/wire_tags.rb +14 -11
- data/lib/dommy/bridge.rb +22 -250
- data/lib/dommy/browser.rb +112 -11
- data/lib/dommy/character_data.rb +539 -0
- data/lib/dommy/class_list.rb +311 -0
- data/lib/dommy/crypto.rb +12 -1
- data/lib/dommy/css.rb +12 -0
- data/lib/dommy/data_transfer.rb +106 -0
- data/lib/dommy/document.rb +307 -369
- data/lib/dommy/dom_parser.rb +3 -0
- data/lib/dommy/element.rb +199 -1817
- data/lib/dommy/encodings/tables.rb +4 -4
- data/lib/dommy/encodings.rb +58 -27
- data/lib/dommy/event.rb +227 -1
- data/lib/dommy/event_source.rb +41 -0
- data/lib/dommy/form_data.rb +6 -64
- data/lib/dommy/form_entry_list.rb +227 -0
- data/lib/dommy/fragment.rb +320 -0
- data/lib/dommy/history.rb +3 -0
- data/lib/dommy/html_collection.rb +18 -8
- data/lib/dommy/html_elements/base.rb +338 -0
- data/lib/dommy/html_elements/embedded.rb +237 -0
- data/lib/dommy/html_elements/forms/form.rb +1088 -0
- data/lib/dommy/html_elements/forms/input.rb +642 -0
- data/lib/dommy/html_elements/forms/select.rb +536 -0
- data/lib/dommy/html_elements/forms.rb +8 -0
- data/lib/dommy/html_elements/interactive.rb +382 -0
- data/lib/dommy/html_elements/media.rb +329 -0
- data/lib/dommy/html_elements/metadata.rb +353 -0
- data/lib/dommy/html_elements/miscellaneous.rb +77 -0
- data/lib/dommy/html_elements/tables.rb +468 -0
- data/lib/dommy/html_elements.rb +11 -5322
- data/lib/dommy/hyperlink_utils.rb +171 -0
- data/lib/dommy/interaction/debug.rb +7 -6
- data/lib/dommy/interaction/dom_summary.rb +65 -40
- data/lib/dommy/interaction/driver.rb +29 -115
- data/lib/dommy/interaction/event_synthesis.rb +97 -4
- data/lib/dommy/interaction/field_interactor.rb +1 -13
- data/lib/dommy/interaction/form_submission.rb +89 -167
- data/lib/dommy/interaction/key_sender.rb +121 -0
- data/lib/dommy/interaction/mime_types.rb +25 -0
- data/lib/dommy/interaction.rb +2 -0
- data/lib/dommy/internal/accessibility_tree.rb +32 -9
- data/lib/dommy/internal/accessibility_visibility.rb +44 -0
- data/lib/dommy/internal/accessible_name.rb +85 -65
- data/lib/dommy/internal/aria_role.rb +21 -6
- data/lib/dommy/internal/aria_snapshot.rb +5 -0
- data/lib/dommy/internal/aria_state.rb +24 -5
- data/lib/dommy/internal/autofill.rb +141 -0
- data/lib/dommy/internal/backend_prefilter.rb +170 -0
- data/lib/dommy/internal/bounded_cache.rb +50 -0
- data/lib/dommy/internal/child_node.rb +9 -34
- data/lib/dommy/internal/css/calc.rb +143 -145
- data/lib/dommy/internal/css/cascade.rb +38 -393
- data/lib/dommy/internal/css/cascaded_declarations.rb +245 -0
- data/lib/dommy/internal/css/color.rb +61 -34
- data/lib/dommy/internal/css/computed_style_builder.rb +205 -0
- data/lib/dommy/internal/css/computed_style_declaration.rb +20 -4
- data/lib/dommy/internal/css/counters.rb +60 -37
- data/lib/dommy/internal/css/custom_properties.rb +87 -39
- data/lib/dommy/internal/css/media_query.rb +228 -203
- data/lib/dommy/internal/css/parser.rb +29 -12
- data/lib/dommy/internal/css/property_registry.rb +32 -56
- data/lib/dommy/internal/css/renderability.rb +61 -0
- data/lib/dommy/internal/css/rule_index.rb +17 -140
- data/lib/dommy/internal/css/selector_dependencies.rb +162 -0
- data/lib/dommy/internal/css/style_cache.rb +61 -0
- data/lib/dommy/internal/css/ua_stylesheet.rb +40 -1
- data/lib/dommy/internal/css/value_tokens.rb +60 -0
- data/lib/dommy/internal/directionality.rb +188 -0
- data/lib/dommy/internal/document_generations.rb +169 -0
- data/lib/dommy/internal/document_interaction_state.rb +47 -0
- data/lib/dommy/internal/document_live_ranges.rb +97 -0
- data/lib/dommy/internal/dom_matching.rb +28 -12
- data/lib/dommy/internal/element_aria.rb +163 -0
- data/lib/dommy/internal/element_geometry.rb +69 -0
- data/lib/dommy/internal/element_matching.rb +5 -12
- data/lib/dommy/internal/element_shadow.rb +117 -0
- data/lib/dommy/internal/element_state.rb +232 -0
- data/lib/dommy/internal/element_tasks.rb +19 -0
- data/lib/dommy/internal/element_top_layer.rb +63 -0
- data/lib/dommy/internal/enumerated_keyword_sets.rb +53 -0
- data/lib/dommy/internal/exception_report.rb +1 -1
- data/lib/dommy/internal/input_type.rb +280 -0
- data/lib/dommy/internal/insertion_point.rb +62 -0
- data/lib/dommy/internal/mime_type.rb +74 -0
- data/lib/dommy/internal/mutation_coordinator.rb +57 -198
- data/lib/dommy/internal/namespaces.rb +41 -43
- data/lib/dommy/internal/node_adopter.rb +84 -77
- data/lib/dommy/internal/node_factory.rb +157 -0
- data/lib/dommy/internal/node_identity.rb +31 -0
- data/lib/dommy/internal/node_wrapper_cache.rb +61 -207
- data/lib/dommy/internal/observer_options.rb +93 -0
- data/lib/dommy/internal/parent_node.rb +27 -27
- data/lib/dommy/internal/post_insertion_steps.rb +213 -0
- data/lib/dommy/internal/reflected_attributes.rb +425 -13
- data/lib/dommy/internal/rendered_text/collector.rb +221 -0
- data/lib/dommy/internal/rendered_text/computed_box.rb +39 -0
- data/lib/dommy/internal/rendered_text/fragment.rb +55 -0
- data/lib/dommy/internal/rendered_text/items.rb +57 -0
- data/lib/dommy/internal/rendered_text/whitespace_collapser.rb +132 -0
- data/lib/dommy/internal/selector_arguments.rb +94 -0
- data/lib/dommy/internal/selector_index.rb +3 -1
- data/lib/dommy/internal/selector_matcher.rb +23 -383
- data/lib/dommy/internal/selector_parser.rb +2 -37
- data/lib/dommy/internal/template_content_registry.rb +112 -66
- data/lib/dommy/internal/text_flattening.rb +20 -0
- data/lib/dommy/internal/text_selection.rb +104 -0
- data/lib/dommy/internal/toggle_events.rb +56 -0
- data/lib/dommy/internal/toggle_task_tracker.rb +57 -0
- data/lib/dommy/internal/url_parser.rb +722 -569
- data/lib/dommy/internal/url_record_accessors.rb +90 -0
- data/lib/dommy/internal/window_constructors.rb +190 -0
- data/lib/dommy/internal/xml_serialization.rb +168 -80
- data/lib/dommy/js/crossing_profile.rb +75 -0
- data/lib/dommy/js/dom_interfaces.rb +22 -2
- data/lib/dommy/js/host_bridge.rb +43 -187
- data/lib/dommy/js/host_callbacks.rb +96 -0
- data/lib/dommy/js/host_runtime.js +840 -763
- data/lib/dommy/js/import_map.rb +10 -1
- data/lib/dommy/js/marshaller.rb +94 -65
- data/lib/dommy/js/observable_runtime.js +125 -146
- data/lib/dommy/js/runtime.rb +10 -43
- data/lib/dommy/js/runtime_registry.rb +46 -0
- data/lib/dommy/js/script_boot.rb +38 -73
- data/lib/dommy/js/webidl_tables.js +619 -0
- data/lib/dommy/js/window_wiring.rb +69 -0
- data/lib/dommy/location.rb +152 -103
- data/lib/dommy/mutation_observer.rb +3 -57
- data/lib/dommy/navigation.rb +65 -12
- data/lib/dommy/node.rb +46 -20
- data/lib/dommy/promise.rb +58 -4
- data/lib/dommy/resources.rb +19 -1
- data/lib/dommy/shadow_root.rb +11 -5
- data/lib/dommy/style_declaration.rb +284 -0
- data/lib/dommy/svg_elements.rb +1 -45
- data/lib/dommy/url.rb +20 -69
- data/lib/dommy/url_pattern/regexp_translator.rb +1 -6
- data/lib/dommy/url_pattern/tokenizer.rb +4 -2
- data/lib/dommy/validity_state.rb +383 -0
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/window.rb +30 -166
- data/lib/dommy/xml_http_request.rb +35 -4
- data/lib/dommy.rb +23 -3
- metadata +74 -7
- data/lib/dommy/bridge/constructor_registry.rb +0 -28
- data/lib/dommy/internal/css_pseudo_handlers.rb +0 -378
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dommy
|
|
4
|
+
# Text, comments, CDATA sections and processing instructions —
|
|
5
|
+
# the nodes whose content is a string rather than a subtree.
|
|
6
|
+
#
|
|
7
|
+
# Lived in element.rb, which is for Element.
|
|
8
|
+
# CharacterData base — TextNode and CommentNode share the data /
|
|
9
|
+
# nodeValue / textContent API and `remove` / `cloneNode` semantics.
|
|
10
|
+
class CharacterDataNode
|
|
11
|
+
include Node
|
|
12
|
+
include EventTarget
|
|
13
|
+
# `before` / `after` / `replaceWith` (+ their argument coercion) — the same
|
|
14
|
+
# spec-correct implementation Element uses, minus appendChild/insertBefore.
|
|
15
|
+
include Internal::ChildNode
|
|
16
|
+
include Internal::LeafNode
|
|
17
|
+
|
|
18
|
+
# The owning Dommy document (as Element exposes), so cross-document adoption
|
|
19
|
+
# checks work for text/comment nodes too.
|
|
20
|
+
attr_reader :document
|
|
21
|
+
|
|
22
|
+
def __dommy_backend_node__ = @__node__
|
|
23
|
+
|
|
24
|
+
# EventTarget needs a parent for event propagation; a character-data node
|
|
25
|
+
# bubbles to its parent element.
|
|
26
|
+
def __internal_event_parent__
|
|
27
|
+
@__node__.parent && @document.wrap_node(@__node__.parent)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Text.splitText / CharacterData split: break the node at `offset` (a UTF-16
|
|
31
|
+
# code unit index), keeping [0, offset) here and returning a new sibling node
|
|
32
|
+
# with the remainder.
|
|
33
|
+
def split_text(offset)
|
|
34
|
+
off = offset.to_i
|
|
35
|
+
full = @__node__.content
|
|
36
|
+
length = utf16_length(full)
|
|
37
|
+
raise DOMException::IndexSizeError, "offset #{off} is out of bounds" if off.negative? || off > length
|
|
38
|
+
|
|
39
|
+
count = length - off
|
|
40
|
+
new_node = @document.create_text_node(utf16_slice(full, off, count))
|
|
41
|
+
new_bn = new_node.__dommy_backend_node__
|
|
42
|
+
parent = @__node__.parent
|
|
43
|
+
if parent
|
|
44
|
+
# Step 7.1 — insert the new node right after self. Its live range steps
|
|
45
|
+
# run here, where the algorithm puts them; its childList RECORD is
|
|
46
|
+
# queued at the very end instead (see below).
|
|
47
|
+
# WHATWG "split a Text node" step 7.1 inserts the new node right after
|
|
48
|
+
# self, so insert step 5 runs first, against self's next sibling.
|
|
49
|
+
@document.__internal_ranges_will_insert__(parent, @__node__.next, 1)
|
|
50
|
+
@__node__.add_next_sibling(new_bn)
|
|
51
|
+
# Live ranges past the split point move to the tail node (the generic
|
|
52
|
+
# insert step above already shifted boundaries sitting further along).
|
|
53
|
+
# Step 7 only runs for a node that HAS a parent: splitting a detached
|
|
54
|
+
# node leaves every boundary on the node itself, to be clamped by the
|
|
55
|
+
# truncation below.
|
|
56
|
+
@document.__internal_ranges_split_text__(self, off, new_node)
|
|
57
|
+
end
|
|
58
|
+
# Step 8 — "replace data with node, offset, count, the empty string": the
|
|
59
|
+
# truncation is a data replacement, so it carries the replace-data live
|
|
60
|
+
# range rules (the ones that clamp a boundary in a detached node).
|
|
61
|
+
write_data(utf16_slice(full, 0, off))
|
|
62
|
+
@document.__internal_ranges_replaced_data__(self, off, count, 0)
|
|
63
|
+
# The one place Dommy queues MutationRecords out of algorithm order. Read
|
|
64
|
+
# literally, the insertion (step 7.1) precedes the data replacement (step
|
|
65
|
+
# 8), so the records would be childList then characterData. Every shipping
|
|
66
|
+
# engine emits them the other way round: Blink's Text::splitText calls
|
|
67
|
+
# DidModifyData before InsertBefore, WebCore matches it, and Gecko's
|
|
68
|
+
# Text::SplitText not only matches but says in a comment that nsRange
|
|
69
|
+
# DEPENDS on the data notification preceding the insertion. Confirmed by
|
|
70
|
+
# running the same script in all three — Chromium 141, WebKitGTK 2.52.6
|
|
71
|
+
# and Firefox all answer [characterData, childList], while all three
|
|
72
|
+
# answer [childList, characterData] for those same two mutations performed
|
|
73
|
+
# explicitly, so this is specific to splitText and not a general
|
|
74
|
+
# reordering. Only the record order moves: the tree and every live range
|
|
75
|
+
# boundary still follow the steps exactly as written.
|
|
76
|
+
# https://github.com/takahashim/dommy/issues/23
|
|
77
|
+
@document.notify_child_list_mutation(target_node: parent, added_nodes: [new_bn], removed_nodes: []) if parent
|
|
78
|
+
new_node
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def initialize(document, nokogiri_node)
|
|
82
|
+
@document = document
|
|
83
|
+
@__node__ = nokogiri_node
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Snake_case facade (CRuby idiomatic)
|
|
87
|
+
|
|
88
|
+
def data
|
|
89
|
+
@__node__.content
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
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
|
+
old_length = utf16_length(@__node__.content)
|
|
96
|
+
write_data(value)
|
|
97
|
+
@document.__internal_ranges_replaced_data__(self, 0, old_length, utf16_length(@__node__.content))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def node_value
|
|
101
|
+
@__node__.content
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def node_value=(value)
|
|
105
|
+
write_data(value)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def text_content
|
|
109
|
+
@__node__.content
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def text_content=(value)
|
|
113
|
+
write_data(value)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def remove
|
|
117
|
+
@document.remove_node_with_notify(@__node__)
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def parent_node
|
|
122
|
+
@__node__.parent && @document.wrap_node(@__node__.parent)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# parentElement is the parent only when it is an element (a document or
|
|
126
|
+
# fragment parent is a parentNode but not a parentElement).
|
|
127
|
+
def parent_element
|
|
128
|
+
@document.wrap_node(@__node__.parent) if @__node__.parent&.element?
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def next_sibling
|
|
132
|
+
@__node__.next && @document.wrap_node(@__node__.next)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def previous_sibling
|
|
136
|
+
@__node__.previous && @document.wrap_node(@__node__.previous)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def [](key)
|
|
140
|
+
__js_get__(key.to_s)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def []=(key, value)
|
|
144
|
+
__js_set__(key.to_s, value)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# WHATWG nodeName for character-data nodes is a per-type constant
|
|
148
|
+
# ("#text" / "#comment" / "#cdata-section"), not the element name.
|
|
149
|
+
def node_name
|
|
150
|
+
case node_type
|
|
151
|
+
when 3 then "#text"
|
|
152
|
+
when 4 then "#cdata-section"
|
|
153
|
+
when 8 then "#comment"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# CharacterData length / mutation methods. Offsets and counts are UTF-16 code
|
|
158
|
+
# units per spec; for BMP text (the common case) Ruby char indices match.
|
|
159
|
+
# Each mutating op routes through write_data, which fires the characterData
|
|
160
|
+
# MutationObserver record.
|
|
161
|
+
|
|
162
|
+
def length
|
|
163
|
+
utf16_length(@__node__.content)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# CharacterData offsets and counts are measured in UTF-16 code units, not
|
|
167
|
+
# Unicode code points, so an astral character (e.g. an emoji) counts as 2.
|
|
168
|
+
# Range boundary offsets mean the same thing, so the conversion itself lives
|
|
169
|
+
# in Internal::Utf16 and both share it.
|
|
170
|
+
def utf16_length(str)
|
|
171
|
+
Internal::Utf16.length(str)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def utf16_slice(str, offset, count)
|
|
175
|
+
Internal::Utf16.slice(str, offset, count)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def substring_data(offset, count)
|
|
179
|
+
s = @__node__.content
|
|
180
|
+
len = utf16_length(s)
|
|
181
|
+
o = to_uint32(offset)
|
|
182
|
+
raise DOMException::IndexSizeError, "offset out of bounds" if o > len
|
|
183
|
+
|
|
184
|
+
c = [to_uint32(count), len - o].min
|
|
185
|
+
utf16_slice(s, o, c)
|
|
186
|
+
end
|
|
187
|
+
|
|
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
|
+
def append_data(value)
|
|
205
|
+
write_data(@__node__.content + dom_string(value))
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# WebIDL DOMString coercion for a CharacterData mutation argument: JS null
|
|
209
|
+
# becomes the string "null" (undefined already stringifies to "undefined").
|
|
210
|
+
def dom_string(value)
|
|
211
|
+
value.nil? ? "null" : value.to_s
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def insert_data(offset, value)
|
|
215
|
+
replace_data(offset, 0, value)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def delete_data(offset, count)
|
|
219
|
+
replace_data(offset, count, "")
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def replace_data(offset, count, value)
|
|
223
|
+
s = @__node__.content
|
|
224
|
+
len = utf16_length(s)
|
|
225
|
+
o = to_uint32(offset)
|
|
226
|
+
raise DOMException::IndexSizeError, "offset out of bounds" if o > len
|
|
227
|
+
|
|
228
|
+
c = [to_uint32(count), len - o].min
|
|
229
|
+
inserted = dom_string(value)
|
|
230
|
+
write_data(utf16_slice(s, 0, o) + inserted + utf16_slice(s, o + c, len - (o + c)))
|
|
231
|
+
# Live ranges whose boundary sits in (or past) the replaced run follow it.
|
|
232
|
+
@document.__internal_ranges_replaced_data__(self, o, c, utf16_length(inserted))
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def __js_get__(key)
|
|
236
|
+
case key
|
|
237
|
+
when "nodeType"
|
|
238
|
+
node_type
|
|
239
|
+
when "nodeName"
|
|
240
|
+
node_name
|
|
241
|
+
when "textContent"
|
|
242
|
+
@__node__.content
|
|
243
|
+
when "data"
|
|
244
|
+
@__node__.content
|
|
245
|
+
when "nodeValue"
|
|
246
|
+
@__node__.content
|
|
247
|
+
when "length"
|
|
248
|
+
length
|
|
249
|
+
when "parentNode"
|
|
250
|
+
parent_node
|
|
251
|
+
when "parentElement"
|
|
252
|
+
parent_element
|
|
253
|
+
when "ownerDocument"
|
|
254
|
+
@document
|
|
255
|
+
when "nextSibling"
|
|
256
|
+
next_sibling
|
|
257
|
+
when "previousSibling"
|
|
258
|
+
previous_sibling
|
|
259
|
+
when "childNodes"
|
|
260
|
+
# CharacterData is a leaf node: childNodes is always an empty (but
|
|
261
|
+
# present and iterable) NodeList, and firstChild/lastChild are null.
|
|
262
|
+
# DOM-walking code (e.g. idiomorph's morphChildren) iterates
|
|
263
|
+
# `node.childNodes` on every node, so a missing one crashes it.
|
|
264
|
+
NodeList.new
|
|
265
|
+
when "firstChild", "lastChild"
|
|
266
|
+
nil
|
|
267
|
+
when "assignedSlot"
|
|
268
|
+
assigned_slot
|
|
269
|
+
else
|
|
270
|
+
Bridge::ABSENT # unknown property: JS undefined, `in` absent
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Slottable mixin: the <slot> this text node is assigned to. A text node has
|
|
275
|
+
# no `slot` attribute, so it targets the default (unnamed) slot of its parent
|
|
276
|
+
# element's shadow tree; a closed shadow tree hides the assignment (null).
|
|
277
|
+
def assigned_slot
|
|
278
|
+
parent = @__node__.parent
|
|
279
|
+
return nil unless parent.respond_to?(:element?) && parent.element?
|
|
280
|
+
|
|
281
|
+
host = @document.wrap_node(parent)
|
|
282
|
+
return nil unless host.respond_to?(:shadow_root)
|
|
283
|
+
|
|
284
|
+
sr = host.shadow_root
|
|
285
|
+
return nil unless sr
|
|
286
|
+
return nil if sr.__js_get__("mode") == "closed"
|
|
287
|
+
|
|
288
|
+
sr.query_selector_all("slot").find do |slot|
|
|
289
|
+
(slot.respond_to?(:name) ? slot.name.to_s : "") == ""
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def __js_set__(key, value)
|
|
294
|
+
case key
|
|
295
|
+
when "textContent", "data", "nodeValue"
|
|
296
|
+
write_data(value)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
nil
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
include Bridge::Methods
|
|
303
|
+
js_methods %w[remove before after replaceWith isEqualNode hasChildNodes
|
|
304
|
+
appendData insertData deleteData replaceData substringData contains
|
|
305
|
+
isSameNode getRootNode normalize splitText compareDocumentPosition
|
|
306
|
+
lookupNamespaceURI lookupPrefix isDefaultNamespace
|
|
307
|
+
appendChild insertBefore removeChild replaceChild
|
|
308
|
+
addEventListener removeEventListener dispatchEvent]
|
|
309
|
+
def __js_call__(method, args)
|
|
310
|
+
case method
|
|
311
|
+
when "hasChildNodes"
|
|
312
|
+
false
|
|
313
|
+
when "contains"
|
|
314
|
+
# A leaf node contains only itself (no descendants).
|
|
315
|
+
args[0].respond_to?(:__dommy_backend_node__) &&
|
|
316
|
+
args[0].__dommy_backend_node__ == @__node__
|
|
317
|
+
when "appendChild"
|
|
318
|
+
append_child(args[0])
|
|
319
|
+
when "insertBefore"
|
|
320
|
+
insert_before(args[0], args[1])
|
|
321
|
+
when "replaceChild"
|
|
322
|
+
replace_child(args[0], args[1])
|
|
323
|
+
when "removeChild"
|
|
324
|
+
remove_child(args[0])
|
|
325
|
+
when "compareDocumentPosition"
|
|
326
|
+
compare_document_position(args[0])
|
|
327
|
+
when "isSameNode"
|
|
328
|
+
is_same_node(args[0])
|
|
329
|
+
when "getRootNode"
|
|
330
|
+
get_root_node(args[0])
|
|
331
|
+
when "lookupNamespaceURI"
|
|
332
|
+
lookup_namespace_uri(args[0])
|
|
333
|
+
when "lookupPrefix"
|
|
334
|
+
lookup_prefix(args[0])
|
|
335
|
+
when "isDefaultNamespace"
|
|
336
|
+
is_default_namespace(args[0])
|
|
337
|
+
when "normalize"
|
|
338
|
+
nil # a leaf has no child text runs to merge
|
|
339
|
+
when "splitText"
|
|
340
|
+
split_text(args[0])
|
|
341
|
+
when "addEventListener"
|
|
342
|
+
add_event_listener(args[0], args[1], args[2])
|
|
343
|
+
when "removeEventListener"
|
|
344
|
+
remove_event_listener(args[0], args[1], args[2])
|
|
345
|
+
when "dispatchEvent"
|
|
346
|
+
dispatch_event(args[0])
|
|
347
|
+
when "appendData"
|
|
348
|
+
raise Bridge::TypeError, "appendData requires 1 argument." if args.empty?
|
|
349
|
+
|
|
350
|
+
append_data(args[0])
|
|
351
|
+
when "insertData"
|
|
352
|
+
insert_data(args[0], args[1])
|
|
353
|
+
when "deleteData"
|
|
354
|
+
delete_data(args[0], args[1])
|
|
355
|
+
when "replaceData"
|
|
356
|
+
replace_data(args[0], args[1], args[2])
|
|
357
|
+
when "substringData"
|
|
358
|
+
raise Bridge::TypeError, "substringData requires 2 arguments." if args.length < 2
|
|
359
|
+
|
|
360
|
+
substring_data(args[0], args[1])
|
|
361
|
+
when "remove"
|
|
362
|
+
remove
|
|
363
|
+
Bridge::UNDEFINED # ChildNode#remove is void -> JS undefined, not null
|
|
364
|
+
when "before"
|
|
365
|
+
before(*args)
|
|
366
|
+
when "after"
|
|
367
|
+
after(*args)
|
|
368
|
+
when "replaceWith"
|
|
369
|
+
replace_with(*args)
|
|
370
|
+
when "isEqualNode"
|
|
371
|
+
is_equal_node(args[0])
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# ChildNode mixin: WHATWG DOM defines `before`, `after`, `replaceWith` on
|
|
376
|
+
# all child nodes, including Text and Comment. The spec-correct algorithm
|
|
377
|
+
# (viable previous/next sibling, forward insertion, string coercion,
|
|
378
|
+
# Fragment/cross-document adoption) lives in Internal::ParentNode and is
|
|
379
|
+
# shared with Element — these just forward to it.
|
|
380
|
+
|
|
381
|
+
def before(*args)
|
|
382
|
+
child_node_before(args)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def after(*args)
|
|
386
|
+
child_node_after(args)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def replace_with(*args)
|
|
390
|
+
child_node_replace_with(args)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
private
|
|
394
|
+
|
|
395
|
+
def write_data(value)
|
|
396
|
+
old = @__node__.content
|
|
397
|
+
@__node__.content = value.to_s
|
|
398
|
+
@document.notify_character_data_mutation(target_node: @__node__, old_value: old)
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
class TextNode < CharacterDataNode
|
|
403
|
+
def node_type
|
|
404
|
+
3
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# WHATWG Text.wholeText — the concatenated data of this node together with
|
|
408
|
+
# its contiguous, logically-adjacent Text / CDATASection siblings (node
|
|
409
|
+
# types 3 and 4), in document order.
|
|
410
|
+
def whole_text
|
|
411
|
+
run = [@__node__]
|
|
412
|
+
prev = @__node__.previous
|
|
413
|
+
while prev && [3, 4].include?(prev.node_type)
|
|
414
|
+
run.unshift(prev)
|
|
415
|
+
prev = prev.previous
|
|
416
|
+
end
|
|
417
|
+
nxt = @__node__.next
|
|
418
|
+
while nxt && [3, 4].include?(nxt.node_type)
|
|
419
|
+
run.push(nxt)
|
|
420
|
+
nxt = nxt.next
|
|
421
|
+
end
|
|
422
|
+
run.map { |bn| bn.content.to_s }.join
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def __js_get__(key)
|
|
426
|
+
return whole_text if key == "wholeText"
|
|
427
|
+
|
|
428
|
+
super
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# Node.cloneNode. CharacterData has no children, so `deep` decides nothing:
|
|
432
|
+
# the copy is a new node of the SAME interface carrying the same data, owned
|
|
433
|
+
# by this node's document. Each subclass builds its own kind — a CDATASection
|
|
434
|
+
# that cloned to a Text would be the wrong node.
|
|
435
|
+
def clone_node(_deep = false)
|
|
436
|
+
@document.create_text_node(@__node__.text)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Own __js_call__ methods, on top of CharacterDataNode's.
|
|
440
|
+
js_methods %w[cloneNode]
|
|
441
|
+
def __js_call__(method, args)
|
|
442
|
+
case method
|
|
443
|
+
when "cloneNode"
|
|
444
|
+
clone_node(args.first)
|
|
445
|
+
else
|
|
446
|
+
super
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# CDATASection — a Text subtype (nodeType 4). CharacterData methods and the
|
|
452
|
+
# "#cdata-section" nodeName come from CharacterDataNode via node_type.
|
|
453
|
+
|
|
454
|
+
# CDATASection — a Text subtype (nodeType 4). CharacterData methods and the
|
|
455
|
+
# "#cdata-section" nodeName come from CharacterDataNode via node_type.
|
|
456
|
+
class CDATASectionNode < TextNode
|
|
457
|
+
def node_type
|
|
458
|
+
4
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def clone_node(_deep = false)
|
|
462
|
+
@document.create_cdata_section(@__node__.content)
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
class CommentNode < CharacterDataNode
|
|
467
|
+
def node_type
|
|
468
|
+
8
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def clone_node(_deep = false)
|
|
472
|
+
@document.create_comment(@__node__.content)
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
# Own __js_call__ methods, on top of CharacterDataNode's.
|
|
476
|
+
js_methods %w[cloneNode]
|
|
477
|
+
def __js_call__(method, args)
|
|
478
|
+
case method
|
|
479
|
+
when "cloneNode"
|
|
480
|
+
clone_node(args.first)
|
|
481
|
+
else
|
|
482
|
+
super
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# ProcessingInstruction (`<?target data?>`, nodeType 7) — CharacterData with a
|
|
488
|
+
# `target`. Backed by a real backend node (created via
|
|
489
|
+
# document.createProcessingInstruction), so it participates in the tree like
|
|
490
|
+
# Text/Comment: insertion, ChildNode methods, identity caching and
|
|
491
|
+
# serialization all come from the shared CharacterDataNode machinery.
|
|
492
|
+
|
|
493
|
+
# ProcessingInstruction (`<?target data?>`, nodeType 7) — CharacterData with a
|
|
494
|
+
# `target`. Backed by a real backend node (created via
|
|
495
|
+
# document.createProcessingInstruction), so it participates in the tree like
|
|
496
|
+
# Text/Comment: insertion, ChildNode methods, identity caching and
|
|
497
|
+
# serialization all come from the shared CharacterDataNode machinery.
|
|
498
|
+
class ProcessingInstructionNode < CharacterDataNode
|
|
499
|
+
def node_type
|
|
500
|
+
7
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# WHATWG: a ProcessingInstruction's nodeName is its target.
|
|
504
|
+
def node_name
|
|
505
|
+
@__node__.target
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def target
|
|
509
|
+
@__node__.target
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def __js_get__(key)
|
|
513
|
+
case key
|
|
514
|
+
when "target"
|
|
515
|
+
@__node__.target
|
|
516
|
+
else
|
|
517
|
+
super
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def clone_node(_deep = false)
|
|
522
|
+
@document.create_processing_instruction(@__node__.target, @__node__.content)
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Own __js_call__ methods, on top of CharacterDataNode's.
|
|
526
|
+
js_methods %w[cloneNode]
|
|
527
|
+
def __js_call__(method, args)
|
|
528
|
+
case method
|
|
529
|
+
when "cloneNode"
|
|
530
|
+
clone_node(args.first)
|
|
531
|
+
else
|
|
532
|
+
super
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# (`LiveChildren` removed — `el.children` now returns a
|
|
538
|
+
# `Dommy::HTMLCollection` initialized with a re-evaluating block.)
|
|
539
|
+
end
|