-
-
Notifications
You must be signed in to change notification settings - Fork 213
Expand file tree
/
Copy pathtransition_delays.sql
More file actions
131 lines (113 loc) · 3.61 KB
/
Copy pathtransition_delays.sql
File metadata and controls
131 lines (113 loc) · 3.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#standardSQL
# Transition and animation delays
CREATE TEMPORARY FUNCTION getTransitionDelays(css STRING)
RETURNS ARRAY<STRING>
LANGUAGE js
OPTIONS (library = "gs://httparchive/lib/css-utils.js")
AS r'''
try {
function compute(ast) {
let ret = {
properties: new Set(),
animation_names: new Set(),
timing_functions: {},
// to calculate avg, median etc in the SQL. All durations and delays are normalized to ms.
durations: {},
delays: {}
};
const easings = /step(s|-start|-end)|ease((-in)?(-out)?)|linear|cubic-bezier/;
function parseTime(duration) {
let num = parseFloat(duration);
let unit = duration.endsWith("ms")? "ms" : "s";
return unit === "s"? num * 1000 : num;
}
walkDeclarations(ast, ({property, value}) => {
if (property === "transition-property") {
value.split(/\s*,\s*/).forEach(p => ret.properties.add(p));
}
else if (property.endsWith("tion-timing-functon")) {
let names = value.match(/^([a-z-]+)/g);
for (let name of names) {
if (/^(jump(-start|-end|-none|-both)|start|end)$/.test(name)) {
// Drop steps() params
continue;
}
incrementByKey(ret.timing_functions, name);
}
}
else if (property.endsWith("-duration")) {
incrementByKey(ret.durations, parseTime(value));
}
else if (property.endsWith("-delay")) {
incrementByKey(ret.delays, parseTime(value));
}
else if (property === "transition" || property === "animation") {
// Extract property name and timing function
let keywords = (value.match(/(^|\s)(d|r|[cr]?x|[cr]?y|[a-z-]{3,})(?=\s|$|\()/g) || []);
for (let keyword of keywords) {
keyword = keyword.trim();
if (/^(jump(-start|-end|-none|-both)|start|end)$/.test(keyword)) {
// Drop steps() params
continue;
}
if (easings.test(keyword)) {
incrementByKey(ret.timing_functions, keyword);
}
else if (property === "transition") {
ret.properties.add(keyword);
}
}
// Extract durations
for (let times of value.matchAll(/(?<duration>[\d.]+m?s)(\s+(?<delay>[\d.]+m?s))?/g)) {
let {duration, delay} = times.groups;
if (duration) {
incrementByKey(ret.durations, parseTime(duration));
if (delay) {
incrementByKey(ret.delays, parseTime(delay));
}
}
}
}
}, {
properties: /^(transition|animation)(?=$|-)/g,
not: {
values: ["inherit", "initial", "unset", "revert", /\bvar\(--/]
}
})
// Animation names
walkRules(ast, rule => {
ret.animation_names.add(rule.name);
}, {type: "keyframes"});
ret.properties = [...new Set(ret.properties)];
ret.animation_names = [...ret.animation_names];
ret.durations = sortObject(ret.durations);
ret.timing_functions = sortObject(ret.timing_functions);
return ret;
}
const ast = JSON.parse(css);
let transitions = compute(ast);
return Object.entries(transitions.delays).filter(([delay]) => {
return !isNaN(+delay);
}).flatMap(([delay, freq]) => {
return new Array(freq).fill(delay);
});
} catch (e) {
return [];
}
''';
SELECT
percentile,
client,
APPROX_QUANTILES(delay, 1000)[OFFSET(percentile * 10)] AS delay
FROM
`httparchive.almanac.parsed_css`,
UNNEST(getTransitionDelays(css)) AS delay,
UNNEST([10, 25, 50, 75, 90, 100]) AS percentile
WHERE
date = '2022-07-01' -- noqa: CV09
GROUP BY
percentile,
client
ORDER BY
percentile,
client