cfsolve 0.1.1 → 0.1.2

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +10 -9
  4. data/UPSTREAM.md +6 -6
  5. data/lib/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
  6. data/lib/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
  7. data/lib/cfsolve/profiles/safari-26-ios.json +0 -8
  8. data/lib/cfsolve/version.rb +2 -2
  9. data/vendor/cfsolve/README.md +0 -35
  10. data/vendor/cfsolve/examples/build_profile.rs +17 -15
  11. data/vendor/cfsolve/examples/capture_session.rs +37 -0
  12. data/vendor/cfsolve/examples/check_answers.rs +66 -0
  13. data/vendor/cfsolve/examples/compare_session.rs +190 -0
  14. data/vendor/cfsolve/examples/stage2_snapshot.rs +4 -1
  15. data/vendor/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
  16. data/vendor/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
  17. data/vendor/cfsolve/profiles/safari-26-ios.json +0 -8
  18. data/vendor/cfsolve/scripts/capture_cssom.mjs +42 -0
  19. data/vendor/cfsolve/scripts/serve_cssom.py +111 -0
  20. data/vendor/cfsolve/src/capture/profile.rs +7 -18
  21. data/vendor/cfsolve/src/capture/recorded.rs +42 -7
  22. data/vendor/cfsolve/src/challenge/installed/audit.rs +131 -0
  23. data/vendor/cfsolve/src/challenge/installed.rs +122 -271
  24. data/vendor/cfsolve/src/challenge/payload.rs +107 -5
  25. data/vendor/cfsolve/src/challenge/state.rs +98 -16
  26. data/vendor/cfsolve/src/deobfuscate/encoded_strings.rs +3 -2
  27. data/vendor/cfsolve/src/deobfuscate/helper_objects.rs +17 -32
  28. data/vendor/cfsolve/src/deobfuscate/register_arrays.rs +18 -5
  29. data/vendor/cfsolve/src/embedding_page/extra_params.rs +0 -14
  30. data/vendor/cfsolve/src/embedding_page/page.rs +28 -2
  31. data/vendor/cfsolve/src/http/client.rs +11 -0
  32. data/vendor/cfsolve/src/js/ast.rs +12 -3
  33. data/vendor/cfsolve/src/js/objects.rs +9 -2
  34. data/vendor/cfsolve/src/listing/fold.rs +0 -83
  35. data/vendor/cfsolve/src/listing/query.rs +25 -244
  36. data/vendor/cfsolve/src/listing.rs +0 -1
  37. data/vendor/cfsolve/src/session/initial.rs +14 -5
  38. data/vendor/cfsolve/src/session/probes.rs +160 -125
  39. data/vendor/cfsolve/src/session/timing.rs +58 -53
  40. data/vendor/cfsolve/src/session/widget.rs +5 -6
  41. data/vendor/cfsolve/src/stage2/analysis/analyze.rs +50 -33
  42. data/vendor/cfsolve/src/stage2/analysis/discovery.rs +61 -66
  43. data/vendor/cfsolve/src/stage2/analysis/envelope.rs +31 -21
  44. data/vendor/cfsolve/src/stage2/analysis/recognize.rs +3 -5
  45. data/vendor/cfsolve/src/stage2/analysis.rs +0 -1
  46. data/vendor/cfsolve/src/stage2/audit_globals.rs +310 -0
  47. data/vendor/cfsolve/src/stage2/benchmark.rs +5 -8
  48. data/vendor/cfsolve/src/stage2/call_stack.rs +72 -16
  49. data/vendor/cfsolve/src/stage2/interaction/native.rs +2 -1
  50. data/vendor/cfsolve/src/stage2/interaction/plan.rs +0 -3
  51. data/vendor/cfsolve/src/stage2/payload/recover.rs +54 -23
  52. data/vendor/cfsolve/src/stage2/probes/ice.rs +11 -10
  53. data/vendor/cfsolve/src/stage2/probes/media.rs +47 -33
  54. data/vendor/cfsolve/src/stage2/probes/performance.rs +1 -0
  55. data/vendor/cfsolve/src/stage2/probes/rects.rs +12 -17
  56. data/vendor/cfsolve/src/stage2/probes/stack.rs +50 -103
  57. data/vendor/cfsolve/src/stage2/probes/websocket_echo.rs +17 -86
  58. data/vendor/cfsolve/src/stage2/probes.rs +0 -1
  59. data/vendor/cfsolve/src/stage2/records.rs +4 -13
  60. data/vendor/cfsolve/src/stage2/reference.json +1 -1
  61. data/vendor/cfsolve/src/stage2/reference.rs +98 -100
  62. data/vendor/cfsolve/src/stage2/registry.rs +41 -34
  63. data/vendor/cfsolve/src/stage2/replay.rs +182 -254
  64. data/vendor/cfsolve/src/stage2/stylesheets.rs +176 -0
  65. data/vendor/cfsolve/src/stage2/trace.rs +18 -0
  66. data/vendor/cfsolve/src/stage2/vocabulary.rs +111 -401
  67. data/vendor/cfsolve/src/stage2.rs +2 -1
  68. data/vendor/cfsolve/src/vm/disassemble.rs +39 -10
  69. data/vendor/cfsolve/src/vm/interpret/access.rs +5 -21
  70. data/vendor/cfsolve/src/vm/interpret/builtin.rs +6 -53
  71. data/vendor/cfsolve/src/vm/interpret/call.rs +86 -9
  72. data/vendor/cfsolve/src/vm/interpret/compile.rs +626 -0
  73. data/vendor/cfsolve/src/vm/interpret/copies.rs +232 -120
  74. data/vendor/cfsolve/src/vm/interpret/evaluate/tests.rs +96 -8
  75. data/vendor/cfsolve/src/vm/interpret/evaluate.rs +219 -92
  76. data/vendor/cfsolve/src/vm/interpret/explore.rs +155 -39
  77. data/vendor/cfsolve/src/vm/interpret/frozen.rs +171 -0
  78. data/vendor/cfsolve/src/vm/interpret/intrinsic.rs +350 -0
  79. data/vendor/cfsolve/src/vm/interpret/plan.rs +71 -44
  80. data/vendor/cfsolve/src/vm/interpret/run.rs +202 -13
  81. data/vendor/cfsolve/src/vm/interpret.rs +7 -2
  82. data/vendor/cfsolve/src/vm/machine.rs +6 -2
  83. data/vendor/cfsolve/src/vm/packed_names.rs +307 -0
  84. data/vendor/cfsolve/src/vm/session.rs +36 -14
  85. data/vendor/cfsolve/src/vm/value.rs +13 -0
  86. data/vendor/cfsolve/src/vm.rs +1 -0
  87. data/vendor/cfsolve/vendor/btls-sys/src/lib.rs +3 -0
  88. metadata +13 -6
  89. data/vendor/cfsolve/src/listing/normalize.rs +0 -264
  90. data/vendor/cfsolve/src/stage2/analysis/outputs.rs +0 -327
  91. data/vendor/cfsolve/src/stage2/probes/stylesheets.rs +0 -249
  92. data/vendor/cfsolve/src/stage2/reference-expanded.json +0 -42
  93. data/vendor/cfsolve/src/stage2/session_names.rs +0 -470
