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/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
|
|
@@ -256,9 +299,36 @@ module Dommy
|
|
|
256
299
|
alias has_focus has_focus?
|
|
257
300
|
|
|
258
301
|
def get_selection
|
|
259
|
-
|
|
302
|
+
@__selection ||= Selection.new(self)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def create_range
|
|
306
|
+
Range.new(self)
|
|
260
307
|
end
|
|
261
308
|
|
|
309
|
+
# Fullscreen API — no actual fullscreen mode, just track which
|
|
310
|
+
# element claimed it. `element.requestFullscreen()` sets it; this
|
|
311
|
+
# is the read side.
|
|
312
|
+
attr_reader :fullscreen_element
|
|
313
|
+
|
|
314
|
+
def __internal_set_fullscreen_element__(element)
|
|
315
|
+
previous = @fullscreen_element
|
|
316
|
+
@fullscreen_element = element
|
|
317
|
+
return if previous == element
|
|
318
|
+
|
|
319
|
+
dispatch_event(Event.new("fullscreenchange"))
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def exit_fullscreen
|
|
323
|
+
return PromiseValue.resolve(@default_view, nil) if @fullscreen_element.nil?
|
|
324
|
+
|
|
325
|
+
@fullscreen_element = nil
|
|
326
|
+
dispatch_event(Event.new("fullscreenchange"))
|
|
327
|
+
PromiseValue.resolve(@default_view, nil)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
alias exitFullscreen exit_fullscreen
|
|
331
|
+
|
|
262
332
|
def element_from_point(_x, _y)
|
|
263
333
|
nil
|
|
264
334
|
end
|
|
@@ -312,8 +382,8 @@ module Dommy
|
|
|
312
382
|
fragment = Parser.fragment(html, owner_doc: @nokogiri_doc)
|
|
313
383
|
removed = []
|
|
314
384
|
added = fragment.children.to_a
|
|
315
|
-
added.each { |node| @body.
|
|
316
|
-
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)
|
|
317
387
|
nil
|
|
318
388
|
end
|
|
319
389
|
|
|
@@ -359,6 +429,12 @@ module Dommy
|
|
|
359
429
|
doctype
|
|
360
430
|
when "defaultView"
|
|
361
431
|
@default_view
|
|
432
|
+
when "fullscreenElement"
|
|
433
|
+
@fullscreen_element
|
|
434
|
+
when "fullscreenEnabled"
|
|
435
|
+
true
|
|
436
|
+
when "scrollingElement"
|
|
437
|
+
wrap_node(@nokogiri_doc.at_css("html"))
|
|
362
438
|
when "documentElement"
|
|
363
439
|
wrap_node(@nokogiri_doc.at_css("html"))
|
|
364
440
|
when "title"
|
|
@@ -375,6 +451,10 @@ module Dommy
|
|
|
375
451
|
base_uri
|
|
376
452
|
when "domain"
|
|
377
453
|
domain
|
|
454
|
+
when "origin"
|
|
455
|
+
origin
|
|
456
|
+
when "contentType"
|
|
457
|
+
content_type
|
|
378
458
|
when "referrer"
|
|
379
459
|
referrer
|
|
380
460
|
when "links"
|
|
@@ -411,8 +491,35 @@ module Dommy
|
|
|
411
491
|
nil
|
|
412
492
|
end
|
|
413
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
|
+
|
|
414
507
|
def __js_call__(method, args)
|
|
415
508
|
case method
|
|
509
|
+
when "exitFullscreen"
|
|
510
|
+
exit_fullscreen
|
|
511
|
+
when "startViewTransition"
|
|
512
|
+
# View Transitions API stub. Spec: invoke the callback
|
|
513
|
+
# synchronously; return a ViewTransition with already-resolved
|
|
514
|
+
# `finished` / `ready` / `updateCallbackDone` promises.
|
|
515
|
+
callback = args[0]
|
|
516
|
+
if callback.respond_to?(:__js_call__)
|
|
517
|
+
callback.__js_call__("call", [])
|
|
518
|
+
elsif callback.respond_to?(:call)
|
|
519
|
+
callback.call
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
ViewTransition.new(@default_view)
|
|
416
523
|
when "createElement"
|
|
417
524
|
create_element(args[0])
|
|
418
525
|
when "createElementNS"
|
|
@@ -474,7 +581,7 @@ module Dommy
|
|
|
474
581
|
end
|
|
475
582
|
end
|
|
476
583
|
|
|
477
|
-
def
|
|
584
|
+
def __internal_event_parent__
|
|
478
585
|
@default_view
|
|
479
586
|
end
|
|
480
587
|
|
|
@@ -486,7 +593,7 @@ module Dommy
|
|
|
486
593
|
# Clear the cached wrapper so the next `wrap_node` creates a new
|
|
487
594
|
# one. Used by `customElements.define` to upgrade nodes that were
|
|
488
595
|
# constructed before the registration landed.
|
|
489
|
-
def
|
|
596
|
+
def __internal_reset_wrapper__(nokogiri_node)
|
|
490
597
|
@node_wrapper_cache.reset_wrapper(nokogiri_node)
|
|
491
598
|
end
|
|
492
599
|
|
|
@@ -496,15 +603,15 @@ module Dommy
|
|
|
496
603
|
# node back to its shadow boundary.
|
|
497
604
|
# Delegate to ShadowRootRegistry
|
|
498
605
|
|
|
499
|
-
def
|
|
606
|
+
def __internal_register_shadow_fragment__(fragment_node, shadow_root)
|
|
500
607
|
@shadow_registry.register(fragment_node, shadow_root)
|
|
501
608
|
end
|
|
502
609
|
|
|
503
|
-
def
|
|
610
|
+
def __internal_shadow_root_for_fragment__(fragment_node)
|
|
504
611
|
@shadow_registry.find_for_fragment(fragment_node)
|
|
505
612
|
end
|
|
506
613
|
|
|
507
|
-
def
|
|
614
|
+
def __internal_shadow_root_containing__(node)
|
|
508
615
|
@shadow_registry.find_enclosing(node)
|
|
509
616
|
end
|
|
510
617
|
|
|
@@ -513,23 +620,23 @@ module Dommy
|
|
|
513
620
|
# break the whole mutation pipeline.
|
|
514
621
|
# Delegate to MutationCoordinator
|
|
515
622
|
|
|
516
|
-
def
|
|
623
|
+
def __internal_notify_connected__(element)
|
|
517
624
|
@mutation_coordinator.notify_connected(element)
|
|
518
625
|
end
|
|
519
626
|
|
|
520
|
-
def
|
|
627
|
+
def __internal_notify_disconnected__(element)
|
|
521
628
|
@mutation_coordinator.notify_disconnected(element)
|
|
522
629
|
end
|
|
523
630
|
|
|
524
|
-
def
|
|
631
|
+
def __internal_notify_connected_subtree__(nk)
|
|
525
632
|
@mutation_coordinator.notify_connected_subtree(nk)
|
|
526
633
|
end
|
|
527
634
|
|
|
528
|
-
def
|
|
635
|
+
def __internal_notify_disconnected_subtree__(nk)
|
|
529
636
|
@mutation_coordinator.notify_disconnected_subtree(nk)
|
|
530
637
|
end
|
|
531
638
|
|
|
532
|
-
def
|
|
639
|
+
def __internal_notify_attribute_changed__(element, name, old_value, new_value)
|
|
533
640
|
@mutation_coordinator.notify_attribute_changed(element, name, old_value, new_value)
|
|
534
641
|
end
|
|
535
642
|
|
|
@@ -628,17 +735,17 @@ module Dommy
|
|
|
628
735
|
# adoptNode for cross-document transfer.
|
|
629
736
|
def clone_into_doc(source, deep)
|
|
630
737
|
copy = if source.element?
|
|
631
|
-
new_el =
|
|
738
|
+
new_el = Backend.create_element(source.name, @nokogiri_doc)
|
|
632
739
|
source.attribute_nodes.each { |a| new_el[a.name] = a.value }
|
|
633
740
|
new_el
|
|
634
741
|
elsif source.text?
|
|
635
|
-
|
|
636
|
-
elsif source.is_a?(
|
|
637
|
-
|
|
742
|
+
Backend.create_text(source.content, @nokogiri_doc)
|
|
743
|
+
elsif source.is_a?(Backend.comment_class)
|
|
744
|
+
Backend.create_comment(source.content, @nokogiri_doc)
|
|
638
745
|
else
|
|
639
746
|
# Fallback: serialize + reparse via fragment for unusual types.
|
|
640
747
|
fragment = Parser.fragment(source.to_html, owner_doc: @nokogiri_doc)
|
|
641
|
-
fragment.children.first ||
|
|
748
|
+
fragment.children.first || Backend.create_text("", @nokogiri_doc)
|
|
642
749
|
end
|
|
643
750
|
|
|
644
751
|
if deep && source.respond_to?(:children)
|
|
@@ -662,13 +769,64 @@ module Dommy
|
|
|
662
769
|
|
|
663
770
|
title = head.at_css("title")
|
|
664
771
|
unless title
|
|
665
|
-
title =
|
|
772
|
+
title = Backend.create_element("title", @nokogiri_doc)
|
|
666
773
|
head.add_child(title)
|
|
667
774
|
end
|
|
668
775
|
|
|
669
776
|
title.children.each(&:unlink)
|
|
670
|
-
title.add_child(
|
|
777
|
+
title.add_child(Backend.create_text(value, @nokogiri_doc))
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
# `ViewTransition` — return value of `document.startViewTransition()`.
|
|
783
|
+
# All three Promises (`finished` / `ready` / `updateCallbackDone`)
|
|
784
|
+
# resolve immediately since dommy has no actual paint phase.
|
|
785
|
+
#
|
|
786
|
+
# Spec: https://drafts.csswg.org/css-view-transitions/
|
|
787
|
+
class ViewTransition
|
|
788
|
+
def initialize(window)
|
|
789
|
+
@finished = PromiseValue.resolve(window, nil)
|
|
790
|
+
@ready = PromiseValue.resolve(window, nil)
|
|
791
|
+
@update_callback_done = PromiseValue.resolve(window, nil)
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
attr_reader :finished, :ready
|
|
795
|
+
|
|
796
|
+
def update_callback_done
|
|
797
|
+
@update_callback_done
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
alias updateCallbackDone update_callback_done
|
|
801
|
+
|
|
802
|
+
def skip_transition
|
|
803
|
+
nil
|
|
671
804
|
end
|
|
672
805
|
|
|
806
|
+
alias skipTransition skip_transition
|
|
807
|
+
|
|
808
|
+
def __js_get__(key)
|
|
809
|
+
case key
|
|
810
|
+
when "finished"
|
|
811
|
+
@finished
|
|
812
|
+
when "ready"
|
|
813
|
+
@ready
|
|
814
|
+
when "updateCallbackDone"
|
|
815
|
+
@update_callback_done
|
|
816
|
+
end
|
|
817
|
+
end
|
|
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
|
+
|
|
825
|
+
def __js_call__(method, _args)
|
|
826
|
+
case method
|
|
827
|
+
when "skipTransition"
|
|
828
|
+
skip_transition
|
|
829
|
+
end
|
|
830
|
+
end
|
|
673
831
|
end
|
|
674
832
|
end
|
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
|