dommy 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +30 -38
- data/lib/dommy/animation.rb +10 -2
- data/lib/dommy/attr.rb +197 -32
- data/lib/dommy/backend/nokogiri_adapter.rb +127 -0
- data/lib/dommy/backend/nokolexbor_adapter.rb +117 -0
- data/lib/dommy/backend.rb +175 -0
- data/lib/dommy/blob.rb +30 -11
- data/lib/dommy/bridge/constructor_registry.rb +28 -0
- data/lib/dommy/bridge/methods.rb +57 -0
- data/lib/dommy/bridge.rb +97 -0
- data/lib/dommy/callable_invoker.rb +36 -0
- data/lib/dommy/compression_streams.rb +4 -4
- data/lib/dommy/cookie_store.rb +4 -2
- data/lib/dommy/crypto.rb +16 -9
- data/lib/dommy/css.rb +53 -7
- data/lib/dommy/custom_elements.rb +33 -9
- data/lib/dommy/data_transfer.rb +4 -0
- data/lib/dommy/document.rb +693 -60
- data/lib/dommy/dom_parser.rb +29 -15
- data/lib/dommy/element.rb +1147 -438
- data/lib/dommy/event.rb +279 -79
- data/lib/dommy/event_source.rb +14 -10
- data/lib/dommy/fetch.rb +509 -39
- data/lib/dommy/file_reader.rb +14 -6
- data/lib/dommy/form_data.rb +3 -3
- data/lib/dommy/history.rb +46 -8
- data/lib/dommy/html_collection.rb +59 -6
- data/lib/dommy/html_elements.rb +153 -1502
- data/lib/dommy/internal/css_pseudo_handlers.rb +137 -0
- data/lib/dommy/internal/dom_matching.rb +3 -3
- data/lib/dommy/internal/global_functions.rb +26 -0
- data/lib/dommy/internal/idna.rb +16 -7
- data/lib/dommy/internal/ipv4_parser.rb +22 -7
- data/lib/dommy/internal/mutation_coordinator.rb +11 -2
- data/lib/dommy/internal/namespaces.rb +70 -0
- data/lib/dommy/internal/node_equality.rb +86 -0
- data/lib/dommy/internal/node_traversal.rb +1 -1
- data/lib/dommy/internal/node_wrapper_cache.rb +77 -31
- data/lib/dommy/internal/observable_callback.rb +1 -5
- data/lib/dommy/internal/parent_node.rb +126 -0
- data/lib/dommy/internal/reflected_attributes.rb +103 -13
- data/lib/dommy/internal/selector_parser.rb +664 -0
- data/lib/dommy/internal/template_content_registry.rb +6 -6
- data/lib/dommy/internal/url_parser.rb +677 -0
- data/lib/dommy/intersection_observer.rb +4 -2
- data/lib/dommy/location.rb +10 -4
- data/lib/dommy/media_query_list.rb +10 -4
- data/lib/dommy/message_channel.rb +41 -11
- data/lib/dommy/mutation_observer.rb +76 -23
- data/lib/dommy/navigator.rb +38 -24
- data/lib/dommy/node.rb +158 -16
- data/lib/dommy/notification.rb +6 -4
- data/lib/dommy/parser.rb +13 -13
- data/lib/dommy/performance.rb +4 -0
- data/lib/dommy/performance_observer.rb +4 -2
- data/lib/dommy/promise.rb +14 -14
- data/lib/dommy/range.rb +74 -5
- data/lib/dommy/resize_observer.rb +4 -2
- data/lib/dommy/scheduler.rb +34 -13
- data/lib/dommy/shadow_root.rb +31 -60
- data/lib/dommy/storage.rb +2 -0
- data/lib/dommy/streams.rb +40 -49
- data/lib/dommy/svg_elements.rb +204 -3606
- data/lib/dommy/text_codec.rb +178 -25
- data/lib/dommy/tree_walker.rb +270 -81
- data/lib/dommy/url.rb +305 -450
- data/lib/dommy/url_pattern.rb +2 -0
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +49 -19
- data/lib/dommy/window.rb +205 -203
- data/lib/dommy/worker.rb +12 -12
- data/lib/dommy/xml_http_request.rb +32 -7
- data/lib/dommy.rb +19 -2
- metadata +22 -27
data/lib/dommy/crypto.rb
CHANGED
|
@@ -62,6 +62,8 @@ module Dommy
|
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
include Bridge::Methods
|
|
66
|
+
js_methods %w[randomUUID getRandomValues]
|
|
65
67
|
def __js_call__(method, args)
|
|
66
68
|
case method
|
|
67
69
|
when "randomUUID"
|
|
@@ -99,7 +101,9 @@ module Dommy
|
|
|
99
101
|
hasher = ALGORITHMS[name]
|
|
100
102
|
raise ArgumentError, "unsupported algorithm: #{name}" unless hasher
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
# WHATWG: digest() resolves to an ArrayBuffer — wrap so it crosses the
|
|
105
|
+
# JS boundary as a bare ArrayBuffer (not a plain Array).
|
|
106
|
+
Bridge::ArrayBuffer.new(hasher.call(coerce_bytes(data)).bytes)
|
|
103
107
|
end
|
|
104
108
|
end
|
|
105
109
|
|
|
@@ -182,7 +186,7 @@ module Dommy
|
|
|
182
186
|
raise ArgumentError, "HMAC key required" unless key.is_a?(CryptoKey) && key.algorithm_name == "HMAC"
|
|
183
187
|
raise ArgumentError, "key.usages must include 'sign'" unless key.usages.include?("sign")
|
|
184
188
|
|
|
185
|
-
OpenSSL::HMAC.digest(openssl_digest_name(key.hash_name), key.
|
|
189
|
+
OpenSSL::HMAC.digest(openssl_digest_name(key.hash_name), key.__dommy_bytes__, coerce_bytes(data)).bytes
|
|
186
190
|
end
|
|
187
191
|
end
|
|
188
192
|
|
|
@@ -192,7 +196,7 @@ module Dommy
|
|
|
192
196
|
raise ArgumentError, "HMAC key required" unless key.is_a?(CryptoKey) && key.algorithm_name == "HMAC"
|
|
193
197
|
raise ArgumentError, "key.usages must include 'verify'" unless key.usages.include?("verify")
|
|
194
198
|
|
|
195
|
-
expected = OpenSSL::HMAC.digest(openssl_digest_name(key.hash_name), key.
|
|
199
|
+
expected = OpenSSL::HMAC.digest(openssl_digest_name(key.hash_name), key.__dommy_bytes__, coerce_bytes(data))
|
|
196
200
|
sig_bytes = coerce_bytes(signature)
|
|
197
201
|
if expected.bytesize == sig_bytes.bytesize
|
|
198
202
|
OpenSSL.fixed_length_secure_compare(expected, sig_bytes)
|
|
@@ -230,6 +234,8 @@ module Dommy
|
|
|
230
234
|
end
|
|
231
235
|
end
|
|
232
236
|
|
|
237
|
+
include Bridge::Methods
|
|
238
|
+
js_methods %w[digest generateKey importKey sign verify encrypt decrypt]
|
|
233
239
|
def __js_call__(method, args)
|
|
234
240
|
case method
|
|
235
241
|
when "digest"
|
|
@@ -327,7 +333,7 @@ module Dommy
|
|
|
327
333
|
end
|
|
328
334
|
|
|
329
335
|
def build_gcm_cipher(direction, algorithm, key)
|
|
330
|
-
raw_key = key.is_a?(CryptoKey) ? key.
|
|
336
|
+
raw_key = key.is_a?(CryptoKey) ? key.__dommy_bytes__ : coerce_bytes(key)
|
|
331
337
|
raise ArgumentError, "AES-GCM key must be 16/24/32 bytes" unless [16, 24, 32].include?(raw_key.bytesize)
|
|
332
338
|
|
|
333
339
|
iv = algorithm.is_a?(Hash) ? (algorithm["iv"] || algorithm[:iv]) : nil
|
|
@@ -359,9 +365,9 @@ module Dommy
|
|
|
359
365
|
end
|
|
360
366
|
|
|
361
367
|
# `CryptoKey` — opaque key handle returned by SubtleCrypto.
|
|
362
|
-
# `extractable: false` keys reject export attempts; the
|
|
363
|
-
#
|
|
364
|
-
#
|
|
368
|
+
# `extractable: false` keys reject export attempts; the raw bytes are
|
|
369
|
+
# reachable only through the `__dommy_bytes__` ecosystem accessor, never
|
|
370
|
+
# the public (Web-mirroring) API.
|
|
365
371
|
class CryptoKey
|
|
366
372
|
attr_reader :type, :algorithm_name, :hash_name, :usages, :extractable
|
|
367
373
|
|
|
@@ -374,8 +380,9 @@ module Dommy
|
|
|
374
380
|
@usages = usages.map(&:to_s).freeze
|
|
375
381
|
end
|
|
376
382
|
|
|
377
|
-
#
|
|
378
|
-
|
|
383
|
+
# Low-level ecosystem accessor (see __dommy_ convention) — the public
|
|
384
|
+
# Web API never exposes raw key bytes.
|
|
385
|
+
def __dommy_bytes__
|
|
379
386
|
@bytes
|
|
380
387
|
end
|
|
381
388
|
|
data/lib/dommy/css.rb
CHANGED
|
@@ -63,7 +63,7 @@ module Dommy
|
|
|
63
63
|
idx = index.nil? ? @css_rules.length : index.to_i
|
|
64
64
|
raise DOMException::IndexSizeError, "out of range" if idx < 0 || idx > @css_rules.length
|
|
65
65
|
|
|
66
|
-
@css_rules.
|
|
66
|
+
@css_rules.__internal_insert__(idx, CSSRule.new(rule_text.to_s, self))
|
|
67
67
|
idx
|
|
68
68
|
end
|
|
69
69
|
|
|
@@ -71,17 +71,17 @@ module Dommy
|
|
|
71
71
|
idx = index.to_i
|
|
72
72
|
raise DOMException::IndexSizeError, "out of range" if idx < 0 || idx >= @css_rules.length
|
|
73
73
|
|
|
74
|
-
@css_rules.
|
|
74
|
+
@css_rules.__internal_delete_at__(idx)
|
|
75
75
|
nil
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# `replaceSync(text)` — replace all rules with a single rule blob
|
|
79
79
|
# (no parsing — we keep it as one opaque entry).
|
|
80
80
|
def replace_sync(text)
|
|
81
|
-
@css_rules.
|
|
81
|
+
@css_rules.__internal_clear__
|
|
82
82
|
return nil if text.to_s.empty?
|
|
83
83
|
|
|
84
|
-
@css_rules.
|
|
84
|
+
@css_rules.__internal_insert__(0, CSSRule.new(text.to_s, self))
|
|
85
85
|
nil
|
|
86
86
|
end
|
|
87
87
|
|
|
@@ -125,6 +125,8 @@ module Dommy
|
|
|
125
125
|
nil
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
+
include Bridge::Methods
|
|
129
|
+
js_methods %w[insertRule deleteRule replaceSync replace]
|
|
128
130
|
def __js_call__(method, args)
|
|
129
131
|
case method
|
|
130
132
|
when "insertRule"
|
|
@@ -173,15 +175,15 @@ module Dommy
|
|
|
173
175
|
@rules.dup
|
|
174
176
|
end
|
|
175
177
|
|
|
176
|
-
def
|
|
178
|
+
def __internal_insert__(index, rule)
|
|
177
179
|
@rules.insert(index, rule)
|
|
178
180
|
end
|
|
179
181
|
|
|
180
|
-
def
|
|
182
|
+
def __internal_delete_at__(index)
|
|
181
183
|
@rules.delete_at(index)
|
|
182
184
|
end
|
|
183
185
|
|
|
184
|
-
def
|
|
186
|
+
def __internal_clear__
|
|
185
187
|
@rules.clear
|
|
186
188
|
end
|
|
187
189
|
|
|
@@ -196,6 +198,8 @@ module Dommy
|
|
|
196
198
|
end
|
|
197
199
|
end
|
|
198
200
|
|
|
201
|
+
include Bridge::Methods
|
|
202
|
+
js_methods %w[item]
|
|
199
203
|
def __js_call__(method, args)
|
|
200
204
|
case method
|
|
201
205
|
when "item"
|
|
@@ -280,4 +284,46 @@ module Dommy
|
|
|
280
284
|
nil
|
|
281
285
|
end
|
|
282
286
|
end
|
|
287
|
+
|
|
288
|
+
# `window.CSS` namespace object — `escape()` for safe selector building
|
|
289
|
+
# (used by Turbo and friends) and a `supports()` stub (no CSS engine).
|
|
290
|
+
class CSSNamespace
|
|
291
|
+
def __js_get__(_key) = nil
|
|
292
|
+
def __js_set__(_key, _value) = Bridge::UNHANDLED
|
|
293
|
+
|
|
294
|
+
include Bridge::Methods
|
|
295
|
+
js_methods %w[escape supports]
|
|
296
|
+
def __js_call__(method, args)
|
|
297
|
+
case method
|
|
298
|
+
when "escape"
|
|
299
|
+
self.class.escape(args[0])
|
|
300
|
+
when "supports"
|
|
301
|
+
false
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# CSSOM `CSS.escape` — escape a string for use as an identifier in a
|
|
306
|
+
# selector. Follows the spec's char rules closely enough for selectors.
|
|
307
|
+
def self.escape(value)
|
|
308
|
+
str = value.to_s
|
|
309
|
+
out = +""
|
|
310
|
+
str.each_char.with_index do |ch, i|
|
|
311
|
+
code = ch.ord
|
|
312
|
+
if code.zero?
|
|
313
|
+
out << "\uFFFD"
|
|
314
|
+
elsif (code >= 0x01 && code <= 0x1F) || code == 0x7F ||
|
|
315
|
+
(i.zero? && code >= 0x30 && code <= 0x39) ||
|
|
316
|
+
(i == 1 && code >= 0x30 && code <= 0x39 && str[0] == "-")
|
|
317
|
+
out << "\\#{code.to_s(16)} "
|
|
318
|
+
elsif code >= 0x80 || code == 0x2D || code == 0x5F ||
|
|
319
|
+
(code >= 0x30 && code <= 0x39) ||
|
|
320
|
+
(code >= 0x41 && code <= 0x5A) || (code >= 0x61 && code <= 0x7A)
|
|
321
|
+
out << ch
|
|
322
|
+
else
|
|
323
|
+
out << "\\#{ch}"
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
out
|
|
327
|
+
end
|
|
328
|
+
end
|
|
283
329
|
end
|
|
@@ -10,7 +10,21 @@ module Dommy
|
|
|
10
10
|
#
|
|
11
11
|
# Names must contain a hyphen per the HTML spec (e.g., `my-button`).
|
|
12
12
|
class CustomElementRegistry
|
|
13
|
-
|
|
13
|
+
# https://html.spec.whatwg.org/#valid-custom-element-name
|
|
14
|
+
# PCENChar — the characters allowed after the first (ASCII-lower) char: a
|
|
15
|
+
# superset of [-._0-9a-z] plus wide Unicode ranges. A valid name is
|
|
16
|
+
# `[a-z] PCENChar* - PCENChar*` (i.e. lower-alpha start + at least one "-").
|
|
17
|
+
PCEN = "\\-._0-9a-z\\u00B7\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u037D" \
|
|
18
|
+
"\\u037F-\\u1FFF\\u200C-\\u200D\\u203F-\\u2040\\u2070-\\u218F" \
|
|
19
|
+
"\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\u{10000}-\\u{EFFFF}"
|
|
20
|
+
NAME_RE = Regexp.new("\\A[a-z][#{PCEN}]*-[#{PCEN}]*\\z")
|
|
21
|
+
|
|
22
|
+
# Hyphenated names that the HTML spec reserves (SVG / MathML elements), so
|
|
23
|
+
# they are NOT valid custom element names even though they match NAME_RE.
|
|
24
|
+
RESERVED_NAMES = %w[
|
|
25
|
+
annotation-xml color-profile font-face font-face-src font-face-uri
|
|
26
|
+
font-face-format font-face-name missing-glyph
|
|
27
|
+
].to_set.freeze
|
|
14
28
|
|
|
15
29
|
def initialize(window)
|
|
16
30
|
@window = window
|
|
@@ -23,7 +37,10 @@ module Dommy
|
|
|
23
37
|
def define(name, klass, _options = nil)
|
|
24
38
|
key = name.to_s
|
|
25
39
|
unless key.match?(NAME_RE)
|
|
26
|
-
raise DOMException::SyntaxError, "name
|
|
40
|
+
raise DOMException::SyntaxError, "#{name.inspect} is not a valid custom element name"
|
|
41
|
+
end
|
|
42
|
+
if RESERVED_NAMES.include?(key)
|
|
43
|
+
raise DOMException::SyntaxError, "#{name.inspect} is a reserved element name"
|
|
27
44
|
end
|
|
28
45
|
|
|
29
46
|
raise DOMException::NotSupportedError, "#{key} already defined" if @definitions.key?(key)
|
|
@@ -64,16 +81,16 @@ module Dommy
|
|
|
64
81
|
# registered; fires `connectedCallback` for each upgraded node
|
|
65
82
|
# that's currently attached to a document tree.
|
|
66
83
|
def upgrade(root)
|
|
67
|
-
return nil unless root.respond_to?(:
|
|
84
|
+
return nil unless root.respond_to?(:__dommy_backend_node__)
|
|
68
85
|
|
|
69
|
-
walk_descendants(root.
|
|
86
|
+
walk_descendants(root.__dommy_backend_node__) do |nk|
|
|
70
87
|
next unless nk.element?
|
|
71
88
|
next unless @definitions.key?(nk.name)
|
|
72
89
|
|
|
73
90
|
# Force re-wrap by clearing the document's cached wrapper.
|
|
74
|
-
@window.document.
|
|
91
|
+
@window.document.__internal_reset_wrapper__(nk)
|
|
75
92
|
wrapped = @window.document.wrap_node(nk)
|
|
76
|
-
@window.document.
|
|
93
|
+
@window.document.__internal_notify_connected__(wrapped) if wrapped
|
|
77
94
|
end
|
|
78
95
|
|
|
79
96
|
nil
|
|
@@ -83,6 +100,8 @@ module Dommy
|
|
|
83
100
|
nil
|
|
84
101
|
end
|
|
85
102
|
|
|
103
|
+
include Bridge::Methods
|
|
104
|
+
js_methods %w[define get whenDefined upgrade]
|
|
86
105
|
def __js_call__(method, args)
|
|
87
106
|
case method
|
|
88
107
|
when "define"
|
|
@@ -108,10 +127,15 @@ module Dommy
|
|
|
108
127
|
# new class and fire connectedCallback.
|
|
109
128
|
def upgrade_existing(name)
|
|
110
129
|
doc = @window.document
|
|
111
|
-
|
|
112
|
-
|
|
130
|
+
# Match by tag name rather than interpolating `name` into a CSS selector:
|
|
131
|
+
# a spec-valid custom element name may contain "." (a CSS class selector
|
|
132
|
+
# char) or other metacharacters, which would corrupt the query.
|
|
133
|
+
doc.nokogiri_doc.css("*").each do |nk|
|
|
134
|
+
next unless nk.name == name
|
|
135
|
+
|
|
136
|
+
doc.__internal_reset_wrapper__(nk)
|
|
113
137
|
wrapped = doc.wrap_node(nk)
|
|
114
|
-
doc.
|
|
138
|
+
doc.__internal_notify_connected__(wrapped) if wrapped
|
|
115
139
|
end
|
|
116
140
|
end
|
|
117
141
|
|
data/lib/dommy/data_transfer.rb
CHANGED
|
@@ -64,11 +64,15 @@ module Dommy
|
|
|
64
64
|
@drop_effect = value.to_s
|
|
65
65
|
when "effectAllowed"
|
|
66
66
|
@effect_allowed = value.to_s
|
|
67
|
+
else
|
|
68
|
+
return Bridge::UNHANDLED
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
nil
|
|
70
72
|
end
|
|
71
73
|
|
|
74
|
+
include Bridge::Methods
|
|
75
|
+
js_methods %w[getData setData clearData]
|
|
72
76
|
def __js_call__(method, args)
|
|
73
77
|
case method
|
|
74
78
|
when "getData"
|