dommy 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/dommy/animation.rb +1 -0
- data/lib/dommy/backend.rb +3 -0
- data/lib/dommy/blob.rb +6 -0
- data/lib/dommy/browser.rb +12 -4
- data/lib/dommy/character_data.rb +1 -13
- data/lib/dommy/cookie_store.rb +2 -1
- data/lib/dommy/document.rb +67 -30
- data/lib/dommy/element.rb +11 -0
- data/lib/dommy/element_internals.rb +12 -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 +121 -57
- data/lib/dommy/event.rb +107 -54
- data/lib/dommy/event_source.rb +1 -0
- data/lib/dommy/fetch.rb +244 -53
- data/lib/dommy/file_reader.rb +1 -0
- data/lib/dommy/form_data.rb +22 -9
- data/lib/dommy/form_entry_list.rb +3 -3
- data/lib/dommy/html_canvas_element.rb +85 -12
- data/lib/dommy/html_elements/base.rb +1 -0
- data/lib/dommy/html_elements/forms/input.rb +3 -0
- data/lib/dommy/html_elements/interactive.rb +56 -48
- data/lib/dommy/html_elements/metadata.rb +23 -0
- data/lib/dommy/interaction/form_submission.rb +24 -42
- data/lib/dommy/internal/accessibility_tree.rb +149 -162
- data/lib/dommy/internal/accessibility_visibility.rb +16 -17
- data/lib/dommy/internal/accessible_name.rb +231 -246
- 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 +127 -130
- data/lib/dommy/internal/child_navigable.rb +86 -83
- data/lib/dommy/internal/close_watcher_manager.rb +1 -0
- data/lib/dommy/internal/css/ancestor_filter.rb +64 -64
- data/lib/dommy/internal/css/calc.rb +90 -71
- data/lib/dommy/internal/css/cascade.rb +69 -67
- data/lib/dommy/internal/css/color.rb +249 -187
- data/lib/dommy/internal/css/computed_style_builder.rb +34 -0
- 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 +114 -114
- data/lib/dommy/internal/css/parser.rb +277 -239
- data/lib/dommy/internal/css/property_registry.rb +321 -302
- data/lib/dommy/internal/css/renderability.rb +37 -35
- data/lib/dommy/internal/css/rule_index.rb +2 -2
- data/lib/dommy/internal/css/rule_index_inputs.rb +2 -2
- data/lib/dommy/internal/css/supports.rb +115 -113
- data/lib/dommy/internal/css/ua_stylesheet.rb +55 -53
- 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 +1 -0
- data/lib/dommy/internal/directionality.rb +120 -123
- data/lib/dommy/internal/document_live_ranges.rb +26 -1
- data/lib/dommy/internal/dom_matching.rb +185 -184
- data/lib/dommy/internal/element_geometry.rb +8 -1
- data/lib/dommy/internal/element_matching.rb +71 -69
- data/lib/dommy/internal/element_shadow.rb +1 -13
- data/lib/dommy/internal/element_state.rb +255 -265
- data/lib/dommy/internal/event_handlers.rb +66 -64
- data/lib/dommy/internal/exception_report.rb +88 -86
- data/lib/dommy/internal/focusability.rb +205 -205
- data/lib/dommy/internal/global_functions.rb +49 -47
- data/lib/dommy/internal/idl_reflection.rb +99 -97
- data/lib/dommy/internal/js_number.rb +35 -33
- data/lib/dommy/internal/light_dismiss.rb +130 -132
- data/lib/dommy/internal/literal_lookup.rb +75 -75
- data/lib/dommy/internal/mime_type.rb +54 -49
- data/lib/dommy/internal/mutation_coordinator.rb +46 -0
- 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 +25 -1
- data/lib/dommy/internal/node_equality.rb +61 -59
- data/lib/dommy/internal/post_insertion_steps.rb +1 -0
- data/lib/dommy/internal/referrer.rb +144 -0
- data/lib/dommy/internal/rendered_text/collector.rb +22 -3
- data/lib/dommy/internal/rendered_text/computed_box.rb +3 -2
- data/lib/dommy/internal/rendered_text/fragment.rb +37 -38
- data/lib/dommy/internal/rendered_text/items.rb +7 -5
- data/lib/dommy/internal/selector_matcher.rb +361 -359
- data/lib/dommy/internal/selector_parser.rb +74 -62
- data/lib/dommy/internal/sequential_focus_navigation.rb +150 -151
- data/lib/dommy/internal/shadow_root_registry.rb +7 -0
- data/lib/dommy/internal/slot_assignment.rb +117 -0
- data/lib/dommy/internal/slot_signals.rb +125 -0
- data/lib/dommy/internal/unsafe_html.rb +79 -77
- data/lib/dommy/internal/url_parser.rb +5 -29
- data/lib/dommy/internal/web_idl.rb +158 -153
- data/lib/dommy/internal/window_bound.rb +14 -0
- data/lib/dommy/internal/window_constructors.rb +3 -0
- data/lib/dommy/internal/xml_serialization.rb +298 -296
- data/lib/dommy/js/dom_interfaces.rb +13 -1
- data/lib/dommy/js/host_bridge.rb +23 -1
- data/lib/dommy/js/host_callbacks.rb +7 -0
- data/lib/dommy/js/host_runtime.js +625 -61
- data/lib/dommy/js/import_map.rb +1 -1
- data/lib/dommy/js/observable_runtime.js +295 -128
- data/lib/dommy/js/webidl_members.js +1 -1
- data/lib/dommy/js/webidl_signatures.js +35 -6
- data/lib/dommy/media_query_list.rb +1 -0
- data/lib/dommy/message_channel.rb +2 -0
- data/lib/dommy/mutation_observer.rb +25 -29
- data/lib/dommy/navigator.rb +4 -1
- data/lib/dommy/node.rb +4 -0
- data/lib/dommy/notification.rb +1 -0
- data/lib/dommy/promise.rb +12 -0
- data/lib/dommy/range.rb +20 -3
- data/lib/dommy/shadow_root.rb +25 -1
- data/lib/dommy/streams.rb +18 -0
- data/lib/dommy/style_declaration.rb +15 -4
- data/lib/dommy/text_codec.rb +7 -5
- data/lib/dommy/tree_walker.rb +68 -61
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +1 -0
- data/lib/dommy/window.rb +22 -4
- data/lib/dommy/worker.rb +1 -0
- data/lib/dommy/xml_http_request.rb +12 -2
- data/lib/dommy.rb +17 -2
- metadata +11 -3
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Generated by script/build_encoding_tables.rb from the Encoding Standard's
|
|
4
|
+
# index-{jis0208,jis0212,big5,euc-kr,gb18030}.txt, against the Ruby
|
|
5
|
+
# encodings LegacyIndexes.ruby_lookup reads. Do not edit by hand.
|
|
6
|
+
#
|
|
7
|
+
# Spec: https://encoding.spec.whatwg.org/#indexes
|
|
8
|
+
|
|
9
|
+
module Dommy
|
|
10
|
+
module Encodings
|
|
11
|
+
# Pointer => code point, where the spec's index and Ruby's table differ.
|
|
12
|
+
MULTIBYTE_INDEX_OVERRIDES = {
|
|
13
|
+
jis0208: {
|
|
14
|
+
}.freeze,
|
|
15
|
+
jis0212: {
|
|
16
|
+
116 => 0xFF5E,
|
|
17
|
+
}.freeze,
|
|
18
|
+
big5: {
|
|
19
|
+
1000 => 0x3875, 1001 => 0x21D53, 1002 => 0x2369E, 1003 => 0x26021, 1004 => 0x3EEC, 1005 => 0x258DE,
|
|
20
|
+
1006 => 0x3AF5, 1007 => 0x7AFC, 1008 => 0x9F97, 1009 => 0x24161, 1010 => 0x2890D, 1011 => 0x231EA,
|
|
21
|
+
1012 => 0x20A8A, 1013 => 0x2325E, 1014 => 0x430A, 1015 => 0x8484, 1016 => 0x9F96, 1017 => 0x942F,
|
|
22
|
+
1018 => 0x4930, 1019 => 0x8613, 1020 => 0x5896, 1021 => 0x974A, 1022 => 0x9218, 1023 => 0x79D0,
|
|
23
|
+
1024 => 0x7A32, 1025 => 0x6660, 1026 => 0x6A29, 1027 => 0x889D, 1028 => 0x744C, 1029 => 0x7BC5,
|
|
24
|
+
1030 => 0x6782, 1031 => 0x7A2C, 1032 => 0x524F, 1033 => 0x9046, 1034 => 0x34E6, 1035 => 0x73C4,
|
|
25
|
+
1036 => 0x25DB9, 1037 => 0x74C6, 1038 => 0x9FC7, 1039 => 0x57B3, 1040 => 0x492F, 1041 => 0x544C,
|
|
26
|
+
1042 => 0x4131, 1043 => 0x2368E, 1044 => 0x5818, 1045 => 0x7A72, 1046 => 0x27B65, 1047 => 0x8B8F,
|
|
27
|
+
1048 => 0x46AE, 1049 => 0x26E88, 1050 => 0x4181, 1051 => 0x25D99, 1052 => 0x7BAE, 1053 => 0x224BC,
|
|
28
|
+
1054 => 0x9FC8, 1055 => 0x224C1, 1056 => 0x224C9, 1057 => 0x224CC, 1058 => 0x9FC9, 1059 => 0x8504,
|
|
29
|
+
1060 => 0x235BB, 1061 => 0x40B4, 1062 => 0x9FCA, 1063 => 0x44E1, 1064 => 0x2ADFF, 1065 => 0x62C1,
|
|
30
|
+
1066 => 0x706E, 1067 => 0x9FCB, 2082 => 0x7BB8, 2088 => 0x7C06, 2103 => 0x7CCE, 2114 => 0x7DD2,
|
|
31
|
+
2123 => 0x7E1D, 2148 => 0x8005, 2151 => 0x8028, 2221 => 0x83C1, 2239 => 0x84A8, 2244 => 0x840F,
|
|
32
|
+
2303 => 0x89A6, 2304 => 0x89A9, 2354 => 0x8D77, 2400 => 0x90FD, 2413 => 0x92B9, 2477 => 0x975C,
|
|
33
|
+
2498 => 0x97FF, 2605 => 0x9F16, 2673 => 0x8503, 2746 => 0x5159, 2747 => 0x515B, 2748 => 0x515D,
|
|
34
|
+
2749 => 0x515E, 2771 => 0x936E, 2780 => 0x7479, 2990 => 0x6D67, 3087 => 0x799B, 3259 => 0x9097,
|
|
35
|
+
3301 => 0x975D, 3436 => 0x701E, 3451 => 0x5B28, 4136 => 0x7201, 4138 => 0x77D7, 4141 => 0x7E87,
|
|
36
|
+
4182 => 0x99D6, 4206 => 0x91D4, 4220 => 0x60DE, 4230 => 0x6FB6, 4241 => 0x8F36, 4258 => 0x4FBB,
|
|
37
|
+
4273 => 0x71DF, 4279 => 0x9104, 4282 => 0x9DF0, 4294 => 0x83CF, 4329 => 0x5C10, 4330 => 0x79E3,
|
|
38
|
+
4349 => 0x5A67, 4419 => 0x8F0B, 4422 => 0x7B51, 4494 => 0x62D0, 4624 => 0x6062, 4694 => 0x75F9,
|
|
39
|
+
4708 => 0x6C4A, 4742 => 0x9B2E, 4748 => 0x9F17, 4815 => 0x50ED, 4828 => 0x5F0C, 4902 => 0x880F,
|
|
40
|
+
4922 => 0x62CE, 4982 => 0x7468, 4992 => 0x7162, 4997 => 0x7250, 5029 => 0x2027, 5038 => 0xFE51,
|
|
41
|
+
5050 => 0x2574, 5120 => 0x00AF, 5121 => 0xFFE3, 5123 => 0x02CD, 5153 => 0xFF5E, 5168 => 0x2295,
|
|
42
|
+
5169 => 0x2299, 5180 => 0xFF0F, 5181 => 0xFF3C, 5182 => 0x2215, 5183 => 0xFE68, 5185 => 0xFFE5,
|
|
43
|
+
5187 => 0xFFE0, 5188 => 0xFFE1, 5287 => 0x5341, 5289 => 0x5345, 5432 => 0x2400, 5433 => 0x2401,
|
|
44
|
+
5434 => 0x2402, 5435 => 0x2403, 5436 => 0x2404, 5437 => 0x2405, 5438 => 0x2406, 5439 => 0x2407,
|
|
45
|
+
5440 => 0x2408, 5441 => 0x2409, 5442 => 0x240A, 5443 => 0x240B, 5444 => 0x240C, 5445 => 0x240D,
|
|
46
|
+
5446 => 0x240E, 5447 => 0x240F, 5448 => 0x2410, 5449 => 0x2411, 5450 => 0x2412, 5451 => 0x2413,
|
|
47
|
+
5452 => 0x2414, 5453 => 0x2415, 5454 => 0x2416, 5455 => 0x2417, 5456 => 0x2418, 5457 => 0x2419,
|
|
48
|
+
5458 => 0x241A, 5459 => 0x241B, 5460 => 0x241C, 5461 => 0x241D, 5462 => 0x241E, 5463 => 0x241F,
|
|
49
|
+
5464 => 0x2421, 5465 => 0x20AC, 10942 => 0x5EF4, 10946 => 0x65E0, 10948 => 0x7676, 10950 => 0x96B6,
|
|
50
|
+
10957 => 0x3003, 10958 => 0x4EDD, 18975 => 0x255E, 18976 => 0x256A, 18977 => 0x2561, 18991 => 0x2550,
|
|
51
|
+
18992 => 0x256D, 18993 => 0x256E, 18994 => 0x2570, 18995 => 0x256F, 19028 => 0x5029, 19035 => 0x507D,
|
|
52
|
+
19088 => 0x5305, 19096 => 0x5344, 19112 => 0x537F, 19162 => 0x5605, 19240 => 0x5A77, 19299 => 0x5E75,
|
|
53
|
+
19305 => 0x5ED0, 19326 => 0x5F58, 19355 => 0x60A4, 19398 => 0x6490, 19439 => 0x6674, 19454 => 0x675E,
|
|
54
|
+
19553 => 0x6C9C, 19554 => 0x6E1D, 19557 => 0x6E2F, 19611 => 0x716E, 19643 => 0x732A, 19672 => 0x745C,
|
|
55
|
+
19697 => 0x74E9, 19748 => 0x7809,
|
|
56
|
+
}.freeze,
|
|
57
|
+
euc_kr: {
|
|
58
|
+
}.freeze,
|
|
59
|
+
gb18030: {
|
|
60
|
+
6555 => 0x3000, 7182 => 0xFE10, 7183 => 0xFE12, 7184 => 0xFE11, 7185 => 0xFE13, 7186 => 0xFE14,
|
|
61
|
+
7187 => 0xFE15, 7188 => 0xFE16, 7201 => 0xFE17, 7202 => 0xFE18, 7208 => 0xFE19, 7533 => 0x1E3F,
|
|
62
|
+
23775 => 0x9FB4, 23783 => 0x9FB5, 23788 => 0x9FB6, 23789 => 0x9FB7, 23795 => 0x9FB8, 23812 => 0x9FB9,
|
|
63
|
+
23829 => 0x9FBA, 23845 => 0x9FBB,
|
|
64
|
+
}.freeze,
|
|
65
|
+
}.freeze
|
|
66
|
+
|
|
67
|
+
# Pointers Ruby's table maps that the spec's index leaves empty.
|
|
68
|
+
MULTIBYTE_INDEX_NULLS = {
|
|
69
|
+
jis0208: [8836..10715].freeze,
|
|
70
|
+
jis0212: [].freeze,
|
|
71
|
+
big5: [].freeze,
|
|
72
|
+
euc_kr: [].freeze,
|
|
73
|
+
gb18030: [].freeze,
|
|
74
|
+
}.freeze
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dommy
|
|
4
|
+
module Encodings
|
|
5
|
+
# HTML's "prescan a byte stream to determine its encoding" over the first
|
|
6
|
+
# 1024 bytes: a walk over the bytes that skips comments and other tags as
|
|
7
|
+
# the tokenizer would, and reads each `<meta>`'s attributes (quoted values
|
|
8
|
+
# included) for a `charset`, or an `http-equiv="content-type"` with a
|
|
9
|
+
# `content` naming one. Running out of bytes ends it with what a leading
|
|
10
|
+
# `<?xml … encoding="…"?>` declares, if anything.
|
|
11
|
+
#
|
|
12
|
+
# Spec: https://html.spec.whatwg.org/#prescan-a-byte-stream-to-determine-its-encoding
|
|
13
|
+
class Prescan
|
|
14
|
+
LIMIT = 1024
|
|
15
|
+
# ASCII whitespace, as bytes: TAB, LF, FF, CR, SPACE.
|
|
16
|
+
SPACE = [0x09, 0x0A, 0x0C, 0x0D, 0x20].freeze
|
|
17
|
+
SPACE_OR_SLASH = (SPACE + [0x2F]).freeze
|
|
18
|
+
SPACE_OR_GT = (SPACE + [0x3E]).freeze
|
|
19
|
+
private_constant :LIMIT, :SPACE, :SPACE_OR_SLASH, :SPACE_OR_GT
|
|
20
|
+
|
|
21
|
+
def self.call(source) = new(source.to_s.b.byteslice(0, LIMIT)).run
|
|
22
|
+
|
|
23
|
+
def initialize(bytes)
|
|
24
|
+
@bytes = bytes
|
|
25
|
+
@position = 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def run
|
|
29
|
+
utf16 = utf16_xml_declaration
|
|
30
|
+
return utf16 if utf16
|
|
31
|
+
|
|
32
|
+
catch(:end) do
|
|
33
|
+
# Only a `<` starts anything; every other byte is passed over.
|
|
34
|
+
while (@position = @bytes.index("<", @position))
|
|
35
|
+
found =
|
|
36
|
+
if starts_with?("<!--") then skip_comment
|
|
37
|
+
elsif meta_tag? then meta
|
|
38
|
+
elsif tag? then skip_tag
|
|
39
|
+
elsif starts_with?("<!") || starts_with?("</") || starts_with?("<?") then skip_to(0x3E)
|
|
40
|
+
end
|
|
41
|
+
return found if found.is_a?(String)
|
|
42
|
+
|
|
43
|
+
@position += 1
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
xml_encoding
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# "Prescan for UTF-16 XML declarations": `<?x` in UTF-16 is UTF-16.
|
|
52
|
+
def utf16_xml_declaration
|
|
53
|
+
return "UTF-16LE" if @bytes.start_with?("<\0?\0x\0".b)
|
|
54
|
+
|
|
55
|
+
"UTF-16BE" if @bytes.start_with?("\0<\0?\0x".b)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def byte
|
|
59
|
+
@bytes.getbyte(@position) || throw(:end)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def starts_with?(text) = @bytes.byteslice(@position, text.bytesize) == text
|
|
63
|
+
|
|
64
|
+
def letter?(value) = value && ((0x41..0x5A).cover?(value) || (0x61..0x7A).cover?(value))
|
|
65
|
+
|
|
66
|
+
# `<meta` (ASCII case-insensitive) followed by whitespace or `/`.
|
|
67
|
+
def meta_tag?
|
|
68
|
+
@bytes.byteslice(@position, 5)&.casecmp?("<meta") && SPACE_OR_SLASH.include?(@bytes.getbyte(@position + 5))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# `<` or `</` followed by an ASCII letter.
|
|
72
|
+
def tag?
|
|
73
|
+
return false unless @bytes.getbyte(@position) == 0x3C
|
|
74
|
+
|
|
75
|
+
letter?(@bytes.getbyte(@position + 1)) || (@bytes.getbyte(@position + 1) == 0x2F && letter?(@bytes.getbyte(@position + 2)))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# To the first `>` preceded by two `-` after the `<`; those may be the
|
|
79
|
+
# `<!--`'s own, so `<!-->` and `<!--->` are closed comments.
|
|
80
|
+
def skip_comment
|
|
81
|
+
index = @bytes.index("-->".b, @position + 2) || throw(:end)
|
|
82
|
+
@position = index + 2
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def skip_to(value)
|
|
86
|
+
index = @bytes.index(value.chr, @position + 1) || throw(:end)
|
|
87
|
+
@position = index
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# A tag that is not a `<meta>`: past its name, then its attributes, so a
|
|
91
|
+
# `>` inside a quoted value does not end it.
|
|
92
|
+
def skip_tag
|
|
93
|
+
@position += 1
|
|
94
|
+
@position += 1 until SPACE_OR_GT.include?(byte)
|
|
95
|
+
nil while attribute
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The `<meta>` steps: its encoding when it declares one, else nil.
|
|
99
|
+
def meta
|
|
100
|
+
@position += 5
|
|
101
|
+
names = []
|
|
102
|
+
got_pragma = false
|
|
103
|
+
need_pragma = nil
|
|
104
|
+
charset = nil
|
|
105
|
+
while (name, value = attribute)
|
|
106
|
+
next if names.include?(name)
|
|
107
|
+
|
|
108
|
+
names << name
|
|
109
|
+
case name
|
|
110
|
+
when "http-equiv"
|
|
111
|
+
got_pragma = true if value == "content-type"
|
|
112
|
+
when "content"
|
|
113
|
+
encoding = encoding_from_content(value)
|
|
114
|
+
if encoding && charset.nil?
|
|
115
|
+
charset = encoding
|
|
116
|
+
need_pragma = true
|
|
117
|
+
end
|
|
118
|
+
when "charset"
|
|
119
|
+
charset = Encodings.get(value) || false
|
|
120
|
+
need_pragma = false
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
return nil if need_pragma.nil? || (need_pragma && !got_pragma) || !charset
|
|
124
|
+
return "UTF-8" if charset.start_with?("UTF-16")
|
|
125
|
+
|
|
126
|
+
charset == "x-user-defined" ? "windows-1252" : charset
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# "Get an attribute": the next attribute's name and value (ASCII
|
|
130
|
+
# lowercased, as bytes), or nil at the tag's `>`.
|
|
131
|
+
def attribute
|
|
132
|
+
@position += 1 while SPACE_OR_SLASH.include?(byte)
|
|
133
|
+
return nil if byte == 0x3E
|
|
134
|
+
|
|
135
|
+
name = +"".b
|
|
136
|
+
value = +"".b
|
|
137
|
+
loop do
|
|
138
|
+
current = byte
|
|
139
|
+
if current == 0x3D && !name.empty?
|
|
140
|
+
@position += 1
|
|
141
|
+
break
|
|
142
|
+
elsif SPACE.include?(current)
|
|
143
|
+
@position += 1 while SPACE.include?(byte)
|
|
144
|
+
return [name, value] unless byte == 0x3D
|
|
145
|
+
|
|
146
|
+
@position += 1
|
|
147
|
+
break
|
|
148
|
+
elsif current == 0x2F || current == 0x3E
|
|
149
|
+
return [name, value]
|
|
150
|
+
end
|
|
151
|
+
name << lower(current)
|
|
152
|
+
@position += 1
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
@position += 1 while SPACE.include?(byte)
|
|
156
|
+
current = byte
|
|
157
|
+
if current == 0x22 || current == 0x27
|
|
158
|
+
loop do
|
|
159
|
+
@position += 1
|
|
160
|
+
if byte == current
|
|
161
|
+
@position += 1
|
|
162
|
+
return [name, value]
|
|
163
|
+
end
|
|
164
|
+
value << lower(byte)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
return [name, value] if current == 0x3E
|
|
168
|
+
|
|
169
|
+
loop do
|
|
170
|
+
current = byte
|
|
171
|
+
return [name, value] if SPACE.include?(current) || current == 0x3E
|
|
172
|
+
|
|
173
|
+
value << lower(current)
|
|
174
|
+
@position += 1
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def lower(value) = (0x41..0x5A).cover?(value) ? value + 0x20 : value
|
|
179
|
+
|
|
180
|
+
# HTML's "extracting a character encoding from a meta element", for a
|
|
181
|
+
# `content` attribute's value: the encoding its `charset=` names, or nil.
|
|
182
|
+
#
|
|
183
|
+
# Spec: https://html.spec.whatwg.org/#algorithm-for-extracting-a-character-encoding-from-a-meta-element
|
|
184
|
+
def encoding_from_content(content)
|
|
185
|
+
position = 0
|
|
186
|
+
loop do
|
|
187
|
+
index = content.index(/charset/i, position) or return nil
|
|
188
|
+
position = index + 7
|
|
189
|
+
position += 1 while SPACE.include?(content.getbyte(position))
|
|
190
|
+
next unless content.getbyte(position) == 0x3D
|
|
191
|
+
|
|
192
|
+
position += 1
|
|
193
|
+
position += 1 while SPACE.include?(content.getbyte(position))
|
|
194
|
+
first = content.getbyte(position) or return nil
|
|
195
|
+
if first == 0x22 || first == 0x27
|
|
196
|
+
close = content.index(first.chr, position + 1) or return nil
|
|
197
|
+
return Encodings.get(content.byteslice(position + 1...close))
|
|
198
|
+
end
|
|
199
|
+
return Encodings.get(content.byteslice(position..)[/\A[^\t\n\f\r ;]*/n])
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# "Get an XML encoding": the encoding a leading `<?xml` declaration's
|
|
204
|
+
# `encoding="…"` (before its `>`, quoted, with no byte at or below 0x20)
|
|
205
|
+
# names, with UTF-16 read as UTF-8, or nil.
|
|
206
|
+
def xml_encoding
|
|
207
|
+
return nil unless @bytes.start_with?("<?xml")
|
|
208
|
+
|
|
209
|
+
finish = @bytes.index(">") or return nil
|
|
210
|
+
position = @bytes.index("encoding")
|
|
211
|
+
return nil if position.nil? || position >= finish
|
|
212
|
+
|
|
213
|
+
position = skip_controls(position + 8)
|
|
214
|
+
return nil unless @bytes.getbyte(position) == 0x3D
|
|
215
|
+
|
|
216
|
+
position = skip_controls(position + 1)
|
|
217
|
+
quote = @bytes.getbyte(position)
|
|
218
|
+
return nil unless quote == 0x22 || quote == 0x27
|
|
219
|
+
|
|
220
|
+
close = @bytes.index(quote.chr, position + 1) or return nil
|
|
221
|
+
label = @bytes.byteslice(position + 1...close)
|
|
222
|
+
return nil if label.each_byte.any? { |value| value <= 0x20 }
|
|
223
|
+
|
|
224
|
+
name = Encodings.get(label)
|
|
225
|
+
name&.start_with?("UTF-16") ? "UTF-8" : name
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def skip_controls(position)
|
|
229
|
+
position += 1 while (value = @bytes.getbyte(position)) && value <= 0x20
|
|
230
|
+
position
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
data/lib/dommy/encodings.rb
CHANGED
|
@@ -8,27 +8,14 @@ module Dommy
|
|
|
8
8
|
# each encoding that turns bytes into code points, streaming, with the
|
|
9
9
|
# replacement-or-throw error mode TextDecoder exposes as `fatal`.
|
|
10
10
|
#
|
|
11
|
-
# UTF-8, UTF-16 and the legacy
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# sequence yields.
|
|
11
|
+
# Every decoder is the spec's own algorithm: UTF-8, UTF-16 and the legacy
|
|
12
|
+
# single-byte encodings here, with their index tables, and the legacy
|
|
13
|
+
# multi-byte ones (Big5, EUC-JP, ISO-2022-JP, Shift_JIS, EUC-KR, GBK,
|
|
14
|
+
# gb18030) in encodings/legacy_multibyte, which read their indexes through
|
|
15
|
+
# the Ruby encoding that matches each.
|
|
17
16
|
#
|
|
18
17
|
# Spec: https://encoding.spec.whatwg.org/
|
|
19
18
|
module Encodings
|
|
20
|
-
# The Ruby encoding whose mapping is closest to the spec's index for each
|
|
21
|
-
# legacy multi-byte encoding.
|
|
22
|
-
RUBY_ENCODINGS = {
|
|
23
|
-
"Big5" => "Big5-HKSCS",
|
|
24
|
-
"EUC-JP" => "EUC-JP",
|
|
25
|
-
"ISO-2022-JP" => "ISO-2022-JP",
|
|
26
|
-
"Shift_JIS" => "Windows-31J",
|
|
27
|
-
"EUC-KR" => "CP949",
|
|
28
|
-
"GBK" => "GBK",
|
|
29
|
-
"gb18030" => "GB18030"
|
|
30
|
-
}.freeze
|
|
31
|
-
|
|
32
19
|
REPLACEMENT_CHARACTER = 0xFFFD
|
|
33
20
|
|
|
34
21
|
# The name of the encoding `label` stands for, or nil. Only ASCII
|
|
@@ -36,7 +23,24 @@ module Dommy
|
|
|
36
23
|
#
|
|
37
24
|
# Spec: https://encoding.spec.whatwg.org/#concept-encoding-get
|
|
38
25
|
def self.get(label)
|
|
39
|
-
LABELS[label.gsub(/\A[\t\n\f\r ]+|[\t\n\f\r ]+\z/, "").downcase]
|
|
26
|
+
LABELS[label.gsub(/\A[\t\n\f\r ]+|[\t\n\f\r ]+\z/, "").downcase(:ascii)]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The encoding `encoding` names, given as an Encoding Standard name or
|
|
30
|
+
# label; an ArgumentError for anything else.
|
|
31
|
+
def self.name_for(encoding)
|
|
32
|
+
text = encoding.to_s
|
|
33
|
+
return text if LABELS.value?(text)
|
|
34
|
+
|
|
35
|
+
get(text) or raise ArgumentError, "unknown encoding: #{encoding.inspect}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The encoding text is encoded in when `name` is the one asked for: UTF-8
|
|
39
|
+
# in place of replacement, UTF-16BE and UTF-16LE, which have no encoder.
|
|
40
|
+
#
|
|
41
|
+
# Spec: https://encoding.spec.whatwg.org/#get-an-output-encoding
|
|
42
|
+
def self.output_encoding(name)
|
|
43
|
+
name.start_with?("UTF-16") || name == "replacement" ? "UTF-8" : name
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
# The byte-order marks that name their own encoding, longest first.
|
|
@@ -67,16 +71,98 @@ module Dommy
|
|
|
67
71
|
valid_utf8(bytes, name) || decoder_for(name).decode(bytes, flush: true).pack("U*")
|
|
68
72
|
end
|
|
69
73
|
|
|
70
|
-
# A document's bytes as text,
|
|
71
|
-
#
|
|
72
|
-
#
|
|
74
|
+
# A document's bytes as text, and the encoding they were decoded with
|
|
75
|
+
# (::document_encoding), which is the document's: a caller hands both to
|
|
76
|
+
# the document, so that its URLs encode a query as its bytes were.
|
|
77
|
+
# `html: false` (an XML or a text document) skips the `<meta>` prescan.
|
|
78
|
+
def self.decode_document(bytes, content_type, html: true)
|
|
79
|
+
encoding = document_encoding(bytes, content_type, html: html)
|
|
80
|
+
[decode(bytes.to_s, encoding), encoding]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The encoding an HTML document's bytes are in: a byte-order mark's, else
|
|
84
|
+
# the Content-Type's `charset`, else what a `<meta>` in the first 1024
|
|
85
|
+
# bytes declares (the prescan), else UTF-8.
|
|
73
86
|
#
|
|
74
87
|
# Spec: https://html.spec.whatwg.org/#determining-the-character-encoding
|
|
75
|
-
def self.
|
|
88
|
+
def self.document_encoding(bytes, content_type, html: true)
|
|
89
|
+
bytes = bytes.to_s.b
|
|
90
|
+
BOMS.each { |mark, name| return name if bytes.start_with?(mark) }
|
|
76
91
|
label = Internal::MimeType.charset_of(content_type.to_s)
|
|
77
|
-
|
|
92
|
+
(label && get(label)) || (html && prescan(bytes)) || "UTF-8"
|
|
78
93
|
end
|
|
79
94
|
|
|
95
|
+
# HTML's "prescan a byte stream to determine its encoding" (Prescan): what
|
|
96
|
+
# the first 1024 bytes' `<meta charset>` or `<meta http-equiv=content-type
|
|
97
|
+
# content="…; charset=…">` declares, else what a leading `<?xml … encoding=…?>`
|
|
98
|
+
# does, or nil. A declared UTF-16 is UTF-8 (the bytes are ASCII-compatible
|
|
99
|
+
# if the tag was read), and x-user-defined windows-1252; `<?x` in UTF-16
|
|
100
|
+
# itself is that UTF-16.
|
|
101
|
+
def self.prescan(source) = Prescan.call(source)
|
|
102
|
+
|
|
103
|
+
# The bytes `string` encodes to in the encoding named `name` (as `get`
|
|
104
|
+
# returns it); a code point the encoding cannot represent is the bytes the
|
|
105
|
+
# block returns for it. The legacy multi-byte encoders and the single-byte
|
|
106
|
+
# ones (an index read backwards) are the spec's own; Ruby encodes the rest,
|
|
107
|
+
# and UTF-8 is the string's own bytes.
|
|
108
|
+
#
|
|
109
|
+
# Spec: https://encoding.spec.whatwg.org/#encode
|
|
110
|
+
def self.encode(name, string, &unmappable)
|
|
111
|
+
return LegacyEncoders.encode(name, string, &unmappable).pack("C*") if LegacyEncoders.handles?(name)
|
|
112
|
+
return single_byte_encode(single_byte_pointers(name), string, &unmappable) if SINGLE_BYTE_INDEXES.key?(name)
|
|
113
|
+
return x_user_defined_encode(string, &unmappable) if name == "x-user-defined"
|
|
114
|
+
|
|
115
|
+
ruby = begin
|
|
116
|
+
::Encoding.find(name.to_s)
|
|
117
|
+
rescue ArgumentError
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
return string.b if ruby.nil? || ruby == ::Encoding::UTF_8
|
|
121
|
+
|
|
122
|
+
begin
|
|
123
|
+
string.encode(ruby).b
|
|
124
|
+
rescue ::Encoding::UndefinedConversionError, ::Encoding::InvalidByteSequenceError
|
|
125
|
+
string.each_char.each_with_object(+"".b) do |char, out|
|
|
126
|
+
out << char.encode(ruby).b
|
|
127
|
+
rescue ::Encoding::UndefinedConversionError, ::Encoding::InvalidByteSequenceError
|
|
128
|
+
out << unmappable.call(char.ord).pack("C*")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# A single-byte index read backwards, code point => byte, made once per
|
|
134
|
+
# encoding.
|
|
135
|
+
@single_byte_pointers = {}
|
|
136
|
+
def self.single_byte_pointers(name)
|
|
137
|
+
@single_byte_pointers[name] ||= SINGLE_BYTE_INDEXES[name].each_with_index.each_with_object({}) do |(code_point, pointer), map|
|
|
138
|
+
map[code_point] ||= pointer + 0x80 if code_point
|
|
139
|
+
end.freeze
|
|
140
|
+
end
|
|
141
|
+
private_class_method :single_byte_pointers
|
|
142
|
+
|
|
143
|
+
# Spec: https://encoding.spec.whatwg.org/#single-byte-encoder
|
|
144
|
+
def self.single_byte_encode(pointers, string, &unmappable)
|
|
145
|
+
string.each_codepoint.each_with_object(+"".b) do |code_point, out|
|
|
146
|
+
byte = code_point <= 0x7F ? code_point : pointers[code_point]
|
|
147
|
+
byte ? out << byte : out << unmappable.call(code_point).pack("C*")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
private_class_method :single_byte_encode
|
|
151
|
+
|
|
152
|
+
# Spec: https://encoding.spec.whatwg.org/#x-user-defined-encoder
|
|
153
|
+
def self.x_user_defined_encode(string, &unmappable)
|
|
154
|
+
string.each_codepoint.each_with_object(+"".b) do |code_point, out|
|
|
155
|
+
if code_point <= 0x7F
|
|
156
|
+
out << code_point
|
|
157
|
+
elsif code_point.between?(0xF780, 0xF7FF)
|
|
158
|
+
out << (code_point - 0xF780 + 0x80)
|
|
159
|
+
else
|
|
160
|
+
out << unmappable.call(code_point).pack("C*")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
private_class_method :x_user_defined_encode
|
|
165
|
+
|
|
80
166
|
# Already-valid UTF-8 needs no decoding: the spec's decoder would walk it
|
|
81
167
|
# byte by byte and rebuild it code point by code point, which measures ~40x
|
|
82
168
|
# the cost of asking Ruby, on the whole-buffer path an XHR response takes.
|
|
@@ -101,17 +187,21 @@ module Dommy
|
|
|
101
187
|
when "UTF-16BE" then Utf16Decoder.new(fatal, big_endian: true)
|
|
102
188
|
when "x-user-defined" then XUserDefinedDecoder.new(fatal)
|
|
103
189
|
when "replacement" then ReplacementDecoder.new(fatal)
|
|
190
|
+
when "ISO-2022-JP" then Iso2022JpDecoder.new(fatal)
|
|
191
|
+
when "Shift_JIS" then ShiftJisDecoder.new(fatal)
|
|
192
|
+
when "EUC-JP" then EucJpDecoder.new(fatal)
|
|
193
|
+
when "Big5" then Big5Decoder.new(fatal)
|
|
194
|
+
when "EUC-KR" then EucKrDecoder.new(fatal)
|
|
195
|
+
# GBK's decoder is gb18030's.
|
|
196
|
+
when "GBK", "gb18030" then Gb18030Decoder.new(fatal)
|
|
104
197
|
else
|
|
105
198
|
index = SINGLE_BYTE_INDEXES[name]
|
|
106
199
|
return SingleByteDecoder.new(fatal, index) if index
|
|
107
200
|
|
|
108
|
-
ruby_encoding = RUBY_ENCODINGS[name]
|
|
109
201
|
# `name` comes from ::get, so every spec encoding is covered above. A
|
|
110
202
|
# name from anywhere else is the caller's mistake, and says so rather
|
|
111
203
|
# than surfacing a Hash's KeyError.
|
|
112
|
-
raise ArgumentError, "not an encoding the standard names: #{name.inspect}"
|
|
113
|
-
|
|
114
|
-
ConverterDecoder.new(fatal, ruby_encoding)
|
|
204
|
+
raise ArgumentError, "not an encoding the standard names: #{name.inspect}"
|
|
115
205
|
end
|
|
116
206
|
end
|
|
117
207
|
|
|
@@ -307,34 +397,8 @@ module Dommy
|
|
|
307
397
|
[replacement!]
|
|
308
398
|
end
|
|
309
399
|
end
|
|
310
|
-
|
|
311
|
-
# A legacy multi-byte encoding, through Ruby's converter. Partial input is
|
|
312
|
-
# held between calls; at a flush what is left over is an error.
|
|
313
|
-
class ConverterDecoder < Decoder
|
|
314
|
-
def initialize(fatal, ruby_encoding)
|
|
315
|
-
super(fatal)
|
|
316
|
-
options = fatal ? {} : {invalid: :replace, undef: :replace, replace: "\uFFFD"}
|
|
317
|
-
@converter = ::Encoding::Converter.new(ruby_encoding, "UTF-8", **options)
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
def decode(bytes, flush:)
|
|
321
|
-
output = +""
|
|
322
|
-
result = @converter.primitive_convert(bytes.dup, output, nil, nil, partial_input: !flush)
|
|
323
|
-
code_points = output.codepoints
|
|
324
|
-
case result
|
|
325
|
-
when :finished, :source_buffer_empty
|
|
326
|
-
code_points
|
|
327
|
-
when :incomplete_input, :invalid_byte_sequence, :undefined_conversion
|
|
328
|
-
# Only reached in fatal mode, or by an incomplete sequence at a
|
|
329
|
-
# flush; either way the stream is over.
|
|
330
|
-
code_points << replacement!
|
|
331
|
-
else
|
|
332
|
-
# :destination_buffer_full and :after_output need a byte limit or an
|
|
333
|
-
# output filter to occur, and this call passes neither. Reaching here
|
|
334
|
-
# would mean the converter contract changed under us.
|
|
335
|
-
raise "unexpected Encoding::Converter result: #{result.inspect}"
|
|
336
|
-
end
|
|
337
|
-
end
|
|
338
|
-
end
|
|
339
400
|
end
|
|
340
401
|
end
|
|
402
|
+
|
|
403
|
+
require_relative "encodings/legacy_multibyte"
|
|
404
|
+
require_relative "encodings/prescan"
|