-
-
Notifications
You must be signed in to change notification settings - Fork 164
/
transition_timing_functions.sql
145 lines (129 loc) · 3.83 KB
/
transition_timing_functions.sql
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#standardSQL
CREATE TEMPORARY FUNCTION getTimingFunctions(css STRING)
RETURNS ARRAY<STRUCT<fn STRING, freq INT64>>
LANGUAGE js
OPTIONS (library = "gs://httparchive/lib/css-utils.js")
AS '''
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 are normalized to ms.
durations: {}
};
const easings = /step(s|-start|-end)|ease((-in)?(-out)?)|linear|cubic-bezier/;
function parseDuration(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, parseDuration(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)) {
incrementByKey(ret.durations, parseDuration(times.groups.duration));
}
}
}, {
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.timing_functions).map(([fn, freq]) => {
return {fn, freq};
});
} catch (e) {
return [];
}
''';
WITH totals AS (
SELECT
_TABLE_SUFFIX AS client,
COUNT(0) AS total_pages
FROM
`httparchive.summary_pages.2022_07_01_*` -- noqa: L062
GROUP BY
client
)
SELECT
*
FROM (
SELECT
client,
fn,
COUNT(DISTINCT page) AS pages,
ANY_VALUE(total_pages) AS total_pages,
COUNT(DISTINCT page) / ANY_VALUE(total_pages) AS pct_pages,
SUM(freq) AS freq,
SUM(SUM(freq)) OVER (PARTITION BY client) AS total,
SUM(freq) / SUM(SUM(freq)) OVER (PARTITION BY client) AS pct
FROM (
SELECT
client,
page,
transition.fn,
transition.freq
FROM
`httparchive.almanac.parsed_css`,
UNNEST(getTimingFunctions(css)) AS transition
WHERE
date = '2022-07-01' AND
# Limit the size of the CSS to avoid OOM crashes.
LENGTH(css) < 0.1 * 1024 * 1024)
JOIN
totals
USING
(client)
GROUP BY
client,
fn)
WHERE
pct >= 0.01
ORDER BY
pct DESC