dommy 0.15.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.
- checksums.yaml +4 -4
- data/lib/dommy/animation.rb +1 -0
- data/lib/dommy/backend.rb +19 -15
- data/lib/dommy/blob.rb +23 -0
- data/lib/dommy/bridge/values.rb +29 -2
- data/lib/dommy/bridge/wire_tags.rb +8 -4
- data/lib/dommy/browser.rb +335 -52
- data/lib/dommy/character_data.rb +21 -40
- data/lib/dommy/class_list.rb +24 -9
- data/lib/dommy/cookie_jar.rb +408 -0
- data/lib/dommy/cookie_store.rb +105 -49
- data/lib/dommy/css.rb +34 -8
- data/lib/dommy/custom_elements.rb +362 -114
- data/lib/dommy/data_transfer.rb +22 -2
- data/lib/dommy/document.rb +1114 -206
- 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 +228 -102
- data/lib/dommy/element_internals.rb +227 -0
- data/lib/dommy/encodings/legacy_multibyte.rb +800 -0
- data/lib/dommy/encodings/multibyte_overrides.rb +76 -0
- data/lib/dommy/encodings/prescan.rb +234 -0
- data/lib/dommy/encodings.rb +126 -52
- data/lib/dommy/event.rb +472 -163
- data/lib/dommy/event_source.rb +5 -30
- data/lib/dommy/fetch.rb +244 -53
- data/lib/dommy/file_reader.rb +6 -29
- data/lib/dommy/form_data.rb +62 -15
- data/lib/dommy/form_entry_list.rb +108 -139
- data/lib/dommy/fragment.rb +11 -10
- data/lib/dommy/history.rb +247 -90
- data/lib/dommy/html_canvas_element.rb +95 -42
- data/lib/dommy/html_collection.rb +49 -30
- data/lib/dommy/html_elements/base.rb +112 -69
- data/lib/dommy/html_elements/embedded.rb +349 -105
- data/lib/dommy/html_elements/forms/form.rb +407 -368
- data/lib/dommy/html_elements/forms/input.rb +336 -174
- data/lib/dommy/html_elements/forms/select.rb +90 -74
- data/lib/dommy/html_elements/interactive.rb +329 -111
- data/lib/dommy/html_elements/media.rb +71 -30
- data/lib/dommy/html_elements/metadata.rb +238 -71
- data/lib/dommy/html_elements/miscellaneous.rb +58 -14
- data/lib/dommy/html_elements/tables.rb +7 -9
- data/lib/dommy/html_elements.rb +23 -1
- data/lib/dommy/hyperlink_utils.rb +15 -4
- 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 +17 -5
- data/lib/dommy/interaction/form_submission.rb +31 -43
- data/lib/dommy/interaction/key_sender.rb +101 -16
- data/lib/dommy/interaction.rb +1 -0
- data/lib/dommy/internal/accessibility_tree.rb +149 -150
- data/lib/dommy/internal/accessibility_visibility.rb +16 -17
- data/lib/dommy/internal/accessible_description.rb +1 -1
- data/lib/dommy/internal/accessible_name.rb +231 -253
- data/lib/dommy/internal/aria_role.rb +182 -195
- data/lib/dommy/internal/aria_snapshot.rb +35 -38
- data/lib/dommy/internal/aria_state.rb +102 -118
- data/lib/dommy/internal/autofill.rb +75 -75
- data/lib/dommy/internal/backend_prefilter.rb +126 -133
- 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 +7 -43
- data/lib/dommy/internal/child_navigable.rb +158 -0
- data/lib/dommy/internal/child_node.rb +44 -41
- data/lib/dommy/internal/close_watcher_manager.rb +239 -0
- data/lib/dommy/internal/complex_match.rb +3 -1
- data/lib/dommy/internal/constraint_validation.rb +130 -0
- data/lib/dommy/internal/css/ancestor_filter.rb +107 -0
- data/lib/dommy/internal/css/calc.rb +90 -71
- data/lib/dommy/internal/css/cascade.rb +72 -70
- data/lib/dommy/internal/css/cascaded_declarations.rb +4 -1
- data/lib/dommy/internal/css/color.rb +249 -187
- data/lib/dommy/internal/css/computed_style_builder.rb +43 -3
- data/lib/dommy/internal/css/computed_style_declaration.rb +50 -11
- data/lib/dommy/internal/css/counters.rb +67 -67
- data/lib/dommy/internal/css/custom_properties.rb +115 -103
- data/lib/dommy/internal/css/parser.rb +273 -257
- data/lib/dommy/internal/css/property_registry.rb +321 -302
- data/lib/dommy/internal/css/renderability.rb +39 -31
- data/lib/dommy/internal/css/rule_index.rb +151 -100
- 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 +7 -4
- data/lib/dommy/internal/css/supports.rb +115 -113
- data/lib/dommy/internal/css/ua_stylesheet.rb +60 -24
- data/lib/dommy/internal/css_rule_text.rb +121 -119
- data/lib/dommy/internal/css_source.rb +1 -3
- data/lib/dommy/internal/css_syntax.rb +157 -92
- data/lib/dommy/internal/declarative_shadow_roots.rb +183 -0
- data/lib/dommy/internal/directionality.rb +120 -123
- data/lib/dommy/internal/document_generations.rb +55 -2
- data/lib/dommy/internal/document_interaction_state.rb +293 -9
- data/lib/dommy/internal/document_live_ranges.rb +26 -1
- data/lib/dommy/internal/dom_matching.rb +185 -184
- data/lib/dommy/internal/element_aria.rb +25 -19
- data/lib/dommy/internal/element_geometry.rb +8 -1
- data/lib/dommy/internal/element_matching.rb +71 -69
- data/lib/dommy/internal/element_popover.rb +226 -25
- data/lib/dommy/internal/element_shadow.rb +98 -39
- data/lib/dommy/internal/element_state.rb +264 -181
- data/lib/dommy/internal/element_top_layer.rb +15 -1
- 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 +151 -0
- data/lib/dommy/internal/exception_report.rb +89 -87
- data/lib/dommy/internal/focusability.rb +267 -0
- data/lib/dommy/internal/form_associated_custom_elements.rb +127 -0
- data/lib/dommy/internal/global_functions.rb +49 -47
- data/lib/dommy/internal/html_or_svg_or_mathml_element.rb +34 -23
- data/lib/dommy/internal/html_serialization.rb +292 -0
- data/lib/dommy/internal/idl_reflection.rb +204 -0
- data/lib/dommy/internal/idl_reflection_table.rb +589 -0
- data/lib/dommy/internal/infra.rb +9 -0
- data/lib/dommy/internal/input_type.rb +76 -18
- data/lib/dommy/internal/insertion_point.rb +27 -3
- data/lib/dommy/internal/js_number.rb +62 -0
- data/lib/dommy/internal/light_dismiss.rb +176 -0
- data/lib/dommy/internal/literal_lookup.rb +75 -75
- data/lib/dommy/internal/mime_type.rb +55 -42
- data/lib/dommy/internal/mutation_coordinator.rb +140 -62
- data/lib/dommy/internal/mutation_observer_microtask.rb +60 -0
- data/lib/dommy/internal/namespaces.rb +51 -49
- data/lib/dommy/internal/node_adopter.rb +77 -5
- data/lib/dommy/internal/node_equality.rb +61 -59
- data/lib/dommy/internal/node_factory.rb +111 -7
- data/lib/dommy/internal/node_identity.rb +1 -1
- data/lib/dommy/internal/node_traversal.rb +22 -4
- data/lib/dommy/internal/node_wrapper_cache.rb +6 -38
- data/lib/dommy/internal/observer_options.rb +24 -14
- data/lib/dommy/internal/origin.rb +86 -0
- data/lib/dommy/internal/parent_node.rb +10 -8
- 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 +141 -71
- data/lib/dommy/internal/referrer.rb +144 -0
- data/lib/dommy/internal/reflected_attributes.rb +128 -18
- data/lib/dommy/internal/rendered_text/collector.rb +23 -8
- data/lib/dommy/internal/rendered_text/computed_box.rb +3 -2
- data/lib/dommy/internal/rendered_text/fragment.rb +37 -34
- data/lib/dommy/internal/rendered_text/items.rb +7 -5
- data/lib/dommy/internal/selector_arguments.rb +2 -1
- data/lib/dommy/internal/selector_match.rb +1 -1
- data/lib/dommy/internal/selector_matcher.rb +361 -354
- data/lib/dommy/internal/selector_parser.rb +88 -63
- data/lib/dommy/internal/sequential_focus_navigation.rb +209 -0
- data/lib/dommy/internal/shadow_root_registry.rb +14 -0
- data/lib/dommy/internal/slot_assignment.rb +117 -0
- data/lib/dommy/internal/slot_signals.rb +125 -0
- data/lib/dommy/internal/template_content_registry.rb +7 -1
- 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 +109 -0
- data/lib/dommy/internal/url_parser.rb +89 -37
- data/lib/dommy/internal/user_activation.rb +192 -0
- data/lib/dommy/internal/web_idl.rb +176 -38
- data/lib/dommy/internal/window_bound.rb +14 -0
- data/lib/dommy/internal/window_constructors.rb +29 -6
- data/lib/dommy/internal/xml_serialization.rb +298 -296
- 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 +63 -16
- data/lib/dommy/js/host_bridge.rb +212 -41
- data/lib/dommy/js/host_callbacks.rb +7 -0
- data/lib/dommy/js/host_runtime.js +2494 -448
- data/lib/dommy/js/import_map.rb +1 -1
- data/lib/dommy/js/marshaller.rb +13 -4
- data/lib/dommy/js/module_loader.rb +37 -6
- data/lib/dommy/js/module_preload.rb +107 -29
- data/lib/dommy/js/observable_runtime.js +295 -128
- data/lib/dommy/js/script_boot.rb +197 -115
- 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 +2084 -0
- data/lib/dommy/js/webidl_tables.js +47 -100
- data/lib/dommy/js/window_wiring.rb +9 -5
- data/lib/dommy/location.rb +129 -76
- data/lib/dommy/media_query_list.rb +5 -13
- data/lib/dommy/message_channel.rb +139 -64
- data/lib/dommy/mutation_observer.rb +27 -29
- data/lib/dommy/navigation.rb +59 -9
- data/lib/dommy/navigator.rb +135 -6
- data/lib/dommy/node.rb +23 -11
- data/lib/dommy/notification.rb +10 -1
- data/lib/dommy/promise.rb +20 -0
- data/lib/dommy/range.rb +25 -37
- data/lib/dommy/resources.rb +38 -3
- data/lib/dommy/scheduler.rb +176 -48
- data/lib/dommy/shadow_root.rb +141 -19
- data/lib/dommy/storage.rb +135 -25
- data/lib/dommy/streams.rb +18 -0
- data/lib/dommy/structured_data.rb +45 -0
- data/lib/dommy/style_declaration.rb +31 -4
- data/lib/dommy/text_codec.rb +9 -19
- data/lib/dommy/tree_walker.rb +78 -79
- data/lib/dommy/url.rb +11 -4
- data/lib/dommy/validity_state.rb +109 -107
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +6 -28
- data/lib/dommy/window.rb +833 -103
- data/lib/dommy/worker.rb +5 -28
- data/lib/dommy/xml_http_request.rb +43 -46
- data/lib/dommy.rb +52 -3
- metadata +45 -6
- data/lib/dommy/internal/cookie_jar.rb +0 -29
|
@@ -36,8 +36,6 @@ module Dommy
|
|
|
36
36
|
# Where a call of the function #next_function is asked for may start, or
|
|
37
37
|
# an atom: one pattern per function name, built on its first use.
|
|
38
38
|
FUNCTION_STOP = Hash.new { |stops, name| stops[name] = Regexp.union(ATOM_START, /#{Regexp.escape(name[0])}/i) }
|
|
39
|
-
# The bracket that closes a block each opening one starts.
|
|
40
|
-
CLOSER = {"(" => ")", "[" => "]", "{" => "}"}.freeze
|
|
41
39
|
|
|
42
40
|
# `bytes:` is a binary String this CssSource may keep as its copy, in
|
|
43
41
|
# place of `text` — a reader handing on what it has already made.
|
|
@@ -195,7 +193,7 @@ module Dommy
|
|
|
195
193
|
# closing bracket closes the innermost only when it is that block's own
|
|
196
194
|
# kind (:closed); any other closing bracket is a token like any other.
|
|
197
195
|
def step_block(blocks, c)
|
|
198
|
-
if (closer = CLOSER[c])
|
|
196
|
+
if (closer = CssSyntax::CLOSER[c])
|
|
199
197
|
blocks.push(closer)
|
|
200
198
|
:opened
|
|
201
199
|
elsif !blocks.empty? && blocks.last == c
|
|
@@ -19,6 +19,8 @@ module Dommy
|
|
|
19
19
|
module CssSyntax
|
|
20
20
|
OPENING_BRACKETS = "([{"
|
|
21
21
|
CLOSING_BRACKETS = ")]}"
|
|
22
|
+
# The mirror each opening bracket's block (or function) closes at.
|
|
23
|
+
CLOSER = {"(" => ")", "[" => "]", "{" => "}"}.freeze
|
|
22
24
|
# css-syntax-3's whitespace (§4.2) — before §3.3's preprocessing folds CR
|
|
23
25
|
# and FF into LF, the same set as Infra's ASCII whitespace.
|
|
24
26
|
WHITESPACE = Infra::ASCII_WHITESPACE_CHARS
|
|
@@ -35,119 +37,182 @@ module Dommy
|
|
|
35
37
|
STRING_STOP = {'"' => /["\\\n]/, "'" => /['\\\n]/}.freeze
|
|
36
38
|
# What can end an unquoted url, or must be stepped over in one.
|
|
37
39
|
URL_STOP = /[)\\]/
|
|
40
|
+
# What makes an unquoted url bad (§4.3.6), whitespace and escapes aside:
|
|
41
|
+
# a quote, a `(`, or a non-printable code point (§4.2).
|
|
42
|
+
BAD_URL_CODE_POINT = /["'(\u0000-\u0008\u000B\u000E-\u001F\u007F]/
|
|
38
43
|
# css-syntax-3 §3.3: the newline forms and NULL the tokenizer never sees.
|
|
39
44
|
NEEDS_FILTERING = /[\r\f\u0000]/
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
class << self
|
|
47
|
+
# css-syntax-3 §3.3 "filter code points", the pass that runs before the
|
|
48
|
+
# tokenizer sees anything: the three newline forms become U+000A, and
|
|
49
|
+
# U+0000 becomes U+FFFD. Without it, a backslash before a CRLF escapes
|
|
50
|
+
# the CR and leaves the LF to end the string, as no browser reads it.
|
|
51
|
+
# The replacement character is itself an ident code point, so `.a<NUL>b`
|
|
52
|
+
# names the class `a<U+FFFD>b` rather than being a syntax error.
|
|
53
|
+
def preprocess(text)
|
|
54
|
+
return text unless text.match?(NEEDS_FILTERING)
|
|
55
|
+
|
|
56
|
+
text.gsub(/\r\n|[\r\f]/, "\n").gsub("\u0000", "\uFFFD")
|
|
57
|
+
end
|
|
42
58
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# names the class `a<U+FFFD>b` rather than being a syntax error.
|
|
49
|
-
def preprocess(text)
|
|
50
|
-
return text unless text.match?(NEEDS_FILTERING)
|
|
59
|
+
# §4.3.8: a backslash at `j` starts a valid escape unless a newline
|
|
60
|
+
# follows it. Every other pair counts, the end of the input included.
|
|
61
|
+
def valid_escape_at?(source, j)
|
|
62
|
+
source[j] == "\\" && source[j + 1] != "\n"
|
|
63
|
+
end
|
|
51
64
|
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
# §4.2 ident-start code point: a letter, an underscore, or a non-ASCII
|
|
66
|
+
# ident code point. nil (past the end) is none.
|
|
67
|
+
def ident_start_code_point?(c)
|
|
68
|
+
return false if c.nil?
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def valid_escape_at?(source, j)
|
|
58
|
-
source[j] == "\\" && source[j + 1] != "\n"
|
|
59
|
-
end
|
|
70
|
+
c.match?(/[A-Za-z_]/) || non_ascii_ident_code_point?(c)
|
|
71
|
+
end
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return false if c.nil?
|
|
73
|
+
# §4.2 ident code point: an ident-start one, a digit, or U+002D.
|
|
74
|
+
def name_code_point?(c)
|
|
75
|
+
return false if c.nil?
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
c.match?(/[A-Za-z0-9_\-]/) || non_ascii_ident_code_point?(c)
|
|
78
|
+
end
|
|
68
79
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
# Index just past the escape, string, unquoted url or comment starting
|
|
81
|
+
# at `j`, or nil if none starts there. A string ends at its closing
|
|
82
|
+
# quote, or before a newline or the end of the input; a url at its `)`
|
|
83
|
+
# or the end of the input; a comment at its `*/` or the end of the input.
|
|
84
|
+
def atom_end(source, j)
|
|
85
|
+
c = source[j]
|
|
86
|
+
if (c == "u" || c == "U") && (k = url_contents_start(source, j))
|
|
87
|
+
url_end(source, k)
|
|
88
|
+
elsif c == "\\"
|
|
89
|
+
return nil unless valid_escape_at?(source, j)
|
|
90
|
+
|
|
91
|
+
[j + 2, source.length].min
|
|
92
|
+
elsif c == '"' || c == "'"
|
|
93
|
+
string_end(source, j + 1, c)
|
|
94
|
+
elsif c == "/" && source[j + 1] == "*"
|
|
95
|
+
close = source.index("*/", j + 2)
|
|
96
|
+
close ? close + 2 : source.length
|
|
97
|
+
end
|
|
98
|
+
end
|
|
72
99
|
|
|
73
|
-
|
|
74
|
-
|
|
100
|
+
# Whether the string or unquoted url starting at `j` is a bad one: a
|
|
101
|
+
# <bad-string-token>, which a newline ends unclosed (§4.3.5), or a
|
|
102
|
+
# <bad-url-token> (§4.3.6). No grammar takes either, not even
|
|
103
|
+
# <any-value>.
|
|
104
|
+
def bad_token_at?(source, j)
|
|
105
|
+
c = source[j]
|
|
106
|
+
if (c == "u" || c == "U") && (k = url_contents_start(source, j))
|
|
107
|
+
bad_url?(source, k)
|
|
108
|
+
elsif c == '"' || c == "'"
|
|
109
|
+
k = string_stop(source, j + 1, c)
|
|
110
|
+
!k.nil? && source[k] == "\n"
|
|
111
|
+
else
|
|
112
|
+
false
|
|
113
|
+
end
|
|
114
|
+
end
|
|
75
115
|
|
|
76
|
-
|
|
77
|
-
# non-ASCII character that a byte-level reader cannot see whole; it is
|
|
78
|
-
# taken for a name code point, which only decides whether a `url(` or
|
|
79
|
-
# `var(` after it is the tail of a longer name.
|
|
80
|
-
def non_ascii_ident_code_point?(c)
|
|
81
|
-
codepoint = c.ord
|
|
82
|
-
return false if codepoint < 0x80
|
|
83
|
-
return true if c.bytesize == 1 || codepoint >= 0x10000
|
|
116
|
+
private
|
|
84
117
|
|
|
85
|
-
|
|
86
|
-
|
|
118
|
+
# A single byte of a binary String at or above 0x80 is part of a
|
|
119
|
+
# non-ASCII character that a byte-level reader cannot see whole; it is
|
|
120
|
+
# taken for a name code point, which only decides whether a `url(` or
|
|
121
|
+
# `var(` after it is the tail of a longer name.
|
|
122
|
+
def non_ascii_ident_code_point?(c)
|
|
123
|
+
codepoint = c.ord
|
|
124
|
+
return false if codepoint < 0x80
|
|
125
|
+
return true if c.bytesize == 1 || codepoint >= 0x10000
|
|
87
126
|
|
|
88
|
-
|
|
89
|
-
# at `j`, or nil if none starts there. A string ends at its closing
|
|
90
|
-
# quote, or before a newline or the end of the input; a url at its `)`
|
|
91
|
-
# or the end of the input; a comment at its `*/` or the end of the input.
|
|
92
|
-
def atom_end(source, j)
|
|
93
|
-
c = source[j]
|
|
94
|
-
if (c == "u" || c == "U") && (k = url_contents_start(source, j))
|
|
95
|
-
url_end(source, k)
|
|
96
|
-
elsif c == "\\"
|
|
97
|
-
return nil unless valid_escape_at?(source, j)
|
|
98
|
-
|
|
99
|
-
[j + 2, source.length].min
|
|
100
|
-
elsif c == '"' || c == "'"
|
|
101
|
-
string_end(source, j + 1, c)
|
|
102
|
-
elsif c == "/" && source[j + 1] == "*"
|
|
103
|
-
close = source.index("*/", j + 2)
|
|
104
|
-
close ? close + 2 : source.length
|
|
127
|
+
NON_ASCII_IDENT_RANGES.any? { |range| range.cover?(codepoint) }
|
|
105
128
|
end
|
|
106
|
-
end
|
|
107
129
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return k if d == "\n"
|
|
117
|
-
|
|
118
|
-
k += 2
|
|
130
|
+
# Index just past the `quote` closing a string whose contents start at
|
|
131
|
+
# `k`, or of the newline that ends it unclosed, or the end of the input.
|
|
132
|
+
# A backslash takes the code point after it (§4.3.5).
|
|
133
|
+
def string_end(source, k, quote)
|
|
134
|
+
k = string_stop(source, k, quote)
|
|
135
|
+
return source.length if k.nil?
|
|
136
|
+
|
|
137
|
+
source[k] == quote ? k + 1 : k
|
|
119
138
|
end
|
|
120
|
-
source.length
|
|
121
|
-
end
|
|
122
139
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return nil if j.positive? && name_code_point?(source[j - 1])
|
|
130
|
-
|
|
131
|
-
k = j + 4
|
|
132
|
-
k += 1 while k < source.length && WHITESPACE.include?(source[k])
|
|
133
|
-
q = source[k]
|
|
134
|
-
q == '"' || q == "'" ? nil : k
|
|
135
|
-
end
|
|
140
|
+
# Index of the `quote` closing a string whose contents start at `k`, or
|
|
141
|
+
# of the newline that ends it unclosed; nil if the input ends first.
|
|
142
|
+
def string_stop(source, k, quote)
|
|
143
|
+
stop = STRING_STOP[quote]
|
|
144
|
+
while (k = source.index(stop, k))
|
|
145
|
+
return k unless source[k] == "\\"
|
|
136
146
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def url_end(source, k)
|
|
142
|
-
while (k = source.index(URL_STOP, k))
|
|
143
|
-
return k + 1 if source[k] == ")"
|
|
147
|
+
k += 2
|
|
148
|
+
end
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
144
151
|
|
|
145
|
-
|
|
152
|
+
# Where an unquoted url token's contents start, if the ident `url`
|
|
153
|
+
# (ASCII case-insensitive, not the tail of a longer name) followed by
|
|
154
|
+
# `(` starts at `j` — after the whitespace the url skips. A quote there
|
|
155
|
+
# makes it a `url(` function holding a string instead (§4.3.4).
|
|
156
|
+
def url_contents_start(source, j)
|
|
157
|
+
return nil unless source[j, 4]&.casecmp?("url(")
|
|
158
|
+
return nil if j.positive? && name_code_point?(source[j - 1])
|
|
159
|
+
|
|
160
|
+
k = j + 4
|
|
161
|
+
k += 1 while k < source.length && WHITESPACE.include?(source[k])
|
|
162
|
+
q = source[k]
|
|
163
|
+
q == '"' || q == "'" ? nil : k
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Index just past the `)` closing an unquoted url whose contents start
|
|
167
|
+
# at `k`, or the end of the input. An escape is stepped over; whatever
|
|
168
|
+
# else would make the url bad (§4.3.6's consume the remnants of a bad
|
|
169
|
+
# url) still ends at the same `)`.
|
|
170
|
+
def url_end(source, k)
|
|
171
|
+
while (k = source.index(URL_STOP, k))
|
|
172
|
+
return k + 1 if source[k] == ")"
|
|
173
|
+
|
|
174
|
+
k += valid_escape_at?(source, k) ? 2 : 1
|
|
175
|
+
end
|
|
176
|
+
source.length
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Whether the unquoted url whose contents start at `k` is bad (§4.3.6):
|
|
180
|
+
# whitespace before anything but its `)` or the end of the input, a
|
|
181
|
+
# BAD_URL_CODE_POINT, or a backslash that starts no valid escape.
|
|
182
|
+
def bad_url?(source, k)
|
|
183
|
+
while k < source.length
|
|
184
|
+
c = source[k]
|
|
185
|
+
return false if c == ")"
|
|
186
|
+
|
|
187
|
+
if WHITESPACE.include?(c)
|
|
188
|
+
k += 1 while k < source.length && WHITESPACE.include?(source[k])
|
|
189
|
+
return k < source.length && source[k] != ")"
|
|
190
|
+
end
|
|
191
|
+
return true if c.match?(BAD_URL_CODE_POINT)
|
|
192
|
+
|
|
193
|
+
if c == "\\"
|
|
194
|
+
return true unless valid_escape_at?(source, k)
|
|
195
|
+
|
|
196
|
+
k = escape_end(source, k)
|
|
197
|
+
else
|
|
198
|
+
k += 1
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
false
|
|
146
202
|
end
|
|
147
|
-
source.length
|
|
148
|
-
end
|
|
149
203
|
|
|
150
|
-
|
|
204
|
+
# Index just past the valid escape whose backslash is at `j` (§4.3.7):
|
|
205
|
+
# up to six hex digits and one whitespace after them, or else the one
|
|
206
|
+
# code point after the backslash.
|
|
207
|
+
def escape_end(source, j)
|
|
208
|
+
hex = source[j + 1, 6].to_s[/\A\h+/]
|
|
209
|
+
return [j + 2, source.length].min if hex.nil?
|
|
210
|
+
|
|
211
|
+
k = j + 1 + hex.length
|
|
212
|
+
k += 1 if k < source.length && WHITESPACE.include?(source[k])
|
|
213
|
+
k
|
|
214
|
+
end
|
|
215
|
+
end
|
|
151
216
|
end
|
|
152
217
|
end
|
|
153
218
|
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dommy
|
|
4
|
+
module Internal
|
|
5
|
+
# Declarative shadow roots, as a pass over what the parser built.
|
|
6
|
+
#
|
|
7
|
+
# The backend's HTML parser has no declarative shadow DOM: it parses a
|
|
8
|
+
# `<template shadowrootmode>` as an ordinary template, with its children
|
|
9
|
+
# in the template contents. HTML's tree builder, when its "allow
|
|
10
|
+
# declarative shadow roots" is on, does instead — at the template start
|
|
11
|
+
# tag ("in head" insertion mode) — attach a shadow root to the adjusted
|
|
12
|
+
# current node and parse the template's children into it, never
|
|
13
|
+
# inserting the template. This pass reproduces that result: it walks the
|
|
14
|
+
# parsed nodes in tree order, which is the order the parser met the start
|
|
15
|
+
# tags, and for each such template
|
|
16
|
+
#
|
|
17
|
+
# - takes its parent as the declarative shadow host (the adjusted
|
|
18
|
+
# current node the template was inserted under). A template whose
|
|
19
|
+
# parent is not an element — the top of template contents (the
|
|
20
|
+
# adjusted current node was the enclosing template), or of a fragment
|
|
21
|
+
# parse (see below) — is left alone;
|
|
22
|
+
# - leaves the template in place when the host is already a shadow host
|
|
23
|
+
# (the first declarative template wins), or when attaching a shadow
|
|
24
|
+
# root throws (not a valid shadow host, a definition that disables
|
|
25
|
+
# shadow);
|
|
26
|
+
# - otherwise attaches one with the template's shadowrootmode,
|
|
27
|
+
# shadowrootdelegatesfocus, shadowrootserializable,
|
|
28
|
+
# shadowrootslotassignment and shadowrootclonable, and a null registry
|
|
29
|
+
# with shadowrootcustomelementregistry (else the host's document's);
|
|
30
|
+
# sets it declarative and available to element internals (and keep
|
|
31
|
+
# custom element registry null with that attribute), moves the template
|
|
32
|
+
# contents into it and drops the template.
|
|
33
|
+
#
|
|
34
|
+
# Template contents, and the shadow trees the pass makes, are walked too:
|
|
35
|
+
# the parser attaches declarative shadow roots wherever it meets one.
|
|
36
|
+
#
|
|
37
|
+
# The fragment case: HTML's fragment parsing algorithm puts only its own
|
|
38
|
+
# `html` root on the stack of open elements, so for a top-level template
|
|
39
|
+
# the adjusted current node is the context element, and the spec text
|
|
40
|
+
# would attach a shadow root to it. Browsers (and the WPT expectations)
|
|
41
|
+
# leave such a template in place, and so does this pass, unless the
|
|
42
|
+
# caller names a `top_host` — document.write, whose parser is the
|
|
43
|
+
# document's own, where the template's adjusted current node really is
|
|
44
|
+
# the element the markup goes into.
|
|
45
|
+
#
|
|
46
|
+
# Moving the contents is a representation fix-up, not a DOM mutation: in
|
|
47
|
+
# the parser's tree they were never anywhere else. So it uses raw backend
|
|
48
|
+
# moves, and queues no mutation records. What the parser does at the
|
|
49
|
+
# time — a script's access mid-parse, the moment a MutationObserver sees
|
|
50
|
+
# the host — cannot be reproduced by a pass that runs once parsing ends.
|
|
51
|
+
#
|
|
52
|
+
# Spec: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead
|
|
53
|
+
class DeclarativeShadowRoots
|
|
54
|
+
MODES = %w[open closed].freeze
|
|
55
|
+
|
|
56
|
+
# Run the pass over `root_bn`'s subtree (an element, a fragment, a
|
|
57
|
+
# document). Returns the shadow roots it attached, in tree order.
|
|
58
|
+
def self.attach(document, root_bn, top_host: nil)
|
|
59
|
+
new(document).run(root_bn, top_host)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def initialize(document)
|
|
63
|
+
@document = document
|
|
64
|
+
@templates = document.__internal_template_registry__
|
|
65
|
+
@attached = []
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def run(root_bn, top_host)
|
|
69
|
+
walk(root_bn, top_host) if candidate?(root_bn)
|
|
70
|
+
@attached
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# Whether a `<template shadowrootmode>` is anywhere in the subtree,
|
|
76
|
+
# template contents included — found by native queries, so a page with
|
|
77
|
+
# none (most) is not walked.
|
|
78
|
+
def candidate?(root_bn)
|
|
79
|
+
return false unless root_bn.respond_to?(:css)
|
|
80
|
+
return true if root_bn.element? && declarative_mode(root_bn)
|
|
81
|
+
return true unless root_bn.css("template[shadowrootmode]").empty?
|
|
82
|
+
|
|
83
|
+
templates = root_bn.css("template").to_a
|
|
84
|
+
templates.unshift(root_bn) if root_bn.element? && @templates.template_node?(root_bn)
|
|
85
|
+
templates.any? do |template|
|
|
86
|
+
contents = @templates.existing_contents(template)
|
|
87
|
+
contents && candidate?(contents)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def walk(parent, host_for_children = nil)
|
|
92
|
+
host_for_children ||= parent if parent.element?
|
|
93
|
+
parent.children.to_a.each do |child|
|
|
94
|
+
next unless child.element?
|
|
95
|
+
|
|
96
|
+
if host_for_children && (mode = declarative_mode(child))
|
|
97
|
+
shadow = attach(host_for_children, child, mode)
|
|
98
|
+
if shadow
|
|
99
|
+
walk(shadow.__dommy_backend_node__)
|
|
100
|
+
next
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
walk(child)
|
|
104
|
+
next unless @templates.template_node?(child)
|
|
105
|
+
|
|
106
|
+
contents = @templates.existing_contents(child)
|
|
107
|
+
walk(contents) if contents
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# The template's shadowrootmode state, when it is open or closed.
|
|
112
|
+
def declarative_mode(node)
|
|
113
|
+
return nil unless @templates.template_node?(node)
|
|
114
|
+
|
|
115
|
+
value = Backend.no_namespace_attribute_value(node, "shadowrootmode")
|
|
116
|
+
return nil unless value
|
|
117
|
+
|
|
118
|
+
mode = value.downcase(:ascii)
|
|
119
|
+
mode if MODES.include?(mode)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def attach(host_bn, template, mode)
|
|
123
|
+
host = @document.wrap_node(host_bn)
|
|
124
|
+
return nil unless host.respond_to?(:__internal_attach_shadow_root__)
|
|
125
|
+
return nil if host.__internal_shadow_root__
|
|
126
|
+
|
|
127
|
+
keep_null = attribute?(template, "shadowrootcustomelementregistry")
|
|
128
|
+
slot = Backend.no_namespace_attribute_value(template, "shadowrootslotassignment")
|
|
129
|
+
begin
|
|
130
|
+
shadow = host.__internal_attach_shadow_root__(
|
|
131
|
+
mode: mode,
|
|
132
|
+
delegates_focus: attribute?(template, "shadowrootdelegatesfocus"),
|
|
133
|
+
serializable: attribute?(template, "shadowrootserializable"),
|
|
134
|
+
slot_assignment: slot&.downcase(:ascii) == "manual" ? "manual" : "named",
|
|
135
|
+
clonable: attribute?(template, "shadowrootclonable"),
|
|
136
|
+
registry: keep_null ? nil : :document
|
|
137
|
+
)
|
|
138
|
+
rescue DOMException
|
|
139
|
+
return nil
|
|
140
|
+
end
|
|
141
|
+
return nil unless shadow
|
|
142
|
+
|
|
143
|
+
shadow.__internal_available_to_internals__ = true
|
|
144
|
+
shadow.__internal_declarative__ = true
|
|
145
|
+
shadow.__internal_keep_registry_null__ = keep_null
|
|
146
|
+
shadow.__internal_parsed_before__ = template.next_sibling
|
|
147
|
+
# Where the merge below leaves the text that followed the template.
|
|
148
|
+
shadow.__internal_parsed_before__ = template.previous_sibling if text?(template.previous_sibling) && text?(template.next_sibling)
|
|
149
|
+
move_contents(template, shadow.__dommy_backend_node__)
|
|
150
|
+
shadow.__internal_first_slots__ = SlotAssignment.first_slots_by_name(shadow)
|
|
151
|
+
drop_template(template)
|
|
152
|
+
@attached << shadow
|
|
153
|
+
shadow
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# The template was never in the tree, so the parser appended the text
|
|
157
|
+
# on either side of it to one Text node.
|
|
158
|
+
def drop_template(template)
|
|
159
|
+
before = template.previous_sibling
|
|
160
|
+
after = template.next_sibling
|
|
161
|
+
template.unlink
|
|
162
|
+
return unless text?(before) && text?(after)
|
|
163
|
+
|
|
164
|
+
before.content = before.content + after.content
|
|
165
|
+
after.unlink
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def text?(node) = node.is_a?(Backend.text_class) && !node.is_a?(Backend.cdata_class)
|
|
169
|
+
|
|
170
|
+
def attribute?(node, name) = !Backend.no_namespace_attribute_value(node, name).nil?
|
|
171
|
+
|
|
172
|
+
def move_contents(template, fragment)
|
|
173
|
+
contents = @templates.existing_contents(template)
|
|
174
|
+
return unless contents
|
|
175
|
+
|
|
176
|
+
contents.children.to_a.each do |child|
|
|
177
|
+
child.unlink
|
|
178
|
+
fragment.add_child(child)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|