dommy 0.8.0 → 0.9.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 +3 -3
- data/lib/dommy/animation.rb +4 -0
- data/lib/dommy/attr.rb +11 -5
- data/lib/dommy/backend/makiri_adapter.rb +330 -0
- data/lib/dommy/backend.rb +114 -33
- data/lib/dommy/blob.rb +2 -0
- data/lib/dommy/bridge.rb +11 -0
- data/lib/dommy/browser.rb +217 -0
- data/lib/dommy/compression_streams.rb +4 -0
- data/lib/dommy/crypto.rb +4 -0
- data/lib/dommy/css.rb +487 -50
- data/lib/dommy/custom_elements.rb +2 -2
- data/lib/dommy/data_transfer.rb +2 -0
- data/lib/dommy/data_uri.rb +35 -0
- data/lib/dommy/deferred_response.rb +59 -0
- data/lib/dommy/document.rb +386 -228
- data/lib/dommy/dom_exception.rb +2 -0
- data/lib/dommy/dom_parser.rb +7 -17
- data/lib/dommy/element.rb +502 -155
- data/lib/dommy/event.rb +240 -9
- data/lib/dommy/fetch.rb +152 -34
- data/lib/dommy/form_data.rb +2 -0
- data/lib/dommy/history.rb +2 -0
- data/lib/dommy/html_canvas_element.rb +230 -0
- data/lib/dommy/html_collection.rb +5 -6
- data/lib/dommy/html_elements.rb +304 -27
- data/lib/dommy/interaction/debug.rb +35 -0
- data/lib/dommy/interaction/dom_summary.rb +131 -0
- data/lib/dommy/interaction/driver.rb +244 -0
- data/lib/dommy/interaction/event_synthesis.rb +56 -0
- data/lib/dommy/interaction/field_interactor.rb +117 -0
- data/lib/dommy/interaction/form_submission.rb +268 -0
- data/lib/dommy/interaction/locator.rb +158 -0
- data/lib/dommy/interaction/role_query.rb +58 -0
- data/lib/dommy/interaction.rb +32 -0
- data/lib/dommy/internal/accessibility_tree.rb +215 -0
- data/lib/dommy/internal/accessible_description.rb +38 -0
- data/lib/dommy/internal/accessible_name.rb +301 -0
- data/lib/dommy/internal/aria_role.rb +252 -0
- data/lib/dommy/internal/aria_snapshot.rb +64 -0
- data/lib/dommy/internal/aria_state.rb +151 -0
- data/lib/dommy/internal/css/calc.rb +242 -0
- data/lib/dommy/internal/css/cascade.rb +430 -0
- data/lib/dommy/internal/css/color.rb +381 -0
- data/lib/dommy/internal/css/computed_style_declaration.rb +130 -0
- data/lib/dommy/internal/css/counters.rb +227 -0
- data/lib/dommy/internal/css/custom_properties.rb +183 -0
- data/lib/dommy/internal/css/media_query.rb +302 -0
- data/lib/dommy/internal/css/parser.rb +265 -0
- data/lib/dommy/internal/css/property_registry.rb +512 -0
- data/lib/dommy/internal/css/rule_index.rb +494 -0
- data/lib/dommy/internal/css/supports.rb +158 -0
- data/lib/dommy/internal/css/ua_stylesheet.rb +53 -0
- data/lib/dommy/internal/css_pseudo_handlers.rb +283 -42
- data/lib/dommy/internal/css_rule_text.rb +160 -0
- data/lib/dommy/internal/dom_matching.rb +80 -9
- data/lib/dommy/internal/element_matching.rb +109 -0
- data/lib/dommy/internal/global_functions.rb +33 -0
- data/lib/dommy/internal/mutation_coordinator.rb +95 -4
- data/lib/dommy/internal/namespaces.rb +49 -5
- data/lib/dommy/internal/node_wrapper_cache.rb +163 -26
- data/lib/dommy/internal/parent_node.rb +82 -5
- data/lib/dommy/internal/selector_ast.rb +124 -0
- data/lib/dommy/internal/selector_index.rb +146 -0
- data/lib/dommy/internal/selector_matcher.rb +756 -0
- data/lib/dommy/internal/selector_parser.rb +283 -131
- data/lib/dommy/internal/shadow_root_registry.rb +9 -2
- data/lib/dommy/internal/template_content_registry.rb +26 -18
- data/lib/dommy/internal/xml_serialization.rb +344 -0
- data/lib/dommy/intersection_observer.rb +2 -0
- data/lib/dommy/js/bridge_conformance.rb +80 -0
- data/lib/dommy/js/constructor_resolver.rb +44 -0
- data/lib/dommy/js/custom_element_bridge.rb +90 -0
- data/lib/dommy/js/dom_interfaces.rb +162 -0
- data/lib/dommy/js/handle_table.rb +60 -0
- data/lib/dommy/js/host_bridge.rb +517 -0
- data/lib/dommy/js/host_runtime.js +1495 -0
- data/lib/dommy/js/import_map.rb +58 -0
- data/lib/dommy/js/marshaller.rb +240 -0
- data/lib/dommy/js/module_loader.rb +99 -0
- data/lib/dommy/js/observable_runtime.js +742 -0
- data/lib/dommy/js/runtime.rb +115 -0
- data/lib/dommy/js/script_boot.rb +221 -0
- data/lib/dommy/js/wire_tags.rb +62 -0
- data/lib/dommy/location.rb +2 -0
- data/lib/dommy/media_query_list.rb +50 -14
- data/lib/dommy/message_channel.rb +22 -6
- data/lib/dommy/minitest/assertions.rb +27 -0
- data/lib/dommy/mutation_observer.rb +89 -4
- data/lib/dommy/navigator.rb +34 -2
- data/lib/dommy/node.rb +24 -14
- data/lib/dommy/notification.rb +2 -0
- data/lib/dommy/parser.rb +1 -1
- data/lib/dommy/performance.rb +21 -1
- data/lib/dommy/promise.rb +94 -10
- data/lib/dommy/range.rb +173 -31
- data/lib/dommy/resources.rb +178 -0
- data/lib/dommy/rspec/capy_style_matchers.rb +126 -0
- data/lib/dommy/scheduler.rb +149 -13
- data/lib/dommy/screen.rb +91 -0
- data/lib/dommy/shadow_root.rb +76 -13
- data/lib/dommy/storage.rb +2 -1
- data/lib/dommy/streams.rb +6 -0
- data/lib/dommy/text_codec.rb +7 -1
- data/lib/dommy/tree_walker.rb +33 -10
- data/lib/dommy/url.rb +13 -1
- data/lib/dommy/version.rb +1 -1
- data/lib/dommy/window.rb +199 -11
- data/lib/dommy/worker.rb +8 -4
- data/lib/dommy/xml_http_request.rb +47 -6
- data/lib/dommy.rb +36 -1
- metadata +96 -10
- data/lib/dommy/backend/nokogiri_adapter.rb +0 -127
- data/lib/dommy/backend/nokolexbor_adapter.rb +0 -117
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dommy
|
|
4
|
+
module Internal
|
|
5
|
+
# A by-id / by-class / by-tag index over a document's backend element tree,
|
|
6
|
+
# in document order. It turns a fast_query pre-filter from an O(tree) walk
|
|
7
|
+
# ("visit every node, test it") into an O(matches) lookup ("here are the nodes
|
|
8
|
+
# with class X"). Built once and memoized per DOM generation
|
|
9
|
+
# (Document#style_generation) — a mutation invalidates it, and the NEXT query
|
|
10
|
+
# rebuilds it, so on a page that fires many queries between mutations (jQuery
|
|
11
|
+
# `$.find` storms) it is a big win, while a build costs the same single tree
|
|
12
|
+
# walk it replaces, so it never loses to the plain walk.
|
|
13
|
+
#
|
|
14
|
+
# Works for ELEMENT-scoped queries too (jQuery's `.find` is `el.querySelectorAll`):
|
|
15
|
+
# each element gets a pre-order interval [enter, exit] during the build, so a
|
|
16
|
+
# candidate is inside a scope element's subtree iff its `enter` falls in the
|
|
17
|
+
# scope's (enter, exit] — an O(candidates) range test, no per-node ancestor
|
|
18
|
+
# walk. Candidates are stored as [enter, node] in document order.
|
|
19
|
+
#
|
|
20
|
+
# Holds backend nodes (never wrapped); the caller wraps only candidates and
|
|
21
|
+
# still runs the authoritative #matches?, so the index need only be a SUPERSET
|
|
22
|
+
# (tag matching is case-insensitive here; exact case / namespace stays
|
|
23
|
+
# #matches?'s job).
|
|
24
|
+
class SelectorIndex
|
|
25
|
+
EMPTY = [].freeze
|
|
26
|
+
|
|
27
|
+
def self.build(backend_doc)
|
|
28
|
+
new.tap { |index| index.populate(backend_doc) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize
|
|
32
|
+
@by_id = {}
|
|
33
|
+
@by_class = {}
|
|
34
|
+
@by_tag = {}
|
|
35
|
+
@extent = {} # node pointer_id => [enter, exit] pre-order interval
|
|
36
|
+
@counter = 0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Walk the backend element tree in document order, numbering each element and
|
|
40
|
+
# recording it under its id / classes / tag.
|
|
41
|
+
def populate(node)
|
|
42
|
+
child = node.first_element_child
|
|
43
|
+
while child
|
|
44
|
+
enter = (@counter += 1)
|
|
45
|
+
record(child, enter)
|
|
46
|
+
populate(child)
|
|
47
|
+
@extent[child.pointer_id] = [enter, @counter]
|
|
48
|
+
child = child.next_element
|
|
49
|
+
end
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Backend nodes for an indexable pre-filter ([:id|:class|:type, value]),
|
|
54
|
+
# optionally restricted to `scope_node`'s subtree; nil when the index can't
|
|
55
|
+
# serve it (a non-indexable :attr, or a scope it doesn't know) so the caller
|
|
56
|
+
# falls back to the walk.
|
|
57
|
+
def candidates(prefilter, scope_node = nil)
|
|
58
|
+
entries = entries_for(prefilter)
|
|
59
|
+
return nil unless entries
|
|
60
|
+
|
|
61
|
+
if scope_node.nil?
|
|
62
|
+
entries.map { |_enter, node| node }
|
|
63
|
+
else
|
|
64
|
+
ext = @extent[scope_node.pointer_id]
|
|
65
|
+
return nil unless ext # detached / unknown scope -> let the caller walk
|
|
66
|
+
|
|
67
|
+
lo, hi = ext
|
|
68
|
+
entries.each_with_object([]) { |(enter, node), out| out << node if enter > lo && enter <= hi }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The pre-order `enter` number of a backend node (its position), or nil if
|
|
73
|
+
# the node isn't in this index (detached / unknown).
|
|
74
|
+
def enter_of(node)
|
|
75
|
+
ext = @extent[node.pointer_id]
|
|
76
|
+
ext && ext[0]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Is there an indexed element matching `prefilter` whose subtree contains
|
|
80
|
+
# the element at pre-order position `enter_l` — i.e. does that element have
|
|
81
|
+
# an ANCESTOR matching the prefilter? Answered in O(log) via a prefix-max of
|
|
82
|
+
# the matching elements' `exit` over their (sorted) `enter`s: an interval
|
|
83
|
+
# [enter, exit] contains enter_l iff enter < enter_l <= exit, and by the
|
|
84
|
+
# pre-order property that element is then an ancestor. Used to settle a
|
|
85
|
+
# descendant combinator's leftmost compound without walking ancestors.
|
|
86
|
+
def any_ancestor?(prefilter, enter_l)
|
|
87
|
+
enters, max_exits = prefix_max_exit(prefilter)
|
|
88
|
+
return false unless enters
|
|
89
|
+
|
|
90
|
+
# last index with enters[i] < enter_l
|
|
91
|
+
gte = enters.bsearch_index { |enter| enter >= enter_l } || enters.length
|
|
92
|
+
i = gte - 1
|
|
93
|
+
i >= 0 && max_exits[i] >= enter_l
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
# [sorted enters, running-max exits] for an indexable prefilter, memoized
|
|
99
|
+
# per prefilter for this generation, or nil when nothing matches. The first
|
|
100
|
+
# call is O(matches) (touches only the matching elements, never the tree);
|
|
101
|
+
# later calls reuse it.
|
|
102
|
+
def prefix_max_exit(prefilter)
|
|
103
|
+
(@prefix_cache ||= {})[prefilter] ||= begin
|
|
104
|
+
entries = entries_for(prefilter)
|
|
105
|
+
if entries.nil? || entries.empty?
|
|
106
|
+
[nil, nil]
|
|
107
|
+
else
|
|
108
|
+
enters = []
|
|
109
|
+
max_exits = []
|
|
110
|
+
running = -1
|
|
111
|
+
entries.each do |enter, node|
|
|
112
|
+
ext = @extent[node.pointer_id]
|
|
113
|
+
exit_pos = ext ? ext[1] : enter
|
|
114
|
+
running = exit_pos if exit_pos > running
|
|
115
|
+
enters << enter
|
|
116
|
+
max_exits << running
|
|
117
|
+
end
|
|
118
|
+
[enters, max_exits]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def entries_for(prefilter)
|
|
124
|
+
kind, value = prefilter
|
|
125
|
+
case kind
|
|
126
|
+
when :id then @by_id[value] || EMPTY
|
|
127
|
+
when :class then @by_class[value] || EMPTY
|
|
128
|
+
when :type then @by_tag[value.to_s.downcase] || EMPTY
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def record(bnode, enter)
|
|
133
|
+
name = bnode.name
|
|
134
|
+
(@by_tag[name.downcase] ||= []) << [enter, bnode] if name && !name.empty?
|
|
135
|
+
|
|
136
|
+
id = bnode["id"]
|
|
137
|
+
(@by_id[id] ||= []) << [enter, bnode] if id && !id.empty?
|
|
138
|
+
|
|
139
|
+
klass = bnode["class"]
|
|
140
|
+
return if klass.nil? || klass.empty?
|
|
141
|
+
|
|
142
|
+
klass.split(/\s+/).each { |token| (@by_class[token] ||= []) << [enter, bnode] unless token.empty? }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|