@@ -1,327 +0,0 @@
1
- //! Recovers result fields whose reference names are held in header registers rather than
2
- //! appearing among a probe's collection strings.
3
-
4
- use std::collections::{BTreeMap, BTreeSet};
5
-
6
- use indexmap::IndexMap;
7
- use swc_core::ecma::ast::BinaryOp;
8
-
9
- use crate::listing::Index;
10
- use crate::stage2::reference::{
11
- COMPUTED_STYLE_MAP, FONT_CANVAS_OUTPUTS, FONT_DOM_OUTPUTS, FONT_FACE_OUTPUTS,
12
- NATIVE_INSPECTION_OUTPUTS,
13
- };
14
- use crate::stage2::{registry::Kind, vocabulary::hashed_name};
15
- use crate::vm::{Constant, Term};
16
-
17
- #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
18
- enum Object {
19
- Created(u32),
20
- Property(u32),
21
- Cell(u32),
22
- }
23
-
24
- #[derive(Default)]
25
- struct Fields(IndexMap<String, Vec<Term>>);
26
-
27
- impl Fields {
28
- fn push(&mut self, name: &str, value: Option<Term>) {
29
- self.0.entry(name.to_owned()).or_default().extend(value);
30
- }
31
-
32
- fn has(&self, position: usize, predicate: impl Fn(&Term) -> bool) -> bool {
33
- self.0
34
- .get_index(position)
35
- .is_some_and(|(_, values)| values.iter().any(predicate))
36
- }
37
- }
38
-
39
- pub(super) fn names(
40
- index: &Index<'_>,
41
- entry: u32,
42
- reference: usize,
43
- common: &BTreeSet<String>,
44
- ) -> Vec<(String, &'static str)> {
45
- let Some(
46
- kind @ (Kind::NativeInspection
47
- | Kind::FontEnumeration
48
- | Kind::ComputedStyleHash
49
- | Kind::Rects),
50
- ) = Kind::from_reference(reference)
51
- else {
52
- return Vec::new();
53
- };
54
- let reached = index.reachable(entry);
55
- if kind == Kind::Rects {
56
- let roles = [
57
- ("bottom", "Cmnj0"),
58
- ("height", "VwYDD8"),
59
- ("left", "knUS7"),
60
- ("right", "cyNU4"),
61
- ("top", "nCLYc7"),
62
- ("width", "UCluM4"),
63
- ("x", "YptYf3"),
64
- ("y", "wAVRH3"),
65
- ];
66
- return roles
67
- .into_iter()
68
- .filter_map(|(property, reference)| {
69
- let names: BTreeSet<_> = index
70
- .stores()
71
- .iter()
72
- .filter(|store| {
73
- reached.contains(&store.pc)
74
- && hashed_name(&store.key)
75
- && index.method(store.pc, &store.value).as_deref() == Some(property)
76
- })
77
- .map(|store| store.key.clone())
78
- .collect();
79
- (names.len() == 1).then(|| (names.into_iter().next().unwrap(), reference))
80
- })
81
- .collect();
82
- }
83
- let mut groups = BTreeMap::<Object, Fields>::new();
84
- for store in index
85
- .stores()
86
- .iter()
87
- .filter(|store| reached.contains(&store.pc))
88
- {
89
- if !hashed_name(&store.key) || common.contains(&store.key) {
90
- continue;
91
- }
92
- let Some((origin, object)) = index.dereference(store.pc, &Term::Reg(store.object)) else {
93
- continue;
94
- };
95
- let object = match object {
96
- Term::Object(_) => Object::Created(origin),
97
- Term::Get(_, _) => Object::Property(origin),
98
- Term::Cell(cell) => Object::Cell(cell),
99
- _ => continue,
100
- };
101
- let value = index
102
- .dereference(store.pc, &store.value)
103
- .map(|(_, value)| value);
104
- groups.entry(object).or_default().push(&store.key, value);
105
- }
106
- let mut pairs = Vec::new();
107
- let mut pin = |candidates: Vec<&Fields>, names: &[&'static str]| {
108
- if let [fields] = candidates.as_slice() {
109
- pairs.extend(
110
- fields
111
- .0
112
- .keys()
113
- .take(names.len())
114
- .cloned()
115
- .zip(names.iter().copied()),
116
- );
117
- }
118
- };
119
- match kind {
120
- Kind::NativeInspection => pin(
121
- groups
122
- .iter()
123
- .filter_map(|(object, fields)| {
124
- (matches!(object, Object::Property(_)) && fields.0.len() == 5).then_some(fields)
125
- })
126
- .collect(),
127
- &NATIVE_INSPECTION_OUTPUTS,
128
- ),
129
- Kind::ComputedStyleHash => pin(
130
- groups
131
- .iter()
132
- .filter_map(|(object, fields)| {
133
- (matches!(object, Object::Property(_)) && fields.0.len() == 1).then_some(fields)
134
- })
135
- .collect(),
136
- &[COMPUTED_STYLE_MAP],
137
- ),
138
- Kind::FontEnumeration => {
139
- // FontFace's fallback is a list, a count, a status, a hash and an error.
140
- pin(
141
- groups
142
- .iter()
143
- .filter_map(|(object, fields)| {
144
- (matches!(object, Object::Created(_))
145
- && fields.0.len() == 5
146
- && fields.has(
147
- 0,
148
- |value| matches!(value, Term::Array(values) if values.is_empty()),
149
- )
150
- && fields.has(1, |value| {
151
- matches!(value, Term::Const(Constant::Number(0.0)))
152
- }))
153
- .then_some(fields)
154
- })
155
- .collect(),
156
- &FONT_FACE_OUTPUTS,
157
- );
158
- // Canvas measurements have three array defaults.
159
- pin(
160
- groups
161
- .iter()
162
- .filter_map(|(object, fields)| {
163
- let arrays = (0..3).all(|position| {
164
- fields.has(
165
- position,
166
- |value| matches!(value, Term::Array(values) if values.is_empty()),
167
- )
168
- });
169
- (matches!(object, Object::Created(_)) && fields.0.len() == 3 && arrays)
170
- .then_some(fields)
171
- })
172
- .collect(),
173
- &FONT_CANVAS_OUTPUTS,
174
- );
175
- // The DOM measurement callback writes an array and a sum into its shared result.
176
- pin(
177
- groups
178
- .iter()
179
- .filter_map(|(object, fields)| {
180
- (matches!(object, Object::Cell(_))
181
- && fields.0.len() == 2
182
- && fields.has(
183
- 0,
184
- |value| matches!(value, Term::Array(values) if values.is_empty()),
185
- )
186
- && fields.has(1, |value| {
187
- matches!(value, Term::Binary(BinaryOp::Add, _, _))
188
- }))
189
- .then_some(fields)
190
- })
191
- .collect(),
192
- &FONT_DOM_OUTPUTS,
193
- );
194
- }
195
- _ => {}
196
- }
197
- pairs
198
- }
199
-
200
- #[cfg(test)]
201
- mod tests {
202
- use super::*;
203
- use crate::vm::{Effect, Exit, Instruction, Listing, Path};
204
-
205
- fn listing(groups: &[(Term, Vec<Term>)]) -> Listing {
206
- let mut effects = Vec::new();
207
- for (group, (object, values)) in groups.iter().enumerate() {
208
- effects.push(Effect::Set(1, object.clone()));
209
- for (position, value) in values.iter().enumerate() {
210
- effects.push(Effect::SetProperty {
211
- object: Box::new(Term::Reg(1)),
212
- key: Box::new(Term::Const(Constant::String(format!(
213
- "_{:024x}",
214
- group * 10 + position
215
- )))),
216
- value: Box::new(value.clone()),
217
- });
218
- }
219
- }
220
- let count = effects.len();
221
- let instructions = effects
222
- .into_iter()
223
- .enumerate()
224
- .map(|(pc, effect)| {
225
- (
226
- pc as u32,
227
- Instruction {
228
- pc: pc as u32,
229
- key: 0,
230
- opcode: 0,
231
- paths: vec![Path {
232
- conditions: vec![],
233
- effects: vec![effect],
234
- exit: if pc + 1 == count {
235
- Exit::Return
236
- } else {
237
- Exit::Next {
238
- pc: pc as u32 + 1,
239
- key: 0,
240
- }
241
- },
242
- }],
243
- },
244
- )
245
- })
246
- .collect();
247
- Listing {
248
- instructions,
249
- ..Listing::default()
250
- }
251
- }
252
-
253
- #[test]
254
- fn recovers_font_roles_from_distinct_object_shapes_and_rejects_ambiguity() {
255
- let empty = Term::Array(vec![]);
256
- let zero = Term::Const(Constant::Number(0.0));
257
- let face = (
258
- Term::Object(vec![]),
259
- vec![
260
- empty.clone(),
261
- zero.clone(),
262
- zero.clone(),
263
- zero.clone(),
264
- zero.clone(),
265
- ],
266
- );
267
- let mut groups = vec![
268
- face.clone(),
269
- (Term::Object(vec![]), vec![empty.clone(); 3]),
270
- (
271
- Term::Cell(8),
272
- vec![
273
- empty,
274
- Term::Binary(BinaryOp::Add, Box::new(Term::Reg(9)), Box::new(zero)),
275
- ],
276
- ),
277
- ];
278
- let reference = Kind::FontEnumeration.references()[0];
279
- let program = listing(&groups);
280
- let pairs = names(&Index::new(&program), 0, reference, &BTreeSet::new());
281
- let expected: Vec<_> = FONT_FACE_OUTPUTS
282
- .into_iter()
283
- .chain(FONT_CANVAS_OUTPUTS)
284
- .chain(FONT_DOM_OUTPUTS)
285
- .collect();
286
- assert_eq!(
287
- pairs
288
- .iter()
289
- .map(|(_, reference)| *reference)
290
- .collect::<Vec<_>>(),
291
- expected
292
- );
293
- groups.push(face);
294
- let program = listing(&groups);
295
- let pairs = names(&Index::new(&program), 0, reference, &BTreeSet::new());
296
- assert!(
297
- pairs
298
- .iter()
299
- .all(|(_, field)| !FONT_FACE_OUTPUTS.contains(field))
300
- );
301
- assert_eq!(
302
- pairs.len(),
303
- FONT_CANVAS_OUTPUTS.len() + FONT_DOM_OUTPUTS.len()
304
- );
305
- }
306
-
307
- #[test]
308
- fn native_inspection_requires_a_unique_result_object() {
309
- let object = Term::Get(
310
- Box::new(Term::Cell(8)),
311
- Box::new(Term::Const(Constant::String("result".into()))),
312
- );
313
- let group = (object, vec![Term::Const(Constant::Null); 5]);
314
- let program = listing(std::slice::from_ref(&group));
315
- let reference = Kind::NativeInspection.references()[0];
316
- let pairs = names(&Index::new(&program), 0, reference, &BTreeSet::new());
317
- assert_eq!(
318
- pairs
319
- .iter()
320
- .map(|(_, reference)| *reference)
321
- .collect::<Vec<_>>(),
322
- NATIVE_INSPECTION_OUTPUTS
323
- );
324
- let program = listing(&[group.clone(), group]);
325
- assert!(names(&Index::new(&program), 0, reference, &BTreeSet::new()).is_empty());
326
- }
327
- }
@@ -1,249 +0,0 @@
1
- //! Replays captured CSSOM formatting after validating a stylesheet's structure.
2
-
3
- use anyhow::{Context, Result, bail, ensure};
4
- use regex::Regex;
5
- use serde::{Deserialize, Serialize};
6
- use sha2::{Digest, Sha256};
7
- use std::collections::BTreeMap;
8
- use std::sync::LazyLock;
9
-
10
- use crate::stage2::{
11
- records::Probe,
12
- reference::{STYLESHEET_DIGEST, STYLESHEET_MARKER},
13
- vocabulary,
14
- };
15
- use crate::{
16
- candidates,
17
- js::{JsObject, json},
18
- };
19
-
20
- static SESSION_NAME: LazyLock<Regex> =
21
- LazyLock::new(|| Regex::new(r"_[0-9a-fA-F]{24}|[A-Za-z]{3,5}[0-9]").unwrap());
22
-
23
- /// Source text paired with the rules the profile's browser exposed through CSSOM.
24
- #[derive(Clone, Debug, Deserialize, Serialize)]
25
- pub struct Sheet {
26
- pub source: String,
27
- pub rules: Vec<String>,
28
- }
29
-
30
- /// One captured stylesheet revision. Multiple revisions can share a device profile.
31
- #[derive(Clone, Debug, Deserialize, Serialize)]
32
- pub struct Snapshot {
33
- pub sheets: Vec<Sheet>,
34
- }
35
-
36
- impl Snapshot {
37
- pub fn capture(html: &str, rules: Vec<Vec<String>>) -> Result<Self> {
38
- let sources = sources(html);
39
- ensure!(
40
- sources.len() == rules.len(),
41
- "CSSOM rule lists do not match the iframe's stylesheets"
42
- );
43
- Ok(Self {
44
- sheets: sources
45
- .into_iter()
46
- .zip(rules)
47
- .map(|(source, rules)| Sheet { source, rules })
48
- .collect(),
49
- })
50
- }
51
-
52
- /// Requires identical text outside randomized names and a consistent one-to-one name map.
53
- pub fn replay(&self, sources: &[String]) -> Result<Vec<Vec<String>>> {
54
- ensure!(
55
- sources.len() == self.sheets.len(),
56
- "stylesheet count changed"
57
- );
58
- let mut names = BTreeMap::new();
59
- let mut reverse = BTreeMap::new();
60
- for (sheet, current) in self.sheets.iter().zip(sources) {
61
- ensure!(
62
- SESSION_NAME
63
- .split(&sheet.source)
64
- .eq(SESSION_NAME.split(current)),
65
- "stylesheet structure changed"
66
- );
67
- for (old, new) in SESSION_NAME
68
- .find_iter(&sheet.source)
69
- .zip(SESSION_NAME.find_iter(current))
70
- {
71
- let (old, new) = (old.as_str(), new.as_str());
72
- ensure!(
73
- names
74
- .insert(old, new)
75
- .is_none_or(|previous| previous == new),
76
- "inconsistent stylesheet name"
77
- );
78
- ensure!(
79
- reverse
80
- .insert(new, old)
81
- .is_none_or(|previous| previous == old),
82
- "ambiguous stylesheet name"
83
- );
84
- }
85
- }
86
- Ok(self
87
- .sheets
88
- .iter()
89
- .map(|sheet| {
90
- sheet
91
- .rules
92
- .iter()
93
- .map(|rule| {
94
- SESSION_NAME
95
- .replace_all(rule, |captures: &regex::Captures<'_>| {
96
- names
97
- .get(&captures[0])
98
- .copied()
99
- .unwrap_or(&captures[0])
100
- .to_owned()
101
- })
102
- .into_owned()
103
- })
104
- .collect()
105
- })
106
- .collect())
107
- }
108
- }
109
-
110
- #[must_use]
111
- pub(crate) fn sources(html: &str) -> Vec<String> {
112
- let document = scraper::Html::parse_document(html);
113
- let selector = scraper::Selector::parse("style").unwrap();
114
- document
115
- .select(&selector)
116
- .map(|style| style.inner_html())
117
- .collect()
118
- }
119
-
120
- /// Updates the stored fields before replay translates reference names and arranges the record.
121
- pub(crate) fn refresh(
122
- snapshots: &[Snapshot],
123
- probe: &Probe,
124
- html: &str,
125
- fields: &mut JsObject,
126
- ) -> Result<()> {
127
- let markers = probe
128
- .features
129
- .iter()
130
- .filter(|text| text.strip_prefix('.').is_some_and(vocabulary::is_name));
131
- let marker = candidates::at_most_one_distinct(markers, "stylesheet marker")?
132
- .context("no stylesheet marker")?;
133
- let (digest, marker_css) = measurement(snapshots, html, marker)?;
134
- for (field, value) in [(STYLESHEET_DIGEST, digest), (STYLESHEET_MARKER, marker_css)] {
135
- *fields
136
- .get_mut(field)
137
- .with_context(|| format!("missing stylesheet field {field}"))? = json!(value);
138
- }
139
- Ok(())
140
- }
141
-
142
- fn measurement(snapshots: &[Snapshot], html: &str, marker: &str) -> Result<(String, String)> {
143
- let sources = sources(html);
144
- for snapshot in snapshots {
145
- let Ok(sheets) = snapshot.replay(&sources) else {
146
- continue;
147
- };
148
- let mut marker_text = None;
149
- let mut measured = Vec::new();
150
- for (index, rules) in sheets.iter().enumerate() {
151
- let text = rules.join("|");
152
- let indexed = format!("{text}{index}");
153
- if text.contains(marker) {
154
- ensure!(
155
- marker_text.replace(indexed).is_none(),
156
- "multiple marker stylesheets"
157
- );
158
- } else {
159
- measured.push(indexed);
160
- }
161
- }
162
- let marker_text = marker_text.context("the current marker stylesheet is absent")?;
163
- let digest = format!("{:x}", Sha256::digest(measured.join("|").as_bytes()));
164
- return Ok((digest[..32].to_owned(), marker_text));
165
- }
166
- bail!("no captured stylesheet revision matches the current iframe")
167
- }
168
-
169
- #[cfg(test)]
170
- mod tests {
171
- use super::*;
172
-
173
- #[test]
174
- fn refreshes_names_in_browser_serialized_rules_without_running_a_browser() {
175
- let snapshot = Snapshot {
176
- sheets: vec![Sheet {
177
- source: ".Abcd1{margin:0}.Abcd1 .Efgh2{color:red}".into(),
178
- rules: vec![
179
- ".Abcd1 { margin: 0px; }".into(),
180
- ".Abcd1 .Efgh2 { color: red; }".into(),
181
- ],
182
- }],
183
- };
184
- assert_eq!(
185
- snapshot
186
- .replay(&[".Ijkl3{margin:0}.Ijkl3 .Mnop4{color:red}".into()])
187
- .unwrap(),
188
- [vec![
189
- ".Ijkl3 { margin: 0px; }",
190
- ".Ijkl3 .Mnop4 { color: red; }"
191
- ]]
192
- );
193
- assert!(
194
- snapshot
195
- .replay(&[".Ijkl3{margin:1}.Ijkl3 .Mnop4{color:red}".into()])
196
- .is_err()
197
- );
198
- assert!(
199
- snapshot
200
- .replay(&[".Ijkl3{margin:0}.Mnop4 .Qrst5{color:red}".into()])
201
- .is_err()
202
- );
203
- assert!(
204
- snapshot
205
- .replay(&[".Ijkl3{margin:0}.Ijkl3 .Ijkl3{color:red}".into()])
206
- .is_err()
207
- );
208
- assert!(snapshot.replay(&[]).is_err());
209
- }
210
-
211
- #[test]
212
- fn measures_current_rules_and_excludes_the_marker_sheet() {
213
- let html = "<style>.Abcd1{color:red}</style><style>.Efgh2{margin:0}</style>";
214
- let snapshot = Snapshot::capture(
215
- html,
216
- vec![
217
- vec![".Abcd1 { color: red; }".into()],
218
- vec![".Efgh2 { margin: 0px; }".into()],
219
- ],
220
- )
221
- .unwrap();
222
- let fresh = "<style>.Ijkl3{color:red}</style><style>.Mnop4{margin:0}</style>";
223
- let (hash, marker) = measurement(std::slice::from_ref(&snapshot), fresh, ".Mnop4").unwrap();
224
- assert_eq!(
225
- hash,
226
- format!("{:x}", Sha256::digest(b".Ijkl3 { color: red; }0"))[..32]
227
- );
228
- assert_eq!(marker, ".Mnop4 { margin: 0px; }1");
229
- assert!(measurement(&[snapshot], fresh, ".Missing5").is_err());
230
- assert!(measurement(&[], fresh, ".Mnop4").is_err());
231
- assert!(Snapshot::capture(html, vec![]).is_err());
232
- let duplicate = Snapshot::capture(
233
- "<style>.Abcd1{color:red}</style><style>.Abcd1{margin:0}</style>",
234
- vec![
235
- vec![".Abcd1 { color: red; }".into()],
236
- vec![".Abcd1 { margin: 0px; }".into()],
237
- ],
238
- )
239
- .unwrap();
240
- assert!(
241
- measurement(
242
- &[duplicate],
243
- "<style>.Abcd1{color:red}</style><style>.Abcd1{margin:0}</style>",
244
- ".Abcd1"
245
- )
246
- .is_err()
247
- );
248
- }
249
- }