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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/lib/dommy/animation.rb +4 -0
  4. data/lib/dommy/attr.rb +11 -5
  5. data/lib/dommy/backend/makiri_adapter.rb +330 -0
  6. data/lib/dommy/backend.rb +114 -33
  7. data/lib/dommy/blob.rb +2 -0
  8. data/lib/dommy/bridge.rb +11 -0
  9. data/lib/dommy/browser.rb +217 -0
  10. data/lib/dommy/compression_streams.rb +4 -0
  11. data/lib/dommy/crypto.rb +4 -0
  12. data/lib/dommy/css.rb +487 -50
  13. data/lib/dommy/custom_elements.rb +2 -2
  14. data/lib/dommy/data_transfer.rb +2 -0
  15. data/lib/dommy/data_uri.rb +35 -0
  16. data/lib/dommy/deferred_response.rb +59 -0
  17. data/lib/dommy/document.rb +386 -228
  18. data/lib/dommy/dom_exception.rb +2 -0
  19. data/lib/dommy/dom_parser.rb +7 -17
  20. data/lib/dommy/element.rb +502 -155
  21. data/lib/dommy/event.rb +240 -9
  22. data/lib/dommy/fetch.rb +152 -34
  23. data/lib/dommy/form_data.rb +2 -0
  24. data/lib/dommy/history.rb +2 -0
  25. data/lib/dommy/html_canvas_element.rb +230 -0
  26. data/lib/dommy/html_collection.rb +5 -6
  27. data/lib/dommy/html_elements.rb +304 -27
  28. data/lib/dommy/interaction/debug.rb +35 -0
  29. data/lib/dommy/interaction/dom_summary.rb +131 -0
  30. data/lib/dommy/interaction/driver.rb +244 -0
  31. data/lib/dommy/interaction/event_synthesis.rb +56 -0
  32. data/lib/dommy/interaction/field_interactor.rb +117 -0
  33. data/lib/dommy/interaction/form_submission.rb +268 -0
  34. data/lib/dommy/interaction/locator.rb +158 -0
  35. data/lib/dommy/interaction/role_query.rb +58 -0
  36. data/lib/dommy/interaction.rb +32 -0
  37. data/lib/dommy/internal/accessibility_tree.rb +215 -0
  38. data/lib/dommy/internal/accessible_description.rb +38 -0
  39. data/lib/dommy/internal/accessible_name.rb +301 -0
  40. data/lib/dommy/internal/aria_role.rb +252 -0
  41. data/lib/dommy/internal/aria_snapshot.rb +64 -0
  42. data/lib/dommy/internal/aria_state.rb +151 -0
  43. data/lib/dommy/internal/css/calc.rb +242 -0
  44. data/lib/dommy/internal/css/cascade.rb +430 -0
  45. data/lib/dommy/internal/css/color.rb +381 -0
  46. data/lib/dommy/internal/css/computed_style_declaration.rb +130 -0
  47. data/lib/dommy/internal/css/counters.rb +227 -0
  48. data/lib/dommy/internal/css/custom_properties.rb +183 -0
  49. data/lib/dommy/internal/css/media_query.rb +302 -0
  50. data/lib/dommy/internal/css/parser.rb +265 -0
  51. data/lib/dommy/internal/css/property_registry.rb +512 -0
  52. data/lib/dommy/internal/css/rule_index.rb +494 -0
  53. data/lib/dommy/internal/css/supports.rb +158 -0
  54. data/lib/dommy/internal/css/ua_stylesheet.rb +53 -0
  55. data/lib/dommy/internal/css_pseudo_handlers.rb +283 -42
  56. data/lib/dommy/internal/css_rule_text.rb +160 -0
  57. data/lib/dommy/internal/dom_matching.rb +80 -9
  58. data/lib/dommy/internal/element_matching.rb +109 -0
  59. data/lib/dommy/internal/global_functions.rb +33 -0
  60. data/lib/dommy/internal/mutation_coordinator.rb +95 -4
  61. data/lib/dommy/internal/namespaces.rb +49 -5
  62. data/lib/dommy/internal/node_wrapper_cache.rb +163 -26
  63. data/lib/dommy/internal/parent_node.rb +82 -5
  64. data/lib/dommy/internal/selector_ast.rb +124 -0
  65. data/lib/dommy/internal/selector_index.rb +146 -0
  66. data/lib/dommy/internal/selector_matcher.rb +756 -0
  67. data/lib/dommy/internal/selector_parser.rb +283 -131
  68. data/lib/dommy/internal/shadow_root_registry.rb +9 -2
  69. data/lib/dommy/internal/template_content_registry.rb +26 -18
  70. data/lib/dommy/internal/xml_serialization.rb +344 -0
  71. data/lib/dommy/intersection_observer.rb +2 -0
  72. data/lib/dommy/js/bridge_conformance.rb +80 -0
  73. data/lib/dommy/js/constructor_resolver.rb +44 -0
  74. data/lib/dommy/js/custom_element_bridge.rb +90 -0
  75. data/lib/dommy/js/dom_interfaces.rb +162 -0
  76. data/lib/dommy/js/handle_table.rb +60 -0
  77. data/lib/dommy/js/host_bridge.rb +517 -0
  78. data/lib/dommy/js/host_runtime.js +1495 -0
  79. data/lib/dommy/js/import_map.rb +58 -0
  80. data/lib/dommy/js/marshaller.rb +240 -0
  81. data/lib/dommy/js/module_loader.rb +99 -0
  82. data/lib/dommy/js/observable_runtime.js +742 -0
  83. data/lib/dommy/js/runtime.rb +115 -0
  84. data/lib/dommy/js/script_boot.rb +221 -0
  85. data/lib/dommy/js/wire_tags.rb +62 -0
  86. data/lib/dommy/location.rb +2 -0
  87. data/lib/dommy/media_query_list.rb +50 -14
  88. data/lib/dommy/message_channel.rb +22 -6
  89. data/lib/dommy/minitest/assertions.rb +27 -0
  90. data/lib/dommy/mutation_observer.rb +89 -4
  91. data/lib/dommy/navigator.rb +34 -2
  92. data/lib/dommy/node.rb +24 -14
  93. data/lib/dommy/notification.rb +2 -0
  94. data/lib/dommy/parser.rb +1 -1
  95. data/lib/dommy/performance.rb +21 -1
  96. data/lib/dommy/promise.rb +94 -10
  97. data/lib/dommy/range.rb +173 -31
  98. data/lib/dommy/resources.rb +178 -0
  99. data/lib/dommy/rspec/capy_style_matchers.rb +126 -0
  100. data/lib/dommy/scheduler.rb +149 -13
  101. data/lib/dommy/screen.rb +91 -0
  102. data/lib/dommy/shadow_root.rb +76 -13
  103. data/lib/dommy/storage.rb +2 -1
  104. data/lib/dommy/streams.rb +6 -0
  105. data/lib/dommy/text_codec.rb +7 -1
  106. data/lib/dommy/tree_walker.rb +33 -10
  107. data/lib/dommy/url.rb +13 -1
  108. data/lib/dommy/version.rb +1 -1
  109. data/lib/dommy/window.rb +199 -11
  110. data/lib/dommy/worker.rb +8 -4
  111. data/lib/dommy/xml_http_request.rb +47 -6
  112. data/lib/dommy.rb +36 -1
  113. metadata +96 -10
  114. data/lib/dommy/backend/nokogiri_adapter.rb +0 -127
  115. 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