dommy 0.6.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 +30 -38
- data/lib/dommy/animation.rb +1 -1
- 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 +4 -4
- data/lib/dommy/cookie_store.rb +1 -1
- data/lib/dommy/crypto.rb +9 -8
- data/lib/dommy/css.rb +7 -7
- data/lib/dommy/custom_elements.rb +6 -6
- data/lib/dommy/document.rb +98 -32
- data/lib/dommy/dom_parser.rb +5 -4
- data/lib/dommy/element.rb +231 -50
- data/lib/dommy/event.rb +61 -25
- data/lib/dommy/event_source.rb +8 -8
- data/lib/dommy/fetch.rb +14 -6
- data/lib/dommy/file_reader.rb +3 -3
- data/lib/dommy/form_data.rb +1 -3
- data/lib/dommy/history.rb +7 -4
- data/lib/dommy/html_collection.rb +4 -4
- data/lib/dommy/html_elements.rb +110 -42
- data/lib/dommy/internal/css_pseudo_handlers.rb +28 -0
- data/lib/dommy/internal/dom_matching.rb +3 -3
- data/lib/dommy/internal/node_traversal.rb +1 -1
- data/lib/dommy/internal/node_wrapper_cache.rb +23 -12
- data/lib/dommy/internal/template_content_registry.rb +6 -6
- data/lib/dommy/intersection_observer.rb +2 -2
- data/lib/dommy/location.rb +8 -4
- data/lib/dommy/media_query_list.rb +3 -3
- data/lib/dommy/message_channel.rb +9 -9
- data/lib/dommy/mutation_observer.rb +21 -11
- data/lib/dommy/navigator.rb +12 -12
- data/lib/dommy/node.rb +12 -0
- data/lib/dommy/notification.rb +3 -3
- data/lib/dommy/parser.rb +13 -13
- data/lib/dommy/performance_observer.rb +2 -2
- data/lib/dommy/range.rb +2 -2
- data/lib/dommy/resize_observer.rb +2 -2
- data/lib/dommy/shadow_root.rb +10 -8
- data/lib/dommy/streams.rb +22 -22
- data/lib/dommy/text_codec.rb +4 -4
- data/lib/dommy/tree_walker.rb +21 -21
- data/lib/dommy/url.rb +25 -8
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/web_socket.rb +13 -13
- data/lib/dommy/window.rb +14 -1
- data/lib/dommy/worker.rb +5 -5
- data/lib/dommy/xml_http_request.rb +19 -4
- data/lib/dommy.rb +12 -2
- metadata +12 -26
data/lib/dommy/tree_walker.rb
CHANGED
|
@@ -45,9 +45,11 @@ module Dommy
|
|
|
45
45
|
# tree rooted at `root` and filter by `whatToShow` + an optional
|
|
46
46
|
# filter callable (or object with `acceptNode`).
|
|
47
47
|
module TreeTraversalCore
|
|
48
|
+
private
|
|
49
|
+
|
|
48
50
|
# Returns FILTER_ACCEPT / FILTER_REJECT / FILTER_SKIP for the
|
|
49
51
|
# given wrapped node.
|
|
50
|
-
def
|
|
52
|
+
def accept(node)
|
|
51
53
|
return NodeFilter::FILTER_REJECT unless node
|
|
52
54
|
return NodeFilter::FILTER_SKIP if (NodeFilter.bitmask_for(node) & @what_to_show) == 0
|
|
53
55
|
|
|
@@ -55,8 +57,6 @@ module Dommy
|
|
|
55
57
|
result || NodeFilter::FILTER_ACCEPT
|
|
56
58
|
end
|
|
57
59
|
|
|
58
|
-
private
|
|
59
|
-
|
|
60
60
|
def invoke_filter(node)
|
|
61
61
|
return NodeFilter::FILTER_ACCEPT if @filter.nil?
|
|
62
62
|
|
|
@@ -92,7 +92,7 @@ module Dommy
|
|
|
92
92
|
def next_node
|
|
93
93
|
node = first_descendant_or_following(@current_node)
|
|
94
94
|
while node
|
|
95
|
-
verdict =
|
|
95
|
+
verdict = accept(node)
|
|
96
96
|
if verdict == NodeFilter::FILTER_ACCEPT
|
|
97
97
|
@current_node = node
|
|
98
98
|
return node
|
|
@@ -109,7 +109,7 @@ module Dommy
|
|
|
109
109
|
def previous_node
|
|
110
110
|
node = preceding(@current_node)
|
|
111
111
|
while node && node != @root
|
|
112
|
-
verdict =
|
|
112
|
+
verdict = accept(node)
|
|
113
113
|
if verdict == NodeFilter::FILTER_ACCEPT
|
|
114
114
|
@current_node = node
|
|
115
115
|
return node
|
|
@@ -124,7 +124,7 @@ module Dommy
|
|
|
124
124
|
def parent_node
|
|
125
125
|
node = wrapped_parent(@current_node)
|
|
126
126
|
while node && reachable_from_root?(node)
|
|
127
|
-
return @current_node = node if
|
|
127
|
+
return @current_node = node if accept(node) == NodeFilter::FILTER_ACCEPT
|
|
128
128
|
|
|
129
129
|
node = wrapped_parent(node)
|
|
130
130
|
end
|
|
@@ -192,7 +192,7 @@ module Dommy
|
|
|
192
192
|
def walk_siblings(start, direction)
|
|
193
193
|
node = start
|
|
194
194
|
while node
|
|
195
|
-
v =
|
|
195
|
+
v = accept(node)
|
|
196
196
|
return @current_node = node if v == NodeFilter::FILTER_ACCEPT
|
|
197
197
|
|
|
198
198
|
node = (v == NodeFilter::FILTER_REJECT) ? nil : send(direction, node)
|
|
@@ -246,30 +246,30 @@ module Dommy
|
|
|
246
246
|
end
|
|
247
247
|
|
|
248
248
|
def wrapped_parent(node)
|
|
249
|
-
parent_nk = node.respond_to?(:
|
|
250
|
-
return nil unless parent_nk && !parent_nk.is_a?(
|
|
249
|
+
parent_nk = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__.parent : nil
|
|
250
|
+
return nil unless parent_nk && !parent_nk.is_a?(Backend.document_class)
|
|
251
251
|
|
|
252
252
|
doc = node.instance_variable_get(:@document) || (@root.respond_to?(:document) ? @root.document : @root)
|
|
253
253
|
doc.wrap_node(parent_nk)
|
|
254
254
|
end
|
|
255
255
|
|
|
256
256
|
def first_wrapped_child(node)
|
|
257
|
-
child_nk = node.respond_to?(:
|
|
257
|
+
child_nk = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__.children.first : nil
|
|
258
258
|
child_nk && document_for(node).wrap_node(child_nk)
|
|
259
259
|
end
|
|
260
260
|
|
|
261
261
|
def last_wrapped_child(node)
|
|
262
|
-
child_nk = node.respond_to?(:
|
|
262
|
+
child_nk = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__.children.last : nil
|
|
263
263
|
child_nk && document_for(node).wrap_node(child_nk)
|
|
264
264
|
end
|
|
265
265
|
|
|
266
266
|
def next_sibling_wrapped(node)
|
|
267
|
-
n = node.respond_to?(:
|
|
267
|
+
n = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__.next : nil
|
|
268
268
|
n && document_for(node).wrap_node(n)
|
|
269
269
|
end
|
|
270
270
|
|
|
271
271
|
def previous_sibling_wrapped(node)
|
|
272
|
-
n = node.respond_to?(:
|
|
272
|
+
n = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__.previous : nil
|
|
273
273
|
n && document_for(node).wrap_node(n)
|
|
274
274
|
end
|
|
275
275
|
|
|
@@ -306,7 +306,7 @@ module Dommy
|
|
|
306
306
|
|
|
307
307
|
@reference_node = node
|
|
308
308
|
@pointer_before_reference = false
|
|
309
|
-
return node if
|
|
309
|
+
return node if accept(node) == NodeFilter::FILTER_ACCEPT
|
|
310
310
|
end
|
|
311
311
|
end
|
|
312
312
|
|
|
@@ -322,7 +322,7 @@ module Dommy
|
|
|
322
322
|
|
|
323
323
|
@reference_node = node
|
|
324
324
|
@pointer_before_reference = true
|
|
325
|
-
return node if
|
|
325
|
+
return node if accept(node) == NodeFilter::FILTER_ACCEPT
|
|
326
326
|
end
|
|
327
327
|
end
|
|
328
328
|
|
|
@@ -392,28 +392,28 @@ module Dommy
|
|
|
392
392
|
end
|
|
393
393
|
|
|
394
394
|
def first_child_node(node)
|
|
395
|
-
n = node.respond_to?(:
|
|
395
|
+
n = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__.children.first : nil
|
|
396
396
|
n && document_for(node).wrap_node(n)
|
|
397
397
|
end
|
|
398
398
|
|
|
399
399
|
def last_child_node(node)
|
|
400
|
-
n = node.respond_to?(:
|
|
400
|
+
n = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__.children.last : nil
|
|
401
401
|
n && document_for(node).wrap_node(n)
|
|
402
402
|
end
|
|
403
403
|
|
|
404
404
|
def next_sibling_node(node)
|
|
405
|
-
n = node.respond_to?(:
|
|
405
|
+
n = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__.next : nil
|
|
406
406
|
n && document_for(node).wrap_node(n)
|
|
407
407
|
end
|
|
408
408
|
|
|
409
409
|
def previous_sibling_node(node)
|
|
410
|
-
n = node.respond_to?(:
|
|
410
|
+
n = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__.previous : nil
|
|
411
411
|
n && document_for(node).wrap_node(n)
|
|
412
412
|
end
|
|
413
413
|
|
|
414
414
|
def parent_node_of(node)
|
|
415
|
-
parent_nk = node.respond_to?(:
|
|
416
|
-
return nil unless parent_nk && !parent_nk.is_a?(
|
|
415
|
+
parent_nk = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__.parent : nil
|
|
416
|
+
return nil unless parent_nk && !parent_nk.is_a?(Backend.document_class)
|
|
417
417
|
|
|
418
418
|
document_for(node).wrap_node(parent_nk)
|
|
419
419
|
end
|
data/lib/dommy/url.rb
CHANGED
|
@@ -30,7 +30,7 @@ module Dommy
|
|
|
30
30
|
|
|
31
31
|
class << self
|
|
32
32
|
# Create a unique blob: URL that resolves back to `blob` via
|
|
33
|
-
# `URL.
|
|
33
|
+
# `URL.__test_resolve_blob_url__(url)`. Returns nil for non-Blob input.
|
|
34
34
|
def create_object_url(blob)
|
|
35
35
|
return nil unless blob.is_a?(Blob)
|
|
36
36
|
|
|
@@ -53,14 +53,31 @@ module Dommy
|
|
|
53
53
|
|
|
54
54
|
# Resolve a blob: URL back to its Blob, or nil if revoked / unknown.
|
|
55
55
|
# Internal — used by fetch / XHR implementations that load blob URLs.
|
|
56
|
-
def
|
|
56
|
+
def __test_resolve_blob_url__(url)
|
|
57
57
|
@blob_urls[url.to_s]
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# Test seam: drop all registered blob URLs.
|
|
61
|
-
def
|
|
61
|
+
def __test_reset_blob_urls__
|
|
62
62
|
@blob_urls.clear
|
|
63
63
|
end
|
|
64
|
+
|
|
65
|
+
# WHATWG URL Standard — `URL.parse(input, base)` is the
|
|
66
|
+
# non-throwing static factory. Returns a URL on success, `nil`
|
|
67
|
+
# on parse failure. The constructor (`new URL(...)`) raises
|
|
68
|
+
# `SyntaxError` for the same failure case.
|
|
69
|
+
def parse(input, base = nil)
|
|
70
|
+
new(input, base)
|
|
71
|
+
rescue DOMException::SyntaxError
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# WHATWG URL Standard — `URL.canParse(input, base)`. Boolean
|
|
76
|
+
# counterpart to `parse`: lets callers peek at validity
|
|
77
|
+
# without rescuing an exception or holding a URL reference.
|
|
78
|
+
def can_parse(input, base = nil)
|
|
79
|
+
!parse(input, base).nil?
|
|
80
|
+
end
|
|
64
81
|
end
|
|
65
82
|
|
|
66
83
|
attr_reader :search_params
|
|
@@ -78,7 +95,7 @@ module Dommy
|
|
|
78
95
|
def href=(value)
|
|
79
96
|
raw = parse_with_base(value.to_s, nil)
|
|
80
97
|
@uri = raw
|
|
81
|
-
@search_params.
|
|
98
|
+
@search_params.__internal_replace__(raw.query.to_s)
|
|
82
99
|
build_href
|
|
83
100
|
end
|
|
84
101
|
|
|
@@ -159,7 +176,7 @@ module Dommy
|
|
|
159
176
|
def search=(value)
|
|
160
177
|
q = value.to_s.sub(/^\?/, "")
|
|
161
178
|
@uri.query = q.empty? ? nil : q
|
|
162
|
-
@search_params.
|
|
179
|
+
@search_params.__internal_replace__(q)
|
|
163
180
|
end
|
|
164
181
|
|
|
165
182
|
def hash
|
|
@@ -289,7 +306,7 @@ module Dommy
|
|
|
289
306
|
# Called by URLSearchParams when it mutates; we need to keep the
|
|
290
307
|
# underlying URI's query string in sync so subsequent `href` is
|
|
291
308
|
# accurate.
|
|
292
|
-
def
|
|
309
|
+
def __internal_notify_params_changed__
|
|
293
310
|
sync_uri_query
|
|
294
311
|
end
|
|
295
312
|
|
|
@@ -636,7 +653,7 @@ module Dommy
|
|
|
636
653
|
@pairs.map { |k, v| "#{encode(k)}=#{encode(v)}" }.join("&")
|
|
637
654
|
end
|
|
638
655
|
|
|
639
|
-
def
|
|
656
|
+
def __internal_replace__(query_string)
|
|
640
657
|
@pairs = parse(query_string)
|
|
641
658
|
nil
|
|
642
659
|
end
|
|
@@ -701,7 +718,7 @@ module Dommy
|
|
|
701
718
|
end
|
|
702
719
|
|
|
703
720
|
def notify
|
|
704
|
-
@owner&.
|
|
721
|
+
@owner&.__internal_notify_params_changed__
|
|
705
722
|
end
|
|
706
723
|
end
|
|
707
724
|
end
|
data/lib/dommy/version.rb
CHANGED
data/lib/dommy/web_socket.rb
CHANGED
|
@@ -5,11 +5,11 @@ module Dommy
|
|
|
5
5
|
# connection; dommy exposes an in-memory transport tests drive via
|
|
6
6
|
# the `__*` seams:
|
|
7
7
|
#
|
|
8
|
-
# ws.
|
|
9
|
-
# ws.
|
|
10
|
-
# ws.
|
|
11
|
-
# ws.
|
|
12
|
-
# ws.
|
|
8
|
+
# ws.__test_simulate_open__ — fires `open`
|
|
9
|
+
# ws.__test_simulate_message__(data) — fires `message`
|
|
10
|
+
# ws.__test_simulate_close__(code, reason) — fires `close`
|
|
11
|
+
# ws.__test_simulate_error__ — fires `error`
|
|
12
|
+
# ws.__test_sent_messages__ — array of sent payloads
|
|
13
13
|
#
|
|
14
14
|
# By default a `new WebSocket(url)` auto-opens via microtask so the
|
|
15
15
|
# common pattern (`ws.onopen = ...; ws.send(...)`) works without
|
|
@@ -42,7 +42,7 @@ module Dommy
|
|
|
42
42
|
|
|
43
43
|
# Auto-open via microtask unless tests disable.
|
|
44
44
|
auto_open = window.globals["__ws_auto_open__"]
|
|
45
|
-
@window.scheduler.queue_microtask(proc {
|
|
45
|
+
@window.scheduler.queue_microtask(proc { __test_simulate_open__ }) unless auto_open == false
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def send(data)
|
|
@@ -56,30 +56,30 @@ module Dommy
|
|
|
56
56
|
return if @ready_state == CLOSED || @ready_state == CLOSING
|
|
57
57
|
|
|
58
58
|
@ready_state = CLOSING
|
|
59
|
-
@window.scheduler.queue_microtask(proc {
|
|
59
|
+
@window.scheduler.queue_microtask(proc { __test_simulate_close__(code, reason) })
|
|
60
60
|
nil
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# --- Test seams ------------------------------------------------
|
|
64
64
|
|
|
65
|
-
def
|
|
65
|
+
def __test_sent_messages__
|
|
66
66
|
@sent_messages.dup
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
def
|
|
69
|
+
def __test_simulate_open__
|
|
70
70
|
return if @ready_state != CONNECTING
|
|
71
71
|
|
|
72
72
|
@ready_state = OPEN
|
|
73
73
|
dispatch_event(Event.new("open"))
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
def
|
|
76
|
+
def __test_simulate_message__(data)
|
|
77
77
|
return if @ready_state != OPEN
|
|
78
78
|
|
|
79
79
|
dispatch_event(MessageEvent.new("message", "data" => data))
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
def
|
|
82
|
+
def __test_simulate_close__(code = 1000, reason = "", was_clean: true)
|
|
83
83
|
@ready_state = CLOSED
|
|
84
84
|
dispatch_event(
|
|
85
85
|
CloseEvent.new(
|
|
@@ -91,7 +91,7 @@ module Dommy
|
|
|
91
91
|
)
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
-
def
|
|
94
|
+
def __test_simulate_error__
|
|
95
95
|
dispatch_event(Event.new("error"))
|
|
96
96
|
end
|
|
97
97
|
|
|
@@ -151,7 +151,7 @@ module Dommy
|
|
|
151
151
|
end
|
|
152
152
|
end
|
|
153
153
|
|
|
154
|
-
def
|
|
154
|
+
def __internal_event_parent__
|
|
155
155
|
nil
|
|
156
156
|
end
|
|
157
157
|
|
data/lib/dommy/window.rb
CHANGED
|
@@ -101,6 +101,8 @@ module Dommy
|
|
|
101
101
|
@url_ctor = Bridge::Constructor.new { |args| URL.new(args[0], args[1]) }
|
|
102
102
|
@url_ctor.define_class_method("createObjectURL") { |args| URL.create_object_url(args[0]) }
|
|
103
103
|
@url_ctor.define_class_method("revokeObjectURL") { |args| URL.revoke_object_url(args[0]) }
|
|
104
|
+
@url_ctor.define_class_method("parse") { |args| URL.parse(args[0], args[1]) }
|
|
105
|
+
@url_ctor.define_class_method("canParse") { |args| URL.can_parse(args[0], args[1]) }
|
|
104
106
|
# `JS.global[:__some_key__] = ...` from user code lands here.
|
|
105
107
|
# Test code uses this for stub installation (e.g. a custom
|
|
106
108
|
# `__fetch_stub__`); production code stays on the typed
|
|
@@ -273,6 +275,17 @@ module Dommy
|
|
|
273
275
|
nil
|
|
274
276
|
end
|
|
275
277
|
|
|
278
|
+
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
279
|
+
JS_METHOD_NAMES = %w[
|
|
280
|
+
fetch encodeURIComponent decodeURIComponent addEventListener removeEventListener
|
|
281
|
+
dispatchEvent setTimeout clearTimeout setInterval clearInterval requestAnimationFrame
|
|
282
|
+
cancelAnimationFrame queueMicrotask requestIdleCallback cancelIdleCallback
|
|
283
|
+
structuredClone matchMedia getComputedStyle
|
|
284
|
+
].freeze
|
|
285
|
+
def __js_method_names__
|
|
286
|
+
JS_METHOD_NAMES
|
|
287
|
+
end
|
|
288
|
+
|
|
276
289
|
def __js_call__(method, args)
|
|
277
290
|
case method
|
|
278
291
|
when "fetch"
|
|
@@ -336,7 +349,7 @@ module Dommy
|
|
|
336
349
|
end
|
|
337
350
|
end
|
|
338
351
|
|
|
339
|
-
def
|
|
352
|
+
def __internal_event_parent__
|
|
340
353
|
nil
|
|
341
354
|
end
|
|
342
355
|
|
data/lib/dommy/worker.rb
CHANGED
|
@@ -6,11 +6,11 @@ module Dommy
|
|
|
6
6
|
#
|
|
7
7
|
# - `new Worker("/path/to/worker.js")` records the URL.
|
|
8
8
|
# - The script body is not executed. Tests install message
|
|
9
|
-
# handlers on the worker-side via `worker.
|
|
9
|
+
# handlers on the worker-side via `worker.__test_on_message__ { ... }`
|
|
10
10
|
# to simulate behavior.
|
|
11
11
|
# - `worker.postMessage(data)` queues a microtask that delivers
|
|
12
12
|
# to the worker-side handler.
|
|
13
|
-
# - The worker-side handler can call `worker.
|
|
13
|
+
# - The worker-side handler can call `worker.__test_post_to_main__(data)`
|
|
14
14
|
# to deliver a message back to the main side's `message` event.
|
|
15
15
|
#
|
|
16
16
|
# This is enough surface to test "the app correctly posts/receives
|
|
@@ -56,12 +56,12 @@ module Dommy
|
|
|
56
56
|
|
|
57
57
|
# Register a callback that runs in the "worker side". Multiple
|
|
58
58
|
# registrations stack.
|
|
59
|
-
def
|
|
59
|
+
def __test_on_message__(&block)
|
|
60
60
|
@worker_side_handlers << block
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# Worker-side: deliver a message to the main-side `message` event.
|
|
64
|
-
def
|
|
64
|
+
def __test_post_to_main__(data)
|
|
65
65
|
cloned = Dommy.structured_clone(data)
|
|
66
66
|
@window.scheduler.queue_microtask(
|
|
67
67
|
proc do
|
|
@@ -104,7 +104,7 @@ module Dommy
|
|
|
104
104
|
end
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
def
|
|
107
|
+
def __internal_event_parent__
|
|
108
108
|
nil
|
|
109
109
|
end
|
|
110
110
|
|
|
@@ -117,6 +117,11 @@ module Dommy
|
|
|
117
117
|
|
|
118
118
|
def abort
|
|
119
119
|
return if @ready_state == UNSENT || @ready_state == DONE
|
|
120
|
+
# WHATWG: abort() is a no-op when in OPENED with the send()
|
|
121
|
+
# flag unset. Without this guard, `xhr.open(); xhr.abort()`
|
|
122
|
+
# would fire abort + loadend even though no request is
|
|
123
|
+
# in flight.
|
|
124
|
+
return if @ready_state == OPENED && !@sent
|
|
120
125
|
|
|
121
126
|
@aborted = true
|
|
122
127
|
@generation += 1
|
|
@@ -236,7 +241,7 @@ module Dommy
|
|
|
236
241
|
end
|
|
237
242
|
end
|
|
238
243
|
|
|
239
|
-
def
|
|
244
|
+
def __internal_event_parent__
|
|
240
245
|
nil
|
|
241
246
|
end
|
|
242
247
|
|
|
@@ -360,8 +365,10 @@ module Dommy
|
|
|
360
365
|
nil
|
|
361
366
|
end
|
|
362
367
|
|
|
363
|
-
when "arraybuffer"
|
|
364
|
-
body
|
|
368
|
+
when "arraybuffer"
|
|
369
|
+
body.bytes
|
|
370
|
+
when "blob"
|
|
371
|
+
Blob.new([body], "type" => response_content_type)
|
|
365
372
|
when "document"
|
|
366
373
|
parse_document(body)
|
|
367
374
|
else
|
|
@@ -369,6 +376,14 @@ module Dommy
|
|
|
369
376
|
end
|
|
370
377
|
end
|
|
371
378
|
|
|
379
|
+
# Content-Type of the received response, read straight from
|
|
380
|
+
# `@response_headers` (not `get_response_header`, which gates on
|
|
381
|
+
# `readyState` — decode runs before that flag advances).
|
|
382
|
+
def response_content_type
|
|
383
|
+
hit = @response_headers.find { |k, _| k.to_s.downcase == "content-type" }
|
|
384
|
+
hit ? hit.last.to_s : ""
|
|
385
|
+
end
|
|
386
|
+
|
|
372
387
|
def parse_document(body)
|
|
373
388
|
DOMParser.new.parse_from_string(body, "text/html")
|
|
374
389
|
rescue StandardError
|
|
@@ -416,7 +431,7 @@ module Dommy
|
|
|
416
431
|
end
|
|
417
432
|
end
|
|
418
433
|
|
|
419
|
-
def
|
|
434
|
+
def __internal_event_parent__
|
|
420
435
|
nil
|
|
421
436
|
end
|
|
422
437
|
end
|
data/lib/dommy.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "nokogiri"
|
|
4
3
|
require "set"
|
|
5
4
|
|
|
6
5
|
require_relative "dommy/version"
|
|
6
|
+
require_relative "dommy/backend"
|
|
7
7
|
require_relative "dommy/dom_exception"
|
|
8
8
|
require_relative "dommy/node"
|
|
9
9
|
require_relative "dommy/html_collection"
|
|
@@ -16,6 +16,7 @@ require_relative "dommy/data_transfer"
|
|
|
16
16
|
require_relative "dommy/crypto"
|
|
17
17
|
require_relative "dommy/text_codec"
|
|
18
18
|
require_relative "dommy/internal/observable_callback"
|
|
19
|
+
require_relative "dommy/internal/css_pseudo_handlers"
|
|
19
20
|
require_relative "dommy/internal/punycode"
|
|
20
21
|
require_relative "dommy/internal/idna"
|
|
21
22
|
require_relative "dommy/internal/ipv4_parser"
|
|
@@ -73,7 +74,7 @@ module Dommy
|
|
|
73
74
|
def self.parse(html)
|
|
74
75
|
s = html.to_s
|
|
75
76
|
if s.match?(/\A\s*(<!doctype|<html\b)/i)
|
|
76
|
-
Window.new(nil, nokogiri_doc:
|
|
77
|
+
Window.new(nil, nokogiri_doc: Backend.parse(s))
|
|
77
78
|
else
|
|
78
79
|
window = Window.new
|
|
79
80
|
window.document.body.inner_html = s
|
|
@@ -81,6 +82,15 @@ module Dommy
|
|
|
81
82
|
end
|
|
82
83
|
end
|
|
83
84
|
|
|
85
|
+
# Convenience accessor: `Dommy.backend` / `Dommy.backend=`.
|
|
86
|
+
def self.backend
|
|
87
|
+
Backend.current
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.backend=(new_backend)
|
|
91
|
+
Backend.current = new_backend
|
|
92
|
+
end
|
|
93
|
+
|
|
84
94
|
# Build a fresh, empty Window (no host). Equivalent to opening a
|
|
85
95
|
# blank document.
|
|
86
96
|
def self.new_window
|
metadata
CHANGED
|
@@ -1,38 +1,20 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dommy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- takahashim
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-05-
|
|
11
|
-
dependencies:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- - "~>"
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '1.15'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - "~>"
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '1.15'
|
|
26
|
-
description: |
|
|
27
|
-
A pure-Ruby DOM polyfill on top of Nokogiri::HTML5, a Ruby-side
|
|
28
|
-
analogue to JavaScript DOM libraries like happy-dom and jsdom.
|
|
29
|
-
It exposes browser-like DOM semantics — events, MutationObserver,
|
|
30
|
-
Custom Elements, Shadow DOM, the File API (Blob / File / FormData /
|
|
31
|
-
DataTransfer), URL, Promise, timers, and Storage — without spinning
|
|
32
|
-
up a real browser.
|
|
10
|
+
date: 2026-05-29 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: 'A pure Ruby DOM polyfill built on Nokogiri::HTML5, inspired by happy-dom
|
|
13
|
+
and jsdom. It gives Ruby tests a browser style DOM with events, MutationObserver,
|
|
14
|
+
Custom Elements, Shadow DOM, the File API, timers, and Storage, without requiring
|
|
15
|
+
a real browser.
|
|
33
16
|
|
|
34
|
-
|
|
35
|
-
drop-in RSpec matchers and Minitest assertions.
|
|
17
|
+
'
|
|
36
18
|
email:
|
|
37
19
|
- takahashimm@gmail.com
|
|
38
20
|
executables: []
|
|
@@ -43,6 +25,9 @@ files:
|
|
|
43
25
|
- lib/dommy.rb
|
|
44
26
|
- lib/dommy/animation.rb
|
|
45
27
|
- lib/dommy/attr.rb
|
|
28
|
+
- lib/dommy/backend.rb
|
|
29
|
+
- lib/dommy/backend/nokogiri_adapter.rb
|
|
30
|
+
- lib/dommy/backend/nokolexbor_adapter.rb
|
|
46
31
|
- lib/dommy/blob.rb
|
|
47
32
|
- lib/dommy/bridge.rb
|
|
48
33
|
- lib/dommy/compression_streams.rb
|
|
@@ -64,6 +49,7 @@ files:
|
|
|
64
49
|
- lib/dommy/html_collection.rb
|
|
65
50
|
- lib/dommy/html_elements.rb
|
|
66
51
|
- lib/dommy/internal/cookie_jar.rb
|
|
52
|
+
- lib/dommy/internal/css_pseudo_handlers.rb
|
|
67
53
|
- lib/dommy/internal/dom_matching.rb
|
|
68
54
|
- lib/dommy/internal/idna.rb
|
|
69
55
|
- lib/dommy/internal/idna_data.rb
|