p_css 0.2.0.beta1-x86_64-darwin
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 +7 -0
- data/Cargo.lock +282 -0
- data/Cargo.toml +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +357 -0
- data/ext/css_native/Cargo.toml +12 -0
- data/ext/css_native/extconf.rb +4 -0
- data/ext/css_native/src/lib.rs +117 -0
- data/ext/css_native/src/matcher.rs +356 -0
- data/ext/css_native/src/selectors.rs +411 -0
- data/ext/css_native/src/snapshot.rs +370 -0
- data/ext/css_native/src/state.rs +174 -0
- data/ext/css_native/src/tokenizer.rs +596 -0
- data/lib/css/3.3/css_native.bundle +0 -0
- data/lib/css/3.4/css_native.bundle +0 -0
- data/lib/css/4.0/css_native.bundle +0 -0
- data/lib/css/cascade.rb +277 -0
- data/lib/css/code_points.rb +59 -0
- data/lib/css/escape.rb +82 -0
- data/lib/css/media_queries/context.rb +60 -0
- data/lib/css/media_queries/evaluator.rb +157 -0
- data/lib/css/media_queries/nodes.rb +41 -0
- data/lib/css/media_queries/parser.rb +374 -0
- data/lib/css/media_queries.rb +9 -0
- data/lib/css/native.rb +179 -0
- data/lib/css/nesting.rb +229 -0
- data/lib/css/nodes.rb +42 -0
- data/lib/css/parser.rb +429 -0
- data/lib/css/selectors/anb_parser.rb +174 -0
- data/lib/css/selectors/matcher.rb +545 -0
- data/lib/css/selectors/nodes.rb +61 -0
- data/lib/css/selectors/parser.rb +395 -0
- data/lib/css/selectors/serializer.rb +102 -0
- data/lib/css/selectors/specificity.rb +81 -0
- data/lib/css/selectors.rb +11 -0
- data/lib/css/serializer.rb +167 -0
- data/lib/css/token.rb +107 -0
- data/lib/css/token_cursor.rb +49 -0
- data/lib/css/tokenizer.rb +447 -0
- data/lib/css/urange.rb +45 -0
- data/lib/css/version.rb +3 -0
- data/lib/css.rb +73 -0
- data/lib/p_css.rb +1 -0
- data/sig/css/cascade.rbs +22 -0
- data/sig/css/media_queries.rbs +107 -0
- data/sig/css/nodes.rbs +76 -0
- data/sig/css/selectors.rbs +164 -0
- data/sig/css/token.rbs +33 -0
- data/sig/css.rbs +99 -0
- metadata +113 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
module CSS
|
|
2
|
+
module MediaQueries
|
|
3
|
+
# Parser for `<media-query-list>` per Media Queries Level 4 §3.
|
|
4
|
+
# https://drafts.csswg.org/mediaqueries-4/
|
|
5
|
+
#
|
|
6
|
+
# Accepts either a String (which is tokenized and re-component-valued
|
|
7
|
+
# so `(...)` becomes a `SimpleBlock`) or an Array of component values
|
|
8
|
+
# (for use against an `@media` rule's prelude from the main parser).
|
|
9
|
+
class Parser
|
|
10
|
+
include CSS::TokenCursor
|
|
11
|
+
|
|
12
|
+
MODIFIER_KEYWORDS = %w[not only].freeze
|
|
13
|
+
LOGICAL_KEYWORDS = %w[and or not].freeze
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def parse(input)
|
|
17
|
+
new(items_from(input)).parse_media_query_list
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def items_from(input)
|
|
23
|
+
input.is_a?(String) ? CSS::Parser.parse_component_values(input) : input.to_a
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(items)
|
|
28
|
+
init_cursor(items)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse_media_query_list
|
|
32
|
+
skip_whitespace
|
|
33
|
+
|
|
34
|
+
queries = [parse_media_query]
|
|
35
|
+
|
|
36
|
+
loop do
|
|
37
|
+
skip_whitespace
|
|
38
|
+
break unless peek_token.type == :comma
|
|
39
|
+
|
|
40
|
+
consume
|
|
41
|
+
queries << parse_media_query
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
skip_whitespace
|
|
45
|
+
|
|
46
|
+
unless eof?
|
|
47
|
+
parse_error!("trailing tokens after media query list: #{describe(peek)}")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
MediaQueryList.new(queries:)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def parse_media_query
|
|
54
|
+
skip_whitespace
|
|
55
|
+
|
|
56
|
+
saved = @pos
|
|
57
|
+
|
|
58
|
+
# Try the `[not | only]? <media-type> [and <condition-without-or>]?` form.
|
|
59
|
+
modifier = consume_modifier
|
|
60
|
+
skip_whitespace if modifier
|
|
61
|
+
|
|
62
|
+
if (item = peek).is_a?(Token) && item.type == :ident && !LOGICAL_KEYWORDS.include?(item.value.downcase)
|
|
63
|
+
type = consume.value.downcase
|
|
64
|
+
skip_whitespace
|
|
65
|
+
|
|
66
|
+
condition = nil
|
|
67
|
+
|
|
68
|
+
if keyword?('and')
|
|
69
|
+
consume
|
|
70
|
+
skip_whitespace
|
|
71
|
+
condition = parse_media_condition(allow_or: false)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
return MediaQuery.new(modifier:, type:, condition:)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Otherwise this is a pure media-condition (no type / modifier).
|
|
78
|
+
@pos = saved
|
|
79
|
+
|
|
80
|
+
MediaQuery.new(modifier: nil, type: nil, condition: parse_media_condition(allow_or: true))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def parse_media_condition(allow_or:)
|
|
86
|
+
skip_whitespace
|
|
87
|
+
|
|
88
|
+
if keyword?('not')
|
|
89
|
+
consume
|
|
90
|
+
skip_whitespace
|
|
91
|
+
return MediaNot.new(operand: parse_media_in_parens)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
first = parse_media_in_parens
|
|
95
|
+
|
|
96
|
+
skip_whitespace
|
|
97
|
+
|
|
98
|
+
if keyword?('and')
|
|
99
|
+
operands = [first]
|
|
100
|
+
while keyword?('and')
|
|
101
|
+
consume
|
|
102
|
+
skip_whitespace
|
|
103
|
+
operands << parse_media_in_parens
|
|
104
|
+
skip_whitespace
|
|
105
|
+
end
|
|
106
|
+
MediaAnd.new(operands:)
|
|
107
|
+
elsif allow_or && keyword?('or')
|
|
108
|
+
operands = [first]
|
|
109
|
+
while keyword?('or')
|
|
110
|
+
consume
|
|
111
|
+
skip_whitespace
|
|
112
|
+
operands << parse_media_in_parens
|
|
113
|
+
skip_whitespace
|
|
114
|
+
end
|
|
115
|
+
MediaOr.new(operands:)
|
|
116
|
+
else
|
|
117
|
+
first
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def parse_media_in_parens
|
|
122
|
+
item = peek
|
|
123
|
+
|
|
124
|
+
unless item.is_a?(Nodes::SimpleBlock) && item.parenthesized?
|
|
125
|
+
parse_error!("expected '(', got #{describe(item)}")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
consume
|
|
129
|
+
inner = self.class.new(item.value)
|
|
130
|
+
inner.parse_in_parens_contents
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
protected
|
|
134
|
+
|
|
135
|
+
# Called on a sub-parser whose @items is the contents inside `(...)`.
|
|
136
|
+
# Returns a media-condition or a feature.
|
|
137
|
+
def parse_in_parens_contents
|
|
138
|
+
skip_whitespace
|
|
139
|
+
|
|
140
|
+
return GeneralEnclosed.new(tokens: []) if eof?
|
|
141
|
+
|
|
142
|
+
# Nested `(condition)`?
|
|
143
|
+
first = peek
|
|
144
|
+
|
|
145
|
+
if first.is_a?(Nodes::SimpleBlock) && first.parenthesized?
|
|
146
|
+
cond = parse_media_condition(allow_or: true)
|
|
147
|
+
skip_whitespace
|
|
148
|
+
|
|
149
|
+
unless eof?
|
|
150
|
+
return GeneralEnclosed.new(tokens: @items)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
return cond
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
result = try_parse_feature
|
|
157
|
+
|
|
158
|
+
return result if result
|
|
159
|
+
|
|
160
|
+
GeneralEnclosed.new(tokens: @items)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def try_parse_feature
|
|
166
|
+
saved = @pos
|
|
167
|
+
|
|
168
|
+
starting_token = peek
|
|
169
|
+
|
|
170
|
+
if starting_token.is_a?(Token) && starting_token.type == :ident
|
|
171
|
+
feature = try_parse_feature_starting_with_ident
|
|
172
|
+
|
|
173
|
+
return feature if feature
|
|
174
|
+
|
|
175
|
+
@pos = saved
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
if value_starts?(starting_token)
|
|
179
|
+
feature = try_parse_feature_starting_with_value
|
|
180
|
+
|
|
181
|
+
return feature if feature
|
|
182
|
+
|
|
183
|
+
@pos = saved
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
nil
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def try_parse_feature_starting_with_ident
|
|
190
|
+
name = consume.value.downcase
|
|
191
|
+
|
|
192
|
+
skip_whitespace
|
|
193
|
+
|
|
194
|
+
if eof?
|
|
195
|
+
return MediaFeature.new(name:, op: nil, value: nil)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
if peek_token.type == :colon
|
|
199
|
+
consume
|
|
200
|
+
skip_whitespace
|
|
201
|
+
value = parse_mf_value
|
|
202
|
+
|
|
203
|
+
return nil if value.nil?
|
|
204
|
+
|
|
205
|
+
skip_whitespace
|
|
206
|
+
|
|
207
|
+
return nil unless eof?
|
|
208
|
+
|
|
209
|
+
return MediaFeature.new(name:, op: :eq, value:)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
if (op = consume_comparison)
|
|
213
|
+
skip_whitespace
|
|
214
|
+
value = parse_mf_value
|
|
215
|
+
|
|
216
|
+
return nil if value.nil?
|
|
217
|
+
|
|
218
|
+
skip_whitespace
|
|
219
|
+
|
|
220
|
+
if eof?
|
|
221
|
+
return MediaFeature.new(name:, op:, value:)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Bounded form: `<name> <op> <value> ... <op> <value>`
|
|
225
|
+
# Per spec, bounded form has the name in the middle, not here.
|
|
226
|
+
# Reject.
|
|
227
|
+
return nil
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
nil
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def try_parse_feature_starting_with_value
|
|
234
|
+
first_value = parse_mf_value
|
|
235
|
+
|
|
236
|
+
return nil if first_value.nil?
|
|
237
|
+
|
|
238
|
+
skip_whitespace
|
|
239
|
+
|
|
240
|
+
first_op = consume_comparison
|
|
241
|
+
|
|
242
|
+
return nil unless first_op
|
|
243
|
+
|
|
244
|
+
skip_whitespace
|
|
245
|
+
|
|
246
|
+
return nil unless peek_token.type == :ident
|
|
247
|
+
|
|
248
|
+
name = consume.value.downcase
|
|
249
|
+
|
|
250
|
+
skip_whitespace
|
|
251
|
+
|
|
252
|
+
if eof?
|
|
253
|
+
return MediaFeature.new(name:, op: invert_op(first_op), value: first_value)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Bounded form: <value> <op1> <name> <op2> <value>
|
|
257
|
+
second_op = consume_comparison
|
|
258
|
+
|
|
259
|
+
return nil unless second_op
|
|
260
|
+
|
|
261
|
+
skip_whitespace
|
|
262
|
+
|
|
263
|
+
second_value = parse_mf_value
|
|
264
|
+
|
|
265
|
+
return nil if second_value.nil?
|
|
266
|
+
|
|
267
|
+
skip_whitespace
|
|
268
|
+
|
|
269
|
+
return nil unless eof?
|
|
270
|
+
|
|
271
|
+
# Decompose into MediaAnd of two normalized features.
|
|
272
|
+
MediaAnd.new(operands: [
|
|
273
|
+
MediaFeature.new(name:, op: invert_op(first_op), value: first_value),
|
|
274
|
+
MediaFeature.new(name:, op: second_op, value: second_value)
|
|
275
|
+
])
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# `value op name` swaps to `name (inverted op) value`.
|
|
279
|
+
INVERSE_OP = {lt: :gt, le: :ge, gt: :lt, ge: :le, eq: :eq}.freeze
|
|
280
|
+
|
|
281
|
+
def invert_op(op) = INVERSE_OP.fetch(op, op)
|
|
282
|
+
|
|
283
|
+
def parse_mf_value
|
|
284
|
+
item = peek
|
|
285
|
+
|
|
286
|
+
return nil unless item.is_a?(Token)
|
|
287
|
+
|
|
288
|
+
case item.type
|
|
289
|
+
when :number
|
|
290
|
+
consume
|
|
291
|
+
|
|
292
|
+
if peek_token.type == :delim && peek_token.value == '/'
|
|
293
|
+
consume
|
|
294
|
+
skip_whitespace
|
|
295
|
+
denom = peek
|
|
296
|
+
|
|
297
|
+
return nil unless denom.is_a?(Token) && denom.type == :number
|
|
298
|
+
|
|
299
|
+
consume
|
|
300
|
+
return Ratio.new(numerator: item.value, denominator: denom.value)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
item
|
|
304
|
+
when :dimension, :percentage, :ident, :string
|
|
305
|
+
consume
|
|
306
|
+
item
|
|
307
|
+
else
|
|
308
|
+
nil
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def consume_comparison
|
|
313
|
+
item = peek
|
|
314
|
+
|
|
315
|
+
return nil unless item.is_a?(Token) && item.type == :delim
|
|
316
|
+
|
|
317
|
+
case item.value
|
|
318
|
+
when '='
|
|
319
|
+
consume
|
|
320
|
+
:eq
|
|
321
|
+
when '<'
|
|
322
|
+
consume
|
|
323
|
+
|
|
324
|
+
if peek_token.type == :delim && peek_token.value == '='
|
|
325
|
+
consume
|
|
326
|
+
:le
|
|
327
|
+
else
|
|
328
|
+
:lt
|
|
329
|
+
end
|
|
330
|
+
when '>'
|
|
331
|
+
consume
|
|
332
|
+
|
|
333
|
+
if peek_token.type == :delim && peek_token.value == '='
|
|
334
|
+
consume
|
|
335
|
+
:ge
|
|
336
|
+
else
|
|
337
|
+
:gt
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def value_starts?(item)
|
|
343
|
+
item.is_a?(Token) && %i[number dimension percentage].include?(item.type)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def consume_modifier
|
|
347
|
+
item = peek
|
|
348
|
+
|
|
349
|
+
return nil unless item.is_a?(Token) && item.type == :ident
|
|
350
|
+
|
|
351
|
+
kw = item.value.downcase
|
|
352
|
+
|
|
353
|
+
return nil unless MODIFIER_KEYWORDS.include?(kw)
|
|
354
|
+
|
|
355
|
+
consume
|
|
356
|
+
kw.to_sym
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def keyword?(kw)
|
|
360
|
+
item = peek
|
|
361
|
+
item.is_a?(Token) && item.type == :ident && item.value.downcase == kw
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def describe(item)
|
|
365
|
+
case item
|
|
366
|
+
when Token then item.type
|
|
367
|
+
when Nodes::SimpleBlock then "#{item.open}-block"
|
|
368
|
+
when Nodes::Function then "#{item.name}()"
|
|
369
|
+
else item.class.name
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
data/lib/css/native.rb
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
require_relative '../css'
|
|
2
|
+
|
|
3
|
+
# cibuildgem ships precompiled binaries at lib/css/<ruby_minor>/css_native.{so,bundle,dll}.
|
|
4
|
+
# When users compile the gem from source (no platform gem available),
|
|
5
|
+
# extconf.rb places the binary at lib/css/css_native.{so,bundle,dll}.
|
|
6
|
+
# Try the version-specific path first, fall back to the non-versioned one.
|
|
7
|
+
begin
|
|
8
|
+
ruby_minor = RUBY_VERSION[/\d+\.\d+/]
|
|
9
|
+
require "css/#{ruby_minor}/css_native"
|
|
10
|
+
rescue LoadError
|
|
11
|
+
require_relative 'css_native'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Rust defines matches?, matches_any?, match_indices with a required
|
|
15
|
+
# `state` argument (3 args). Wrap them in Ruby so callers can pass 2
|
|
16
|
+
# args when stateful pseudos aren't relevant.
|
|
17
|
+
module CSS
|
|
18
|
+
module Native
|
|
19
|
+
class Snapshot
|
|
20
|
+
alias_method :_native_matches?, :matches?
|
|
21
|
+
alias_method :_native_matches_any?, :matches_any?
|
|
22
|
+
alias_method :_native_match_indices, :match_indices
|
|
23
|
+
|
|
24
|
+
def matches?(element, selector, state = nil)
|
|
25
|
+
_native_matches?(element, selector, state)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def matches_any?(element, selectors, state = nil)
|
|
29
|
+
_native_matches_any?(element, selectors, state)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def match_indices(element, selectors, state = nil)
|
|
33
|
+
_native_match_indices(element, selectors, state)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module CSS
|
|
40
|
+
module Native
|
|
41
|
+
# High-level wrapper: takes a Nokogiri element + a selector (string or
|
|
42
|
+
# parsed AST) and returns matches?(element). Falls back to the pure-Ruby
|
|
43
|
+
# Matcher when the selector contains features the native matcher doesn't
|
|
44
|
+
# support yet (pseudo-classes, :not, etc.).
|
|
45
|
+
#
|
|
46
|
+
# `document:` / `snapshot:` let callers control snapshot reuse across
|
|
47
|
+
# many matches against the same DOM. With neither, a per-document
|
|
48
|
+
# snapshot is cached by document identity.
|
|
49
|
+
class << self
|
|
50
|
+
def matches?(element, selector, snapshot: nil, document: nil, state: nil)
|
|
51
|
+
ast = selector.is_a?(String) ? CSS.parse_selector_list(selector) : selector
|
|
52
|
+
compiled = compile_or_nil(ast)
|
|
53
|
+
snap = snapshot || snapshot_for(document || element.document)
|
|
54
|
+
|
|
55
|
+
return CSS.matches?(element, ast, state: state) unless compiled
|
|
56
|
+
|
|
57
|
+
native_state = state.nil? ? nil : (state.is_a?(State) ? state : snap.compile_state(state))
|
|
58
|
+
snap.matches?(element, compiled, native_state)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def compile_or_nil(ast)
|
|
62
|
+
Selector.compile(ast)
|
|
63
|
+
rescue Unsupported
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def snapshot_for(document)
|
|
70
|
+
(@snapshots ||= {}.compare_by_identity)[document] ||= Snapshot.from_document(document)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Subclass of CSS::Cascade that uses the native matcher for the inner
|
|
75
|
+
# rule-matching loop. Selectors are pre-compiled at construction —
|
|
76
|
+
# those that can't be compiled (pseudo-classes etc.) fall through to
|
|
77
|
+
# the pure-Ruby matcher, so behavior is identical to CSS::Cascade.
|
|
78
|
+
#
|
|
79
|
+
# Requires a Nokogiri document at construction; the snapshot is built
|
|
80
|
+
# once and reused for every resolve(). Mutate the DOM and you must
|
|
81
|
+
# construct a fresh CSS::Native::Cascade.
|
|
82
|
+
class Cascade < CSS::Cascade
|
|
83
|
+
def initialize(stylesheet, document, context: CSS::MediaQueries::Context.default)
|
|
84
|
+
super(stylesheet, context: context)
|
|
85
|
+
|
|
86
|
+
@snapshot = Snapshot.from_document(document)
|
|
87
|
+
@compiled_by_ast = {}.compare_by_identity
|
|
88
|
+
|
|
89
|
+
@entries.each do |entry|
|
|
90
|
+
entry.selector_pairs.each {|ast, _spec|
|
|
91
|
+
@compiled_by_ast[ast] = Native.compile_or_nil(ast)
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Override: batch every candidate's compiled selectors into one FFI
|
|
97
|
+
# hop per resolve (GVL released), then merge in any Ruby-fallback
|
|
98
|
+
# matches. Cuts per-resolve FFI cost from O(candidates) to O(1).
|
|
99
|
+
def resolve(element, inline_style: nil, state: nil)
|
|
100
|
+
cache = {}
|
|
101
|
+
candidates = collect_candidate_indexes(element, cache)
|
|
102
|
+
order = 0
|
|
103
|
+
matches = []
|
|
104
|
+
native_state = state && @snapshot.compile_state(state)
|
|
105
|
+
|
|
106
|
+
best_by_entry = native_pass(element, candidates, native_state)
|
|
107
|
+
ruby_fallback_pass(element, candidates, best_by_entry, cache, state)
|
|
108
|
+
|
|
109
|
+
candidates.each do |entry_idx|
|
|
110
|
+
spec = best_by_entry[entry_idx] or next
|
|
111
|
+
|
|
112
|
+
@entries[entry_idx].declarations.each {|decl|
|
|
113
|
+
order += 1
|
|
114
|
+
matches << CSS::Cascade::Match.new(declaration: decl, specificity: spec, inline: false, order: order)
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if inline_style
|
|
119
|
+
inline_declarations(inline_style).each {|decl|
|
|
120
|
+
order += 1
|
|
121
|
+
matches << CSS::Cascade::Match.new(
|
|
122
|
+
declaration: decl,
|
|
123
|
+
specificity: CSS::Selectors::Specificity::ZERO,
|
|
124
|
+
inline: true,
|
|
125
|
+
order: order
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
pick_winners(matches)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
# Flatten the candidates' compiled selectors into one batched
|
|
136
|
+
# match_indices call. Returns Hash{entry_idx => best_specificity}.
|
|
137
|
+
def native_pass(element, candidates, native_state)
|
|
138
|
+
positions = []
|
|
139
|
+
sels = []
|
|
140
|
+
|
|
141
|
+
candidates.each do |entry_idx|
|
|
142
|
+
@entries[entry_idx].selector_pairs.each {|ast, spec|
|
|
143
|
+
compiled = @compiled_by_ast[ast] or next
|
|
144
|
+
|
|
145
|
+
positions << [entry_idx, spec]
|
|
146
|
+
sels << compiled
|
|
147
|
+
}
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
best_by_entry = {}
|
|
151
|
+
|
|
152
|
+
return best_by_entry if sels.empty?
|
|
153
|
+
|
|
154
|
+
@snapshot.match_indices(element, sels, native_state).each {|i|
|
|
155
|
+
entry_idx, spec = positions[i]
|
|
156
|
+
cur = best_by_entry[entry_idx]
|
|
157
|
+
|
|
158
|
+
best_by_entry[entry_idx] = spec if cur.nil? || spec > cur
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
best_by_entry
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Run pure-Ruby matching for any selectors that didn't compile
|
|
165
|
+
# (pseudo-classes, etc.), merging results into best_by_entry.
|
|
166
|
+
def ruby_fallback_pass(element, candidates, best_by_entry, cache, state)
|
|
167
|
+
candidates.each do |entry_idx|
|
|
168
|
+
@entries[entry_idx].selector_pairs.each {|ast, spec|
|
|
169
|
+
next if @compiled_by_ast[ast]
|
|
170
|
+
next unless Selectors::Matcher.matches?(element, ast, cache: cache, state: state)
|
|
171
|
+
|
|
172
|
+
cur = best_by_entry[entry_idx]
|
|
173
|
+
best_by_entry[entry_idx] = spec if cur.nil? || spec > cur
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|