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
|
@@ -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
|
|
data/lib/dommy/document.rb
CHANGED
|
@@ -46,6 +46,9 @@ module Dommy
|
|
|
46
46
|
|
|
47
47
|
attr_reader :body, :nokogiri_doc
|
|
48
48
|
attr_accessor :default_view
|
|
49
|
+
# content_type defaults to "text/html"; settable so an integration layer
|
|
50
|
+
# can reflect the response Content-Type. Read-only over the JS bridge.
|
|
51
|
+
attr_accessor :content_type
|
|
49
52
|
|
|
50
53
|
def initialize(host = nil, nokogiri_doc: nil, default_view: nil)
|
|
51
54
|
@host = host
|
|
@@ -56,9 +59,10 @@ module Dommy
|
|
|
56
59
|
@cookie_jar = Internal::CookieJar.new
|
|
57
60
|
@template_content_registry = Internal::TemplateContentRegistry.new(self)
|
|
58
61
|
@mutation_coordinator = Internal::MutationCoordinator.new(self, @observer_manager)
|
|
59
|
-
@nokogiri_doc = nokogiri_doc ||
|
|
62
|
+
@nokogiri_doc = nokogiri_doc || Backend.parse("<!doctype html><html><head></head><body></body></html>")
|
|
60
63
|
body_node = @nokogiri_doc.at_css("body")
|
|
61
64
|
@body = wrap_node(body_node) if body_node
|
|
65
|
+
@content_type = "text/html"
|
|
62
66
|
end
|
|
63
67
|
|
|
64
68
|
# ----- Public Ruby API (snake_case) -----
|
|
@@ -79,6 +83,21 @@ module Dommy
|
|
|
79
83
|
wrap_node(@nokogiri_doc.at_css("head"))
|
|
80
84
|
end
|
|
81
85
|
|
|
86
|
+
# Serialize the whole document to HTML (including the doctype).
|
|
87
|
+
def to_html
|
|
88
|
+
@nokogiri_doc.to_html
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# XPath queries returning wrapped nodes (Element / TextNode / etc).
|
|
92
|
+
def at_xpath(expression)
|
|
93
|
+
node = @nokogiri_doc.at_xpath(expression)
|
|
94
|
+
node && wrap_node(node)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def xpath(expression)
|
|
98
|
+
@nokogiri_doc.xpath(expression).map { |node| wrap_node(node) }
|
|
99
|
+
end
|
|
100
|
+
|
|
82
101
|
# `document.URL` / `documentURI` — both return location.href in
|
|
83
102
|
# real browsers (legacy aliases of the same field).
|
|
84
103
|
def url
|
|
@@ -116,6 +135,15 @@ module Dommy
|
|
|
116
135
|
view.location.__js_get__("hostname").to_s
|
|
117
136
|
end
|
|
118
137
|
|
|
138
|
+
# `document.origin` — serialized origin of the document URL, mirroring
|
|
139
|
+
# `window.location.origin`. Empty when there is no associated window.
|
|
140
|
+
def origin
|
|
141
|
+
view = @default_view
|
|
142
|
+
return "" unless view&.location
|
|
143
|
+
|
|
144
|
+
view.location.__js_get__("origin").to_s
|
|
145
|
+
end
|
|
146
|
+
|
|
119
147
|
# `document.referrer` — Dommy never has a referring page, so this
|
|
120
148
|
# is always empty.
|
|
121
149
|
def referrer
|
|
@@ -175,7 +203,7 @@ module Dommy
|
|
|
175
203
|
@active_element || @body
|
|
176
204
|
end
|
|
177
205
|
|
|
178
|
-
def
|
|
206
|
+
def __internal_set_active_element__(el)
|
|
179
207
|
@active_element = el
|
|
180
208
|
end
|
|
181
209
|
|
|
@@ -202,9 +230,9 @@ module Dommy
|
|
|
202
230
|
# wrapper is owned by `this`. Per spec, the source node is left
|
|
203
231
|
# in place. `deep: true` copies the entire subtree.
|
|
204
232
|
def import_node(node, deep = false)
|
|
205
|
-
return nil unless node.respond_to?(:
|
|
233
|
+
return nil unless node.respond_to?(:__dommy_backend_node__)
|
|
206
234
|
|
|
207
|
-
copy = clone_into_doc(node.
|
|
235
|
+
copy = clone_into_doc(node.__dommy_backend_node__, deep)
|
|
208
236
|
wrap_node(copy)
|
|
209
237
|
end
|
|
210
238
|
|
|
@@ -212,17 +240,32 @@ module Dommy
|
|
|
212
240
|
# node is detached from its previous owner and its ownerDocument
|
|
213
241
|
# becomes this. Returns the (possibly re-wrapped) node.
|
|
214
242
|
def adopt_node(node)
|
|
215
|
-
return nil unless node.respond_to?(:
|
|
243
|
+
return nil unless node.respond_to?(:__dommy_backend_node__)
|
|
216
244
|
|
|
217
|
-
src = node.
|
|
245
|
+
src = node.__dommy_backend_node__
|
|
218
246
|
src.unlink if src.parent
|
|
219
|
-
moved = if src.document == @nokogiri_doc
|
|
220
|
-
src
|
|
221
|
-
else
|
|
222
|
-
clone_into_doc(src, true)
|
|
223
|
-
end
|
|
224
247
|
|
|
225
|
-
|
|
248
|
+
# Same document: just return the wrapper after the detach above.
|
|
249
|
+
return wrap_node(src) if src.document == @nokogiri_doc
|
|
250
|
+
|
|
251
|
+
# Cross-document: Nokogiri reassigns `src.document` when src is
|
|
252
|
+
# added under a node owned by another document. We transiently
|
|
253
|
+
# attach to our root, then unlink so src ends up free-floating
|
|
254
|
+
# but now belongs to @nokogiri_doc. The underlying Ruby object
|
|
255
|
+
# identity is preserved.
|
|
256
|
+
src_doc_wrapper = node.instance_variable_get(:@document)
|
|
257
|
+
@nokogiri_doc.root.add_child(src)
|
|
258
|
+
src.unlink
|
|
259
|
+
|
|
260
|
+
# Move the caller's Dommy wrapper from the source document's
|
|
261
|
+
# wrapper cache into ours, and re-point its @document. This
|
|
262
|
+
# keeps `adopt_node(x).equal?(x)` true across documents.
|
|
263
|
+
node.instance_variable_set(:@document, self)
|
|
264
|
+
if src_doc_wrapper.respond_to?(:__internal_reset_wrapper__)
|
|
265
|
+
src_doc_wrapper.__internal_reset_wrapper__(src)
|
|
266
|
+
end
|
|
267
|
+
@node_wrapper_cache.register(src, node)
|
|
268
|
+
node
|
|
226
269
|
end
|
|
227
270
|
|
|
228
271
|
# Legacy `document.createEvent("EventName")` factory. Returns an
|
|
@@ -268,7 +311,7 @@ module Dommy
|
|
|
268
311
|
# is the read side.
|
|
269
312
|
attr_reader :fullscreen_element
|
|
270
313
|
|
|
271
|
-
def
|
|
314
|
+
def __internal_set_fullscreen_element__(element)
|
|
272
315
|
previous = @fullscreen_element
|
|
273
316
|
@fullscreen_element = element
|
|
274
317
|
return if previous == element
|
|
@@ -339,8 +382,8 @@ module Dommy
|
|
|
339
382
|
fragment = Parser.fragment(html, owner_doc: @nokogiri_doc)
|
|
340
383
|
removed = []
|
|
341
384
|
added = fragment.children.to_a
|
|
342
|
-
added.each { |node| @body.
|
|
343
|
-
notify_child_list_mutation(target_node: @body.
|
|
385
|
+
added.each { |node| @body.__dommy_backend_node__.add_child(node) }
|
|
386
|
+
notify_child_list_mutation(target_node: @body.__dommy_backend_node__, added_nodes: added, removed_nodes: removed)
|
|
344
387
|
nil
|
|
345
388
|
end
|
|
346
389
|
|
|
@@ -408,6 +451,10 @@ module Dommy
|
|
|
408
451
|
base_uri
|
|
409
452
|
when "domain"
|
|
410
453
|
domain
|
|
454
|
+
when "origin"
|
|
455
|
+
origin
|
|
456
|
+
when "contentType"
|
|
457
|
+
content_type
|
|
411
458
|
when "referrer"
|
|
412
459
|
referrer
|
|
413
460
|
when "links"
|
|
@@ -444,6 +491,19 @@ module Dommy
|
|
|
444
491
|
nil
|
|
445
492
|
end
|
|
446
493
|
|
|
494
|
+
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
495
|
+
JS_METHOD_NAMES = %w[
|
|
496
|
+
exitFullscreen startViewTransition createElement createElementNS createTextNode
|
|
497
|
+
createComment createDocumentFragment querySelector querySelectorAll getElementById
|
|
498
|
+
getElementsByClassName getElementsByTagName getElementsByName createAttribute
|
|
499
|
+
createAttributeNS createTreeWalker createNodeIterator createEvent importNode adoptNode
|
|
500
|
+
hasFocus getSelection elementFromPoint queryCommandSupported addEventListener
|
|
501
|
+
removeEventListener dispatchEvent write open close
|
|
502
|
+
].freeze
|
|
503
|
+
def __js_method_names__
|
|
504
|
+
JS_METHOD_NAMES
|
|
505
|
+
end
|
|
506
|
+
|
|
447
507
|
def __js_call__(method, args)
|
|
448
508
|
case method
|
|
449
509
|
when "exitFullscreen"
|
|
@@ -521,7 +581,7 @@ module Dommy
|
|
|
521
581
|
end
|
|
522
582
|
end
|
|
523
583
|
|
|
524
|
-
def
|
|
584
|
+
def __internal_event_parent__
|
|
525
585
|
@default_view
|
|
526
586
|
end
|
|
527
587
|
|
|
@@ -533,7 +593,7 @@ module Dommy
|
|
|
533
593
|
# Clear the cached wrapper so the next `wrap_node` creates a new
|
|
534
594
|
# one. Used by `customElements.define` to upgrade nodes that were
|
|
535
595
|
# constructed before the registration landed.
|
|
536
|
-
def
|
|
596
|
+
def __internal_reset_wrapper__(nokogiri_node)
|
|
537
597
|
@node_wrapper_cache.reset_wrapper(nokogiri_node)
|
|
538
598
|
end
|
|
539
599
|
|
|
@@ -543,15 +603,15 @@ module Dommy
|
|
|
543
603
|
# node back to its shadow boundary.
|
|
544
604
|
# Delegate to ShadowRootRegistry
|
|
545
605
|
|
|
546
|
-
def
|
|
606
|
+
def __internal_register_shadow_fragment__(fragment_node, shadow_root)
|
|
547
607
|
@shadow_registry.register(fragment_node, shadow_root)
|
|
548
608
|
end
|
|
549
609
|
|
|
550
|
-
def
|
|
610
|
+
def __internal_shadow_root_for_fragment__(fragment_node)
|
|
551
611
|
@shadow_registry.find_for_fragment(fragment_node)
|
|
552
612
|
end
|
|
553
613
|
|
|
554
|
-
def
|
|
614
|
+
def __internal_shadow_root_containing__(node)
|
|
555
615
|
@shadow_registry.find_enclosing(node)
|
|
556
616
|
end
|
|
557
617
|
|
|
@@ -560,23 +620,23 @@ module Dommy
|
|
|
560
620
|
# break the whole mutation pipeline.
|
|
561
621
|
# Delegate to MutationCoordinator
|
|
562
622
|
|
|
563
|
-
def
|
|
623
|
+
def __internal_notify_connected__(element)
|
|
564
624
|
@mutation_coordinator.notify_connected(element)
|
|
565
625
|
end
|
|
566
626
|
|
|
567
|
-
def
|
|
627
|
+
def __internal_notify_disconnected__(element)
|
|
568
628
|
@mutation_coordinator.notify_disconnected(element)
|
|
569
629
|
end
|
|
570
630
|
|
|
571
|
-
def
|
|
631
|
+
def __internal_notify_connected_subtree__(nk)
|
|
572
632
|
@mutation_coordinator.notify_connected_subtree(nk)
|
|
573
633
|
end
|
|
574
634
|
|
|
575
|
-
def
|
|
635
|
+
def __internal_notify_disconnected_subtree__(nk)
|
|
576
636
|
@mutation_coordinator.notify_disconnected_subtree(nk)
|
|
577
637
|
end
|
|
578
638
|
|
|
579
|
-
def
|
|
639
|
+
def __internal_notify_attribute_changed__(element, name, old_value, new_value)
|
|
580
640
|
@mutation_coordinator.notify_attribute_changed(element, name, old_value, new_value)
|
|
581
641
|
end
|
|
582
642
|
|
|
@@ -675,17 +735,17 @@ module Dommy
|
|
|
675
735
|
# adoptNode for cross-document transfer.
|
|
676
736
|
def clone_into_doc(source, deep)
|
|
677
737
|
copy = if source.element?
|
|
678
|
-
new_el =
|
|
738
|
+
new_el = Backend.create_element(source.name, @nokogiri_doc)
|
|
679
739
|
source.attribute_nodes.each { |a| new_el[a.name] = a.value }
|
|
680
740
|
new_el
|
|
681
741
|
elsif source.text?
|
|
682
|
-
|
|
683
|
-
elsif source.is_a?(
|
|
684
|
-
|
|
742
|
+
Backend.create_text(source.content, @nokogiri_doc)
|
|
743
|
+
elsif source.is_a?(Backend.comment_class)
|
|
744
|
+
Backend.create_comment(source.content, @nokogiri_doc)
|
|
685
745
|
else
|
|
686
746
|
# Fallback: serialize + reparse via fragment for unusual types.
|
|
687
747
|
fragment = Parser.fragment(source.to_html, owner_doc: @nokogiri_doc)
|
|
688
|
-
fragment.children.first ||
|
|
748
|
+
fragment.children.first || Backend.create_text("", @nokogiri_doc)
|
|
689
749
|
end
|
|
690
750
|
|
|
691
751
|
if deep && source.respond_to?(:children)
|
|
@@ -709,12 +769,12 @@ module Dommy
|
|
|
709
769
|
|
|
710
770
|
title = head.at_css("title")
|
|
711
771
|
unless title
|
|
712
|
-
title =
|
|
772
|
+
title = Backend.create_element("title", @nokogiri_doc)
|
|
713
773
|
head.add_child(title)
|
|
714
774
|
end
|
|
715
775
|
|
|
716
776
|
title.children.each(&:unlink)
|
|
717
|
-
title.add_child(
|
|
777
|
+
title.add_child(Backend.create_text(value, @nokogiri_doc))
|
|
718
778
|
end
|
|
719
779
|
|
|
720
780
|
end
|
|
@@ -756,6 +816,12 @@ module Dommy
|
|
|
756
816
|
end
|
|
757
817
|
end
|
|
758
818
|
|
|
819
|
+
# Methods routed through __js_call__ (keep in sync with its when-arms).
|
|
820
|
+
JS_METHOD_NAMES = %w[skipTransition].freeze
|
|
821
|
+
def __js_method_names__
|
|
822
|
+
JS_METHOD_NAMES
|
|
823
|
+
end
|
|
824
|
+
|
|
759
825
|
def __js_call__(method, _args)
|
|
760
826
|
case method
|
|
761
827
|
when "skipTransition"
|
data/lib/dommy/dom_parser.rb
CHANGED
|
@@ -44,12 +44,13 @@ module Dommy
|
|
|
44
44
|
private
|
|
45
45
|
|
|
46
46
|
def parse_html(str)
|
|
47
|
-
nokogiri_doc =
|
|
47
|
+
nokogiri_doc = Backend.parse(str.empty? ? "<html><body></body></html>" : str)
|
|
48
48
|
Document.new(nil, nokogiri_doc: nokogiri_doc)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def parse_xml(str)
|
|
52
|
-
|
|
52
|
+
# Backends are HTML-only; parse XML input as HTML for now.
|
|
53
|
+
nokogiri_doc = Backend.parse(str.empty? ? "<html><body></body></html>" : str)
|
|
53
54
|
Document.new(nil, nokogiri_doc: nokogiri_doc)
|
|
54
55
|
end
|
|
55
56
|
end
|
|
@@ -63,8 +64,8 @@ module Dommy
|
|
|
63
64
|
|
|
64
65
|
if node.respond_to?(:outer_html)
|
|
65
66
|
node.outer_html
|
|
66
|
-
elsif node.respond_to?(:
|
|
67
|
-
node.
|
|
67
|
+
elsif node.respond_to?(:__dommy_backend_node__)
|
|
68
|
+
node.__dommy_backend_node__.to_xml
|
|
68
69
|
elsif node.respond_to?(:to_xml)
|
|
69
70
|
node.to_xml
|
|
70
71
|
else
|