dommy 0.13.0 → 0.14.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/lib/dommy/backend/makiri_adapter.rb +63 -29
- data/lib/dommy/backend.rb +32 -9
- data/lib/dommy/bridge/constructor.rb +72 -0
- data/lib/dommy/bridge/errors.rb +42 -0
- data/lib/dommy/bridge/methods.rb +21 -14
- data/lib/dommy/bridge/sentinels.rb +39 -0
- data/lib/dommy/bridge/values.rb +78 -0
- data/lib/dommy/{js → bridge}/wire_tags.rb +14 -11
- data/lib/dommy/bridge.rb +22 -250
- data/lib/dommy/browser.rb +112 -11
- data/lib/dommy/character_data.rb +539 -0
- data/lib/dommy/class_list.rb +311 -0
- data/lib/dommy/crypto.rb +12 -1
- data/lib/dommy/css.rb +12 -0
- data/lib/dommy/data_transfer.rb +106 -0
- data/lib/dommy/document.rb +307 -369
- data/lib/dommy/dom_parser.rb +3 -0
- data/lib/dommy/element.rb +199 -1817
- data/lib/dommy/encodings/tables.rb +4 -4
- data/lib/dommy/encodings.rb +58 -27
- data/lib/dommy/event.rb +227 -1
- data/lib/dommy/event_source.rb +41 -0
- data/lib/dommy/form_data.rb +6 -64
- data/lib/dommy/form_entry_list.rb +227 -0
- data/lib/dommy/fragment.rb +320 -0
- data/lib/dommy/history.rb +3 -0
- data/lib/dommy/html_collection.rb +18 -8
- data/lib/dommy/html_elements/base.rb +338 -0
- data/lib/dommy/html_elements/embedded.rb +237 -0
- data/lib/dommy/html_elements/forms/form.rb +1088 -0
- data/lib/dommy/html_elements/forms/input.rb +642 -0
- data/lib/dommy/html_elements/forms/select.rb +536 -0
- data/lib/dommy/html_elements/forms.rb +8 -0
- data/lib/dommy/html_elements/interactive.rb +382 -0
- data/lib/dommy/html_elements/media.rb +329 -0
- data/lib/dommy/html_elements/metadata.rb +353 -0
- data/lib/dommy/html_elements/miscellaneous.rb +77 -0
- data/lib/dommy/html_elements/tables.rb +468 -0
- data/lib/dommy/html_elements.rb +11 -5322
- data/lib/dommy/hyperlink_utils.rb +171 -0
- data/lib/dommy/interaction/debug.rb +7 -6
- data/lib/dommy/interaction/dom_summary.rb +65 -40
- data/lib/dommy/interaction/driver.rb +29 -115
- data/lib/dommy/interaction/event_synthesis.rb +97 -4
- data/lib/dommy/interaction/field_interactor.rb +1 -13
- data/lib/dommy/interaction/form_submission.rb +89 -167
- data/lib/dommy/interaction/key_sender.rb +121 -0
- data/lib/dommy/interaction/mime_types.rb +25 -0
- data/lib/dommy/interaction.rb +2 -0
- data/lib/dommy/internal/accessibility_tree.rb +32 -9
- data/lib/dommy/internal/accessibility_visibility.rb +44 -0
- data/lib/dommy/internal/accessible_name.rb +85 -65
- data/lib/dommy/internal/aria_role.rb +21 -6
- data/lib/dommy/internal/aria_snapshot.rb +5 -0
- data/lib/dommy/internal/aria_state.rb +24 -5
- data/lib/dommy/internal/autofill.rb +141 -0
- data/lib/dommy/internal/backend_prefilter.rb +170 -0
- data/lib/dommy/internal/bounded_cache.rb +50 -0
- data/lib/dommy/internal/child_node.rb +9 -34
- data/lib/dommy/internal/css/calc.rb +143 -145
- data/lib/dommy/internal/css/cascade.rb +38 -393
- data/lib/dommy/internal/css/cascaded_declarations.rb +245 -0
- data/lib/dommy/internal/css/color.rb +61 -34
- data/lib/dommy/internal/css/computed_style_builder.rb +205 -0
- data/lib/dommy/internal/css/computed_style_declaration.rb +20 -4
- data/lib/dommy/internal/css/counters.rb +60 -37
- data/lib/dommy/internal/css/custom_properties.rb +87 -39
- data/lib/dommy/internal/css/media_query.rb +228 -203
- data/lib/dommy/internal/css/parser.rb +29 -12
- data/lib/dommy/internal/css/property_registry.rb +32 -56
- data/lib/dommy/internal/css/renderability.rb +61 -0
- data/lib/dommy/internal/css/rule_index.rb +17 -140
- data/lib/dommy/internal/css/selector_dependencies.rb +162 -0
- data/lib/dommy/internal/css/style_cache.rb +61 -0
- data/lib/dommy/internal/css/ua_stylesheet.rb +40 -1
- data/lib/dommy/internal/css/value_tokens.rb +60 -0
- data/lib/dommy/internal/directionality.rb +188 -0
- data/lib/dommy/internal/document_generations.rb +169 -0
- data/lib/dommy/internal/document_interaction_state.rb +47 -0
- data/lib/dommy/internal/document_live_ranges.rb +97 -0
- data/lib/dommy/internal/dom_matching.rb +28 -12
- data/lib/dommy/internal/element_aria.rb +163 -0
- data/lib/dommy/internal/element_geometry.rb +69 -0
- data/lib/dommy/internal/element_matching.rb +5 -12
- data/lib/dommy/internal/element_shadow.rb +117 -0
- data/lib/dommy/internal/element_state.rb +232 -0
- data/lib/dommy/internal/element_tasks.rb +19 -0
- data/lib/dommy/internal/element_top_layer.rb +63 -0
- data/lib/dommy/internal/enumerated_keyword_sets.rb +53 -0
- data/lib/dommy/internal/exception_report.rb +1 -1
- data/lib/dommy/internal/input_type.rb +280 -0
- data/lib/dommy/internal/insertion_point.rb +62 -0
- data/lib/dommy/internal/mime_type.rb +74 -0
- data/lib/dommy/internal/mutation_coordinator.rb +57 -198
- data/lib/dommy/internal/namespaces.rb +41 -43
- data/lib/dommy/internal/node_adopter.rb +84 -77
- data/lib/dommy/internal/node_factory.rb +157 -0
- data/lib/dommy/internal/node_identity.rb +31 -0
- data/lib/dommy/internal/node_wrapper_cache.rb +61 -207
- data/lib/dommy/internal/observer_options.rb +93 -0
- data/lib/dommy/internal/parent_node.rb +27 -27
- data/lib/dommy/internal/post_insertion_steps.rb +213 -0
- data/lib/dommy/internal/reflected_attributes.rb +425 -13
- data/lib/dommy/internal/rendered_text/collector.rb +221 -0
- data/lib/dommy/internal/rendered_text/computed_box.rb +39 -0
- data/lib/dommy/internal/rendered_text/fragment.rb +55 -0
- data/lib/dommy/internal/rendered_text/items.rb +57 -0
- data/lib/dommy/internal/rendered_text/whitespace_collapser.rb +132 -0
- data/lib/dommy/internal/selector_arguments.rb +94 -0
- data/lib/dommy/internal/selector_index.rb +3 -1
- data/lib/dommy/internal/selector_matcher.rb +23 -383
- data/lib/dommy/internal/selector_parser.rb +2 -37
- data/lib/dommy/internal/template_content_registry.rb +112 -66
- data/lib/dommy/internal/text_flattening.rb +20 -0
- data/lib/dommy/internal/text_selection.rb +104 -0
- data/lib/dommy/internal/toggle_events.rb +56 -0
- data/lib/dommy/internal/toggle_task_tracker.rb +57 -0
- data/lib/dommy/internal/url_parser.rb +722 -569
- data/lib/dommy/internal/url_record_accessors.rb +90 -0
- data/lib/dommy/internal/window_constructors.rb +190 -0
- data/lib/dommy/internal/xml_serialization.rb +168 -80
- data/lib/dommy/js/crossing_profile.rb +75 -0
- data/lib/dommy/js/dom_interfaces.rb +22 -2
- data/lib/dommy/js/host_bridge.rb +43 -187
- data/lib/dommy/js/host_callbacks.rb +96 -0
- data/lib/dommy/js/host_runtime.js +840 -763
- data/lib/dommy/js/import_map.rb +10 -1
- data/lib/dommy/js/marshaller.rb +94 -65
- data/lib/dommy/js/observable_runtime.js +125 -146
- data/lib/dommy/js/runtime.rb +10 -43
- data/lib/dommy/js/runtime_registry.rb +46 -0
- data/lib/dommy/js/script_boot.rb +38 -73
- data/lib/dommy/js/webidl_tables.js +619 -0
- data/lib/dommy/js/window_wiring.rb +69 -0
- data/lib/dommy/location.rb +152 -103
- data/lib/dommy/mutation_observer.rb +3 -57
- data/lib/dommy/navigation.rb +65 -12
- data/lib/dommy/node.rb +46 -20
- data/lib/dommy/promise.rb +58 -4
- data/lib/dommy/resources.rb +19 -1
- data/lib/dommy/shadow_root.rb +11 -5
- data/lib/dommy/style_declaration.rb +284 -0
- data/lib/dommy/svg_elements.rb +1 -45
- data/lib/dommy/url.rb +20 -69
- data/lib/dommy/url_pattern/regexp_translator.rb +1 -6
- data/lib/dommy/url_pattern/tokenizer.rb +4 -2
- data/lib/dommy/validity_state.rb +383 -0
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/window.rb +30 -166
- data/lib/dommy/xml_http_request.rb +35 -4
- data/lib/dommy.rb +23 -3
- metadata +74 -7
- data/lib/dommy/bridge/constructor_registry.rb +0 -28
- data/lib/dommy/internal/css_pseudo_handlers.rb +0 -378
|
@@ -15,8 +15,9 @@ module Dommy
|
|
|
15
15
|
# - every live wrapper in the subtree, so a reference page script is
|
|
16
16
|
# holding (an aria element reference, a queued MutationRecord's target)
|
|
17
17
|
# keeps pointing at a node in the right document;
|
|
18
|
-
# - a `<template>`'s contents, which are not in its child list and
|
|
19
|
-
#
|
|
18
|
+
# - a `<template>`'s contents, which are not in its child list, and which
|
|
19
|
+
# the import copies in a shape of its own between an HTML and an XML
|
|
20
|
+
# document.
|
|
20
21
|
#
|
|
21
22
|
# Spec: https://dom.spec.whatwg.org/#concept-node-adopt
|
|
22
23
|
class NodeAdopter
|
|
@@ -55,50 +56,10 @@ module Dommy
|
|
|
55
56
|
return node if node.document == backend_doc
|
|
56
57
|
|
|
57
58
|
adopted = Backend.adopt(node, backend_doc)
|
|
58
|
-
if source_document && !source_document.equal?(@document)
|
|
59
|
-
reseat_wrapper(node, adopted, source_document)
|
|
60
|
-
reseat_descendant_wrappers(node, adopted, source_document)
|
|
61
|
-
adopt_template_contents(node, adopted, source_document)
|
|
62
|
-
end
|
|
59
|
+
carry_over(node, adopted, source_document) if source_document && !source_document.equal?(@document)
|
|
63
60
|
adopted
|
|
64
61
|
end
|
|
65
62
|
|
|
66
|
-
# Move each live wrapper for a descendant of `src_root` onto the matching
|
|
67
|
-
# node in `dst_root` (the imported copy), pruning it from the source
|
|
68
|
-
# document. `include_root` also re-binds the root pair — #adopt re-binds
|
|
69
|
-
# its own root wrapper by hand, but a template's content fragment has no
|
|
70
|
-
# such caller.
|
|
71
|
-
def reseat_descendant_wrappers(src_root, dst_root, src_doc, include_root: false)
|
|
72
|
-
return unless src_doc.respond_to?(:__internal_peek_wrapper__)
|
|
73
|
-
|
|
74
|
-
each_node_pair(src_root, dst_root) do |orig, copy|
|
|
75
|
-
next if orig.equal?(src_root) && !include_root
|
|
76
|
-
|
|
77
|
-
reseat_wrapper(orig, copy, src_doc)
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# HTML: adopting a `<template>` adopts its template contents
|
|
82
|
-
# DocumentFragment along with it — the SAME fragment object, so
|
|
83
|
-
# `template.content` keeps both its identity and its children across the
|
|
84
|
-
# move. The contents are not in the template's child list, so neither the
|
|
85
|
-
# backend's adopt nor the descendant walk above ever reaches them; without
|
|
86
|
-
# this the adopted template comes out empty. Recurses, since a template's
|
|
87
|
-
# contents can hold further templates.
|
|
88
|
-
# Spec: https://html.spec.whatwg.org/#the-template-element (adopting steps)
|
|
89
|
-
def adopt_template_contents(src_root, dst_root, src_doc)
|
|
90
|
-
return if src_doc.nil? || src_doc.equal?(@document)
|
|
91
|
-
return unless src_doc.respond_to?(:__internal_template_registry__)
|
|
92
|
-
|
|
93
|
-
src_registry = src_doc.__internal_template_registry__
|
|
94
|
-
each_node_pair(src_root, dst_root) do |orig, copy|
|
|
95
|
-
src_frag = src_registry.raw_fragment_for(orig)
|
|
96
|
-
next unless src_frag
|
|
97
|
-
|
|
98
|
-
adopt_one_template_content(src_frag, copy, src_doc)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
63
|
private
|
|
103
64
|
|
|
104
65
|
def backend_doc = @document.backend_doc
|
|
@@ -108,15 +69,13 @@ module Dommy
|
|
|
108
69
|
# publicId / systemId (as createDocument does), then re-bind the caller's
|
|
109
70
|
# wrapper onto the new node so JS identity survives the move.
|
|
110
71
|
def adopt_doctype(node, src)
|
|
111
|
-
adopted =
|
|
112
|
-
Backend.create_document_type(node.name, node.public_id, node.system_id, backend_doc)
|
|
113
|
-
rescue StandardError
|
|
114
|
-
nil
|
|
115
|
-
end
|
|
116
|
-
return node unless adopted
|
|
117
|
-
|
|
72
|
+
adopted = Backend.create_document_type(node.name, node.public_id, node.system_id, backend_doc)
|
|
118
73
|
reseat_known_wrapper(node, src, adopted, node.document)
|
|
119
74
|
node
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
# Returning the node unchanged would read as a successful adoption while
|
|
77
|
+
# its ownerDocument never moved, so the caller hears about it instead.
|
|
78
|
+
raise DOMException::NotSupportedError, "Cannot adopt this doctype: #{e.message}"
|
|
120
79
|
end
|
|
121
80
|
|
|
122
81
|
# Hand the detached source to the backend, which returns the node now
|
|
@@ -127,42 +86,90 @@ module Dommy
|
|
|
127
86
|
adopted = Backend.adopt(src, backend_doc)
|
|
128
87
|
|
|
129
88
|
reseat_known_wrapper(node, src, adopted, src_doc)
|
|
130
|
-
|
|
131
|
-
# descendant wrapper must be re-bound onto its corresponding copy —
|
|
132
|
-
# otherwise it stays bound to the old document. Import preserves document
|
|
133
|
-
# order, so walk both subtrees in lockstep.
|
|
134
|
-
reseat_descendant_wrappers(src, adopted, src_doc)
|
|
135
|
-
adopt_template_contents(src, adopted, src_doc)
|
|
89
|
+
carry_over(src, adopted, src_doc)
|
|
136
90
|
node
|
|
137
91
|
end
|
|
138
92
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
93
|
+
# A deep adopt imports a fresh copy of the whole subtree, so each live
|
|
94
|
+
# wrapper in it must be re-bound onto its copy — otherwise it stays bound
|
|
95
|
+
# to the old document. Import preserves document order, so the two
|
|
96
|
+
# subtrees are walked together.
|
|
97
|
+
def carry_over(orig, copy, src_doc)
|
|
98
|
+
reseat_wrapper(orig, copy, src_doc)
|
|
99
|
+
if registry.template_node?(orig)
|
|
100
|
+
carry_over_template(orig, copy, src_doc)
|
|
101
|
+
elsif orig.respond_to?(:children)
|
|
102
|
+
carry_over_each(orig.children.to_a, copy.children.to_a, src_doc)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# A length mismatch means the copies are not the import of the originals,
|
|
107
|
+
# so nothing is paired at all rather than paired wrongly.
|
|
108
|
+
def carry_over_each(origs, copies, src_doc)
|
|
109
|
+
return unless origs.length == copies.length
|
|
110
|
+
|
|
111
|
+
origs.zip(copies).each { |orig, copy| carry_over(orig, copy, src_doc) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# HTML: adopting a `<template>` adopts its template contents
|
|
115
|
+
# DocumentFragment along with it — the SAME fragment object, so
|
|
116
|
+
# `template.content` keeps both its identity and its children across the
|
|
117
|
+
# move. An HTML document's contents are the backend's own, which its
|
|
118
|
+
# import copies as contents into an HTML document and as the leading
|
|
119
|
+
# children into an XML one (which has no contents); an XML document's
|
|
120
|
+
# live apart from the tree and move here one by one. Whatever the import
|
|
121
|
+
# made of them, they end up as the destination keeps contents, and the
|
|
122
|
+
# template's own children as its children.
|
|
123
|
+
# Spec: https://html.spec.whatwg.org/#the-template-element (adopting steps)
|
|
124
|
+
def carry_over_template(orig, copy, src_doc)
|
|
125
|
+
src_registry = src_doc.__internal_template_registry__
|
|
126
|
+
src_frag = src_registry.existing_contents(orig)
|
|
127
|
+
contents = src_frag ? src_frag.children.to_a : []
|
|
128
|
+
children = orig.children.to_a
|
|
129
|
+
dst_frag = registry.contents(copy)
|
|
130
|
+
from_html = !Backend.template_contents(orig).nil?
|
|
131
|
+
to_html = !Backend.template_contents(copy).nil?
|
|
132
|
+
|
|
133
|
+
if from_html && to_html
|
|
134
|
+
carry_over_each(contents, dst_frag.children.to_a, src_doc)
|
|
135
|
+
carry_over_each(children, copy.children.to_a, src_doc)
|
|
136
|
+
elsif from_html
|
|
137
|
+
copied = copy.children.to_a
|
|
138
|
+
copied.first(contents.length).each { |node| move_node(node, dst_frag) }
|
|
139
|
+
carry_over_each(contents, copied.first(contents.length), src_doc)
|
|
140
|
+
carry_over_each(children, copied.drop(contents.length), src_doc)
|
|
141
|
+
else
|
|
142
|
+
if to_html
|
|
143
|
+
# The import made the XML template's own children the contents.
|
|
144
|
+
copied = dst_frag.children.to_a
|
|
145
|
+
copied.each { |node| move_node(node, copy) }
|
|
146
|
+
end
|
|
147
|
+
carry_over_each(children, copy.children.to_a, src_doc)
|
|
148
|
+
adopt_detached_contents(contents, dst_frag, src_doc)
|
|
149
|
+
src_registry.release(orig)
|
|
150
150
|
end
|
|
151
|
-
|
|
152
|
-
reseat_wrapper(src_frag, frag, src_doc)
|
|
151
|
+
reseat_wrapper(src_frag, dst_frag, src_doc) if src_frag
|
|
153
152
|
end
|
|
154
153
|
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
# An XML document's contents, which no import reached, moved one by one.
|
|
155
|
+
def adopt_detached_contents(nodes, dst_frag, src_doc)
|
|
156
|
+
nodes.each do |node|
|
|
157
|
+
moved = Backend.adopt(node, backend_doc)
|
|
158
|
+
carry_over(node, moved, src_doc)
|
|
159
|
+
dst_frag.add_child(moved)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
# Put a copy the import placed where the destination does not keep it
|
|
164
|
+
# into place. Not a DOM mutation — the node is where the adopt means it to
|
|
165
|
+
# be — so a raw unlink.
|
|
166
|
+
def move_node(node, parent)
|
|
167
|
+
node.unlink
|
|
168
|
+
parent.add_child(node)
|
|
164
169
|
end
|
|
165
170
|
|
|
171
|
+
def registry = @document.__internal_template_registry__
|
|
172
|
+
|
|
166
173
|
# Look up the live wrapper for `orig` in the source document, if it has
|
|
167
174
|
# one, and move it onto `copy`.
|
|
168
175
|
def reseat_wrapper(orig, copy, src_doc)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dommy
|
|
4
|
+
module Internal
|
|
5
|
+
# Document's createElement / createTextNode / createAttribute family: mint a
|
|
6
|
+
# backend node, then hand back the wrapper for it.
|
|
7
|
+
#
|
|
8
|
+
# A factory is not a cache. These lived in NodeWrapperCache, which made that
|
|
9
|
+
# class's name describe about a third of it; it keeps the identity table the
|
|
10
|
+
# wrappers come from, and this asks it for them.
|
|
11
|
+
class NodeFactory
|
|
12
|
+
def initialize(document, wrappers)
|
|
13
|
+
@document = document
|
|
14
|
+
@wrappers = wrappers
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create_element(name)
|
|
18
|
+
str = domstring(name)
|
|
19
|
+
raise DOMException::InvalidCharacterError, "name must not be empty" if str.empty?
|
|
20
|
+
raise DOMException::InvalidCharacterError, "invalid element name: #{str.inspect}" unless Namespaces.valid_element_local_name?(str)
|
|
21
|
+
|
|
22
|
+
# WHATWG createElement: lowercase (ASCII) the name only in an HTML
|
|
23
|
+
# document; the namespace is the HTML namespace for HTML/XHTML documents
|
|
24
|
+
# and null for a non-XHTML XML document. Record the metadata so the
|
|
25
|
+
# element's localName/tagName/namespaceURI getters report it faithfully
|
|
26
|
+
# (in particular case preservation for XML/XHTML).
|
|
27
|
+
if @document.html_document?
|
|
28
|
+
local = str.downcase(:ascii)
|
|
29
|
+
namespace = Element::HTML_NAMESPACE
|
|
30
|
+
else
|
|
31
|
+
local = str
|
|
32
|
+
namespace = @document.content_type == "application/xhtml+xml" ? Element::HTML_NAMESPACE : nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# createElement validates against the XML *Name* production, which is
|
|
36
|
+
# looser than the QName an XML backend insists on: ":", "foo:", "f::oo"
|
|
37
|
+
# and a local part with a combining char are all valid element names the
|
|
38
|
+
# backend would reject. The loose creator builds those verbatim; anything
|
|
39
|
+
# it (or the strict path) still refuses is an InvalidCharacterError.
|
|
40
|
+
node =
|
|
41
|
+
begin
|
|
42
|
+
Backend.create_element_loose(local, nil, local, namespace, @document.backend_doc) ||
|
|
43
|
+
Backend.create_element(local, @document.backend_doc)
|
|
44
|
+
rescue ArgumentError
|
|
45
|
+
raise DOMException::InvalidCharacterError, "invalid element name: #{str.inspect}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
wrapper = @wrappers.wrap(node)
|
|
49
|
+
wrapper.__internal_set_namespace__(namespace, nil, local, local)
|
|
50
|
+
@document.__internal_note_namespaced_element__(namespace, nil)
|
|
51
|
+
wrapper
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def create_text_node(text)
|
|
55
|
+
@wrappers.wrap(Backend.create_text(text.to_s, @document.backend_doc))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def create_cdata_section(text)
|
|
59
|
+
@wrappers.wrap(Backend.create_cdata(text.to_s, @document.backend_doc))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def create_comment(text)
|
|
63
|
+
@wrappers.wrap(Backend.create_comment(text.to_s, @document.backend_doc))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# WHATWG Document.createProcessingInstruction: the target must be a valid
|
|
67
|
+
# XML Name and the data must not contain the PI close delimiter "?>", else
|
|
68
|
+
# InvalidCharacterError. The result is a real backend-backed PI node.
|
|
69
|
+
def create_processing_instruction(target, data)
|
|
70
|
+
t = domstring(target)
|
|
71
|
+
d = domstring(data)
|
|
72
|
+
raise DOMException::InvalidCharacterError, "invalid processing instruction target: #{t.inspect}" unless t.match?(Namespaces::NAME)
|
|
73
|
+
raise DOMException::InvalidCharacterError, "processing instruction data must not contain '?>'" if d.include?("?>")
|
|
74
|
+
|
|
75
|
+
@wrappers.wrap(Backend.create_processing_instruction(t, d, @document.backend_doc))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def create_document_fragment
|
|
79
|
+
@wrappers.wrap(Parser.fragment("", owner_doc: @document.backend_doc))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def create_attribute(name)
|
|
83
|
+
str = domstring(name)
|
|
84
|
+
raise DOMException::InvalidCharacterError, "name must not be empty" if str.empty?
|
|
85
|
+
raise DOMException::InvalidCharacterError, "invalid attribute name: #{str.inspect}" unless Namespaces.valid_attribute_local_name?(str)
|
|
86
|
+
|
|
87
|
+
# WHATWG createAttribute: an HTML document lower-cases the name (an XML
|
|
88
|
+
# document preserves it). Attr.new no longer folds case, so do it here.
|
|
89
|
+
str = str.downcase if @document.html_document?
|
|
90
|
+
Attr.new(str, document: @document)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def create_attribute_ns(namespace_uri, qualified_name)
|
|
94
|
+
namespace_uri = nil if namespace_uri.equal?(Bridge::UNDEFINED)
|
|
95
|
+
qualified_name = domstring(qualified_name)
|
|
96
|
+
ns, prefix, local = Namespaces.validate_and_extract(namespace_uri, qualified_name)
|
|
97
|
+
Attr.new(qualified_name, namespace_uri: ns, prefix: prefix, local_name: local, document: @document)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def create_element_ns(namespace_uri, qualified_name)
|
|
101
|
+
# WHATWG "validate and extract": QName-validate the qualifiedName
|
|
102
|
+
# (InvalidCharacterError) and apply the prefix/namespace rules
|
|
103
|
+
# (NamespaceError), then build the element with its prefix bound.
|
|
104
|
+
# namespace is nullable (undefined → null); qualifiedName is a plain
|
|
105
|
+
# DOMString (undefined → "undefined", null → "null").
|
|
106
|
+
namespace_uri = nil if namespace_uri.equal?(Bridge::UNDEFINED)
|
|
107
|
+
qualified_name = domstring(qualified_name)
|
|
108
|
+
ns, prefix, local = Namespaces.validate_and_extract(namespace_uri, qualified_name, context: :element)
|
|
109
|
+
|
|
110
|
+
# An XML backend rejects some DOM-valid qualified names (an invalid char
|
|
111
|
+
# in the local part, which DOM permits): the loose creator builds them
|
|
112
|
+
# verbatim. An HTML backend builds the element in its namespace. A
|
|
113
|
+
# genuinely invalid name it (or the strict path) rejects with an
|
|
114
|
+
# ArgumentError becomes an InvalidCharacterError, per DOM.
|
|
115
|
+
el =
|
|
116
|
+
begin
|
|
117
|
+
Backend.create_element_loose(qualified_name, prefix, local, ns, @document.backend_doc) ||
|
|
118
|
+
Backend.create_element_ns(ns, qualified_name, @document.backend_doc) ||
|
|
119
|
+
Backend.create_element(qualified_name, @document.backend_doc)
|
|
120
|
+
rescue ArgumentError
|
|
121
|
+
raise DOMException::InvalidCharacterError, "'#{qualified_name}' is not a valid element name"
|
|
122
|
+
end
|
|
123
|
+
Backend.add_namespace_definition(el, prefix, ns) if ns
|
|
124
|
+
|
|
125
|
+
wrapper = @wrappers.build_element_wrapper(el, namespace: ns, local_name: local)
|
|
126
|
+
wrapper.__internal_set_namespace__(ns, prefix, local, qualified_name)
|
|
127
|
+
@document.__internal_note_namespaced_element__(ns, prefix)
|
|
128
|
+
wrapper
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Wrap a freshly-cloned backend element whose original was built via
|
|
132
|
+
# createElementNS: route the wrapper class by the known local name (the
|
|
133
|
+
# backend node name may be the full qualified name, e.g. "foo:div", which
|
|
134
|
+
# would otherwise resolve to HTMLUnknownElement) and reapply its namespace
|
|
135
|
+
# metadata (namespaceURI / prefix / localName / tagName).
|
|
136
|
+
def wrap_cloned_element_ns(node, namespace, prefix, local, qualified_name)
|
|
137
|
+
@wrappers.reset_wrapper(node)
|
|
138
|
+
wrapper = @wrappers.build_element_wrapper(node, namespace: namespace, local_name: local)
|
|
139
|
+
wrapper.__internal_set_namespace__(namespace, prefix, local, qualified_name)
|
|
140
|
+
wrapper
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Query methods
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# WebIDL DOMString coercion for a name/qualifiedName argument: JS
|
|
148
|
+
# `undefined` → "undefined", JS `null` (Ruby nil) → "null", else #to_s.
|
|
149
|
+
def domstring(value)
|
|
150
|
+
return "undefined" if value.equal?(Bridge::UNDEFINED)
|
|
151
|
+
return "null" if value.nil?
|
|
152
|
+
|
|
153
|
+
value.to_s
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dommy
|
|
4
|
+
module Internal
|
|
5
|
+
# Whether two DOM references stand for the same node.
|
|
6
|
+
#
|
|
7
|
+
# Comparing the backend nodes with `==` is not it: a backend may mint a
|
|
8
|
+
# fresh wrapper per traversal, and one that frees nodes may hand the same
|
|
9
|
+
# pointer to a later node. Backend.identity_key is what each backend says
|
|
10
|
+
# its stable key is (see NodeWrapperCache#identity_key), so that is what
|
|
11
|
+
# this asks — in one place, because three callers used to answer it three
|
|
12
|
+
# ways.
|
|
13
|
+
module NodeIdentity
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# `first` and `second` may be wrappers or backend nodes, in any mix.
|
|
17
|
+
def same_node?(first, second)
|
|
18
|
+
return false unless first && second
|
|
19
|
+
|
|
20
|
+
left = key_for(first)
|
|
21
|
+
right = key_for(second)
|
|
22
|
+
!left.nil? && left == right
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def key_for(node)
|
|
26
|
+
backend = node.respond_to?(:__dommy_backend_node__) ? node.__dommy_backend_node__ : node
|
|
27
|
+
backend && Backend.identity_key(backend)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|