dommy 0.5.0 → 0.7.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 +31 -13
- data/lib/dommy/animation.rb +288 -0
- data/lib/dommy/attr.rb +23 -11
- data/lib/dommy/backend/nokogiri_adapter.rb +51 -0
- data/lib/dommy/backend/nokolexbor_adapter.rb +80 -0
- data/lib/dommy/backend.rb +129 -0
- data/lib/dommy/blob.rb +2 -2
- data/lib/dommy/compression_streams.rb +147 -0
- data/lib/dommy/cookie_store.rb +128 -0
- data/lib/dommy/crypto.rb +396 -0
- data/lib/dommy/css.rb +7 -7
- data/lib/dommy/custom_elements.rb +6 -6
- data/lib/dommy/document.rb +190 -32
- data/lib/dommy/dom_parser.rb +5 -4
- data/lib/dommy/element.rb +356 -53
- data/lib/dommy/event.rb +431 -25
- data/lib/dommy/event_source.rb +131 -0
- data/lib/dommy/fetch.rb +76 -6
- data/lib/dommy/file_reader.rb +176 -0
- data/lib/dommy/form_data.rb +1 -3
- data/lib/dommy/history.rb +82 -0
- data/lib/dommy/html_collection.rb +4 -4
- data/lib/dommy/html_elements.rb +130 -67
- data/lib/dommy/internal/cookie_jar.rb +2 -0
- data/lib/dommy/internal/css_pseudo_handlers.rb +28 -0
- data/lib/dommy/internal/dom_matching.rb +4 -4
- data/lib/dommy/internal/idna.rb +443 -0
- data/lib/dommy/internal/idna_data.rb +10379 -0
- data/lib/dommy/internal/ipv4_parser.rb +78 -0
- data/lib/dommy/internal/node_traversal.rb +1 -1
- data/lib/dommy/internal/node_wrapper_cache.rb +23 -12
- data/lib/dommy/internal/observable_callback.rb +25 -0
- data/lib/dommy/internal/punycode.rb +202 -0
- data/lib/dommy/internal/range_text_serializer.rb +72 -0
- data/lib/dommy/internal/reflected_attributes.rb +45 -0
- data/lib/dommy/internal/template_content_registry.rb +6 -6
- data/lib/dommy/intersection_observer.rb +82 -0
- data/lib/dommy/{router.rb → location.rb} +8 -142
- data/lib/dommy/media_query_list.rb +118 -0
- data/lib/dommy/message_channel.rb +249 -0
- data/lib/dommy/{observer.rb → mutation_observer.rb} +21 -11
- data/lib/dommy/navigator.rb +365 -5
- data/lib/dommy/node.rb +12 -0
- data/lib/dommy/notification.rb +89 -0
- data/lib/dommy/parser.rb +13 -13
- data/lib/dommy/performance.rb +146 -0
- data/lib/dommy/performance_observer.rb +55 -0
- data/lib/dommy/range.rb +597 -0
- data/lib/dommy/resize_observer.rb +53 -0
- data/lib/dommy/shadow_root.rb +10 -8
- data/lib/dommy/streams.rb +386 -0
- data/lib/dommy/svg_elements.rb +3863 -0
- data/lib/dommy/text_codec.rb +175 -0
- data/lib/dommy/tree_walker.rb +21 -21
- data/lib/dommy/url.rb +274 -29
- data/lib/dommy/url_pattern.rb +144 -0
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +209 -0
- data/lib/dommy/window.rb +369 -0
- data/lib/dommy/worker.rb +143 -0
- data/lib/dommy/xml_http_request.rb +438 -0
- data/lib/dommy.rb +43 -5
- metadata +44 -29
- data/lib/dommy/world.rb +0 -209
data/lib/dommy/crypto.rb
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "digest"
|
|
5
|
+
require "openssl"
|
|
6
|
+
|
|
7
|
+
module Dommy
|
|
8
|
+
# `Crypto` — mirror of `window.crypto`. Exposes `randomUUID()`,
|
|
9
|
+
# `getRandomValues(typedArray)`, and a minimal `subtle` surface
|
|
10
|
+
# (digest only, sufficient for most test fixtures).
|
|
11
|
+
#
|
|
12
|
+
# Spec: https://w3c.github.io/webcrypto/
|
|
13
|
+
class Crypto
|
|
14
|
+
def initialize(window = nil)
|
|
15
|
+
@window = window
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# JS: crypto.randomUUID() → version-4 UUID string.
|
|
19
|
+
def random_uuid
|
|
20
|
+
SecureRandom.uuid
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
alias randomUUID random_uuid
|
|
24
|
+
|
|
25
|
+
# JS: crypto.getRandomValues(typedArray) — fills the supplied
|
|
26
|
+
# buffer in place and returns it. JS TypedArrays carry a
|
|
27
|
+
# `byteLength` property; we honor that to fill multi-byte
|
|
28
|
+
# element arrays (Uint16Array, etc.) correctly. Plain Ruby
|
|
29
|
+
# arrays fall back to `size` (1 byte per slot).
|
|
30
|
+
def get_random_values(typed_array)
|
|
31
|
+
return typed_array unless typed_array.respond_to?(:size) && typed_array.respond_to?(:[]=)
|
|
32
|
+
|
|
33
|
+
byte_length = if typed_array.respond_to?(:byteLength)
|
|
34
|
+
typed_array.byteLength
|
|
35
|
+
elsif typed_array.respond_to?(:byte_length)
|
|
36
|
+
typed_array.byte_length
|
|
37
|
+
else
|
|
38
|
+
typed_array.size
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
bytes_per_element = [byte_length / typed_array.size, 1].max
|
|
42
|
+
bytes = SecureRandom.bytes(byte_length).bytes
|
|
43
|
+
typed_array.size.times do |i|
|
|
44
|
+
offset = i * bytes_per_element
|
|
45
|
+
value = bytes[offset, bytes_per_element].reduce(0) { |acc, b| (acc << 8) | b }
|
|
46
|
+
typed_array[i] = value
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
typed_array
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
alias getRandomValues get_random_values
|
|
53
|
+
|
|
54
|
+
def subtle
|
|
55
|
+
@subtle ||= SubtleCrypto.new(@window)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def __js_get__(key)
|
|
59
|
+
case key
|
|
60
|
+
when "subtle"
|
|
61
|
+
subtle
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def __js_call__(method, args)
|
|
66
|
+
case method
|
|
67
|
+
when "randomUUID"
|
|
68
|
+
random_uuid
|
|
69
|
+
when "getRandomValues"
|
|
70
|
+
get_random_values(args[0])
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# `SubtleCrypto` — `window.crypto.subtle`. Currently covers `digest`
|
|
76
|
+
# (SHA-1 / SHA-256 / SHA-384 / SHA-512), which is by far the most
|
|
77
|
+
# commonly used operation in test contexts. Encrypt / decrypt /
|
|
78
|
+
# sign / verify / key generation are out of scope; tests that
|
|
79
|
+
# need them should mock `crypto.subtle` directly.
|
|
80
|
+
#
|
|
81
|
+
# Returned values are byte arrays — real `SubtleCrypto.digest`
|
|
82
|
+
# resolves to an `ArrayBuffer`; we expose the equivalent Ruby
|
|
83
|
+
# byte array so callers can convert as needed.
|
|
84
|
+
class SubtleCrypto
|
|
85
|
+
ALGORITHMS = {
|
|
86
|
+
"SHA-1" => -> (data) { Digest::SHA1.digest(data) },
|
|
87
|
+
"SHA-256" => -> (data) { Digest::SHA256.digest(data) },
|
|
88
|
+
"SHA-384" => -> (data) { Digest::SHA384.digest(data) },
|
|
89
|
+
"SHA-512" => -> (data) { Digest::SHA512.digest(data) }
|
|
90
|
+
}.freeze
|
|
91
|
+
|
|
92
|
+
def initialize(window = nil)
|
|
93
|
+
@window = window
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def digest(algorithm, data)
|
|
97
|
+
promise do
|
|
98
|
+
name = algorithm_name(algorithm)
|
|
99
|
+
hasher = ALGORITHMS[name]
|
|
100
|
+
raise ArgumentError, "unsupported algorithm: #{name}" unless hasher
|
|
101
|
+
|
|
102
|
+
hasher.call(coerce_bytes(data)).bytes
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Generate a fresh symmetric key. `algorithm` is `{name: "HMAC",
|
|
107
|
+
# hash: "SHA-256"}` or `{name: "AES-GCM", length: 128|256}`.
|
|
108
|
+
def generate_key(algorithm, extractable = true, usages = nil)
|
|
109
|
+
promise do
|
|
110
|
+
case primary_algorithm_name(algorithm)
|
|
111
|
+
when "HMAC"
|
|
112
|
+
hash = hmac_hash_from(algorithm)
|
|
113
|
+
CryptoKey.new(
|
|
114
|
+
:secret,
|
|
115
|
+
"HMAC",
|
|
116
|
+
hash,
|
|
117
|
+
SecureRandom.bytes(openssl_digest_size(hash)),
|
|
118
|
+
extractable: extractable,
|
|
119
|
+
usages: usages || %w[sign verify]
|
|
120
|
+
)
|
|
121
|
+
when "AES-GCM", "AES-CBC", "AES-CTR"
|
|
122
|
+
length = (algorithm.is_a?(Hash) && (algorithm["length"] || algorithm[:length])) || 256
|
|
123
|
+
raise ArgumentError, "AES key length must be 128/192/256" unless [128, 192, 256].include?(length)
|
|
124
|
+
|
|
125
|
+
CryptoKey.new(
|
|
126
|
+
:secret,
|
|
127
|
+
primary_algorithm_name(algorithm),
|
|
128
|
+
nil,
|
|
129
|
+
SecureRandom.bytes(length / 8),
|
|
130
|
+
extractable: extractable,
|
|
131
|
+
usages: usages || %w[encrypt decrypt]
|
|
132
|
+
)
|
|
133
|
+
else
|
|
134
|
+
raise ArgumentError, "unsupported algorithm: #{primary_algorithm_name(algorithm)}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
alias generateKey generate_key
|
|
140
|
+
|
|
141
|
+
# Import a raw key. Supports HMAC and AES-GCM/CBC/CTR.
|
|
142
|
+
def import_key(format, key_data, algorithm, extractable = true, usages = nil)
|
|
143
|
+
promise do
|
|
144
|
+
raise ArgumentError, "only raw format supported" unless format.to_s == "raw"
|
|
145
|
+
|
|
146
|
+
bytes = coerce_bytes(key_data)
|
|
147
|
+
case primary_algorithm_name(algorithm)
|
|
148
|
+
when "HMAC"
|
|
149
|
+
hash = hmac_hash_from(algorithm)
|
|
150
|
+
CryptoKey.new(
|
|
151
|
+
:secret,
|
|
152
|
+
"HMAC",
|
|
153
|
+
hash,
|
|
154
|
+
bytes,
|
|
155
|
+
extractable: extractable,
|
|
156
|
+
usages: usages || %w[sign verify]
|
|
157
|
+
)
|
|
158
|
+
when "AES-GCM", "AES-CBC", "AES-CTR"
|
|
159
|
+
unless [16, 24, 32].include?(bytes.bytesize)
|
|
160
|
+
raise ArgumentError, "AES key must be 16/24/32 bytes"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
CryptoKey.new(
|
|
164
|
+
:secret,
|
|
165
|
+
primary_algorithm_name(algorithm),
|
|
166
|
+
nil,
|
|
167
|
+
bytes,
|
|
168
|
+
extractable: extractable,
|
|
169
|
+
usages: usages || %w[encrypt decrypt]
|
|
170
|
+
)
|
|
171
|
+
else
|
|
172
|
+
raise ArgumentError, "unsupported algorithm: #{primary_algorithm_name(algorithm)}"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
alias importKey import_key
|
|
178
|
+
|
|
179
|
+
# HMAC sign — returns the MAC as a byte array.
|
|
180
|
+
def sign(_algorithm, key, data)
|
|
181
|
+
promise do
|
|
182
|
+
raise ArgumentError, "HMAC key required" unless key.is_a?(CryptoKey) && key.algorithm_name == "HMAC"
|
|
183
|
+
raise ArgumentError, "key.usages must include 'sign'" unless key.usages.include?("sign")
|
|
184
|
+
|
|
185
|
+
OpenSSL::HMAC.digest(openssl_digest_name(key.hash_name), key.__dommy_bytes__, coerce_bytes(data)).bytes
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# HMAC verify — constant-time compare of the MAC.
|
|
190
|
+
def verify(_algorithm, key, signature, data)
|
|
191
|
+
promise do
|
|
192
|
+
raise ArgumentError, "HMAC key required" unless key.is_a?(CryptoKey) && key.algorithm_name == "HMAC"
|
|
193
|
+
raise ArgumentError, "key.usages must include 'verify'" unless key.usages.include?("verify")
|
|
194
|
+
|
|
195
|
+
expected = OpenSSL::HMAC.digest(openssl_digest_name(key.hash_name), key.__dommy_bytes__, coerce_bytes(data))
|
|
196
|
+
sig_bytes = coerce_bytes(signature)
|
|
197
|
+
if expected.bytesize == sig_bytes.bytesize
|
|
198
|
+
OpenSSL.fixed_length_secure_compare(expected, sig_bytes)
|
|
199
|
+
else
|
|
200
|
+
false
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# AES-GCM encrypt. `algorithm` must be `{name: "AES-GCM", iv:
|
|
206
|
+
# <bytes>, additionalData?: <bytes>, tagLength?: 128}`.
|
|
207
|
+
# Output is `ciphertext || authTag`, matching WebCrypto.
|
|
208
|
+
def encrypt(algorithm, key, data)
|
|
209
|
+
promise do
|
|
210
|
+
cipher = build_gcm_cipher(:encrypt, algorithm, key)
|
|
211
|
+
ct = cipher.update(coerce_bytes(data)) + cipher.final
|
|
212
|
+
# OpenSSL always produces a 16-byte tag for GCM; truncate to
|
|
213
|
+
# the requested `tagLength` to honour the spec.
|
|
214
|
+
tag = cipher.auth_tag.byteslice(0, aes_gcm_tag_length(algorithm))
|
|
215
|
+
(ct + tag).bytes
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def decrypt(algorithm, key, data)
|
|
220
|
+
promise do
|
|
221
|
+
bytes = coerce_bytes(data)
|
|
222
|
+
tag_len = aes_gcm_tag_length(algorithm)
|
|
223
|
+
raise ArgumentError, "ciphertext shorter than auth tag" if bytes.bytesize < tag_len
|
|
224
|
+
|
|
225
|
+
ct = bytes.byteslice(0, bytes.bytesize - tag_len)
|
|
226
|
+
tag = bytes.byteslice(bytes.bytesize - tag_len, tag_len)
|
|
227
|
+
cipher = build_gcm_cipher(:decrypt, algorithm, key)
|
|
228
|
+
cipher.auth_tag = tag
|
|
229
|
+
(cipher.update(ct) + cipher.final).bytes
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def __js_call__(method, args)
|
|
234
|
+
case method
|
|
235
|
+
when "digest"
|
|
236
|
+
digest(args[0], args[1])
|
|
237
|
+
when "generateKey"
|
|
238
|
+
generate_key(args[0], args[1], args[2])
|
|
239
|
+
when "importKey"
|
|
240
|
+
import_key(args[0], args[1], args[2], args[3], args[4])
|
|
241
|
+
when "sign"
|
|
242
|
+
sign(args[0], args[1], args[2])
|
|
243
|
+
when "verify"
|
|
244
|
+
verify(args[0], args[1], args[2], args[3])
|
|
245
|
+
when "encrypt"
|
|
246
|
+
encrypt(args[0], args[1], args[2])
|
|
247
|
+
when "decrypt"
|
|
248
|
+
decrypt(args[0], args[1], args[2])
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
private
|
|
253
|
+
|
|
254
|
+
# Run `block` synchronously and wrap the result (or raised error)
|
|
255
|
+
# in a `PromiseValue`. Required by the WebCrypto spec — every
|
|
256
|
+
# method is `Promise`-returning even when the underlying work is
|
|
257
|
+
# synchronous.
|
|
258
|
+
def promise(&block)
|
|
259
|
+
result = block.call
|
|
260
|
+
PromiseValue.resolve(@window, result)
|
|
261
|
+
rescue StandardError => e
|
|
262
|
+
PromiseValue.reject(@window, e)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def algorithm_name(algorithm)
|
|
266
|
+
raw = algorithm.is_a?(Hash) ? (algorithm["name"] || algorithm[:name]) : algorithm
|
|
267
|
+
s = raw.to_s.upcase
|
|
268
|
+
# Normalize `SHA256` → `SHA-256`; preserve already-hyphenated `SHA-256`.
|
|
269
|
+
return s if s.include?("-")
|
|
270
|
+
return s.sub("SHA", "SHA-") if s.start_with?("SHA")
|
|
271
|
+
|
|
272
|
+
s
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def coerce_bytes(data)
|
|
276
|
+
case data
|
|
277
|
+
when String
|
|
278
|
+
data
|
|
279
|
+
when Array
|
|
280
|
+
data.pack("C*")
|
|
281
|
+
else
|
|
282
|
+
data.respond_to?(:to_a) ? data.to_a.pack("C*") : data.to_s
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Resolve `algorithm["hash"]` to a canonical hash name (`"SHA-256"`
|
|
287
|
+
# etc.). Accepts the spec shapes:
|
|
288
|
+
# "SHA-256" (bare string)
|
|
289
|
+
# {hash: "SHA-256"}
|
|
290
|
+
# {hash: {name: "SHA-256"}}
|
|
291
|
+
# {name: "HMAC", hash: "SHA-256"}
|
|
292
|
+
# Raises `ArgumentError` if no hash can be resolved — unlike some
|
|
293
|
+
# browser UAs, dommy refuses to silently default to SHA-256.
|
|
294
|
+
def hmac_hash_from(algorithm)
|
|
295
|
+
hash_field = hash_descriptor(algorithm)
|
|
296
|
+
name = algorithm_name(hash_field)
|
|
297
|
+
|
|
298
|
+
if name.nil? || name.empty? || name == "HMAC"
|
|
299
|
+
raise ArgumentError, "HMAC requires an explicit hash (e.g. {hash: 'SHA-256'})"
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
raise ArgumentError, "unsupported HMAC hash: #{name}" unless ALGORITHMS.key?(name)
|
|
303
|
+
|
|
304
|
+
name
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def hash_descriptor(algorithm)
|
|
308
|
+
return algorithm unless algorithm.is_a?(Hash)
|
|
309
|
+
|
|
310
|
+
algorithm["hash"] || algorithm[:hash] || algorithm
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# The algorithm's primary name (`"HMAC"` / `"AES-GCM"` / ...) —
|
|
314
|
+
# ignoring any nested `hash` descriptor.
|
|
315
|
+
def primary_algorithm_name(algorithm)
|
|
316
|
+
raw = algorithm.is_a?(Hash) ? (algorithm["name"] || algorithm[:name]) : algorithm
|
|
317
|
+
algorithm_name(raw)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def openssl_digest_name(hash_name)
|
|
321
|
+
hash_name.sub("SHA-", "SHA")
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def aes_gcm_tag_length(algorithm)
|
|
325
|
+
bits = (algorithm.is_a?(Hash) && (algorithm["tagLength"] || algorithm[:tagLength])) || 128
|
|
326
|
+
bits / 8
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def build_gcm_cipher(direction, algorithm, key)
|
|
330
|
+
raw_key = key.is_a?(CryptoKey) ? key.__dommy_bytes__ : coerce_bytes(key)
|
|
331
|
+
raise ArgumentError, "AES-GCM key must be 16/24/32 bytes" unless [16, 24, 32].include?(raw_key.bytesize)
|
|
332
|
+
|
|
333
|
+
iv = algorithm.is_a?(Hash) ? (algorithm["iv"] || algorithm[:iv]) : nil
|
|
334
|
+
raise ArgumentError, "AES-GCM requires an iv" if iv.nil?
|
|
335
|
+
|
|
336
|
+
cipher = OpenSSL::Cipher.new("aes-#{raw_key.bytesize * 8}-gcm")
|
|
337
|
+
direction == :encrypt ? cipher.encrypt : cipher.decrypt
|
|
338
|
+
cipher.key = raw_key
|
|
339
|
+
cipher.iv = coerce_bytes(iv)
|
|
340
|
+
aad = algorithm.is_a?(Hash) ? (algorithm["additionalData"] || algorithm[:additionalData]) : nil
|
|
341
|
+
cipher.auth_data = coerce_bytes(aad) if aad
|
|
342
|
+
cipher
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def openssl_digest_size(hash_name)
|
|
346
|
+
case hash_name
|
|
347
|
+
when "SHA-1"
|
|
348
|
+
20
|
|
349
|
+
when "SHA-256"
|
|
350
|
+
32
|
|
351
|
+
when "SHA-384"
|
|
352
|
+
48
|
|
353
|
+
when "SHA-512"
|
|
354
|
+
64
|
|
355
|
+
else
|
|
356
|
+
32
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# `CryptoKey` — opaque key handle returned by SubtleCrypto.
|
|
362
|
+
# `extractable: false` keys reject export attempts; the raw bytes are
|
|
363
|
+
# reachable only through the `__dommy_bytes__` ecosystem accessor, never
|
|
364
|
+
# the public (Web-mirroring) API.
|
|
365
|
+
class CryptoKey
|
|
366
|
+
attr_reader :type, :algorithm_name, :hash_name, :usages, :extractable
|
|
367
|
+
|
|
368
|
+
def initialize(type, algorithm_name, hash_name, bytes, extractable: true, usages: [])
|
|
369
|
+
@type = type
|
|
370
|
+
@algorithm_name = algorithm_name
|
|
371
|
+
@hash_name = hash_name
|
|
372
|
+
@bytes = bytes
|
|
373
|
+
@extractable = extractable
|
|
374
|
+
@usages = usages.map(&:to_s).freeze
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Low-level ecosystem accessor (see __dommy_ convention) — the public
|
|
378
|
+
# Web API never exposes raw key bytes.
|
|
379
|
+
def __dommy_bytes__
|
|
380
|
+
@bytes
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def __js_get__(key)
|
|
384
|
+
case key
|
|
385
|
+
when "type"
|
|
386
|
+
@type.to_s
|
|
387
|
+
when "extractable"
|
|
388
|
+
@extractable
|
|
389
|
+
when "algorithm"
|
|
390
|
+
{"name" => @algorithm_name, "hash" => {"name" => @hash_name}}
|
|
391
|
+
when "usages"
|
|
392
|
+
@usages
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
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
|
|
|
@@ -173,15 +173,15 @@ module Dommy
|
|
|
173
173
|
@rules.dup
|
|
174
174
|
end
|
|
175
175
|
|
|
176
|
-
def
|
|
176
|
+
def __internal_insert__(index, rule)
|
|
177
177
|
@rules.insert(index, rule)
|
|
178
178
|
end
|
|
179
179
|
|
|
180
|
-
def
|
|
180
|
+
def __internal_delete_at__(index)
|
|
181
181
|
@rules.delete_at(index)
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
-
def
|
|
184
|
+
def __internal_clear__
|
|
185
185
|
@rules.clear
|
|
186
186
|
end
|
|
187
187
|
|
|
@@ -64,16 +64,16 @@ module Dommy
|
|
|
64
64
|
# registered; fires `connectedCallback` for each upgraded node
|
|
65
65
|
# that's currently attached to a document tree.
|
|
66
66
|
def upgrade(root)
|
|
67
|
-
return nil unless root.respond_to?(:
|
|
67
|
+
return nil unless root.respond_to?(:__dommy_backend_node__)
|
|
68
68
|
|
|
69
|
-
walk_descendants(root.
|
|
69
|
+
walk_descendants(root.__dommy_backend_node__) do |nk|
|
|
70
70
|
next unless nk.element?
|
|
71
71
|
next unless @definitions.key?(nk.name)
|
|
72
72
|
|
|
73
73
|
# Force re-wrap by clearing the document's cached wrapper.
|
|
74
|
-
@window.document.
|
|
74
|
+
@window.document.__internal_reset_wrapper__(nk)
|
|
75
75
|
wrapped = @window.document.wrap_node(nk)
|
|
76
|
-
@window.document.
|
|
76
|
+
@window.document.__internal_notify_connected__(wrapped) if wrapped
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
nil
|
|
@@ -109,9 +109,9 @@ module Dommy
|
|
|
109
109
|
def upgrade_existing(name)
|
|
110
110
|
doc = @window.document
|
|
111
111
|
doc.nokogiri_doc.css(name).each do |nk|
|
|
112
|
-
doc.
|
|
112
|
+
doc.__internal_reset_wrapper__(nk)
|
|
113
113
|
wrapped = doc.wrap_node(nk)
|
|
114
|
-
doc.
|
|
114
|
+
doc.__internal_notify_connected__(wrapped) if wrapped
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
|