-
-
Notifications
You must be signed in to change notification settings - Fork 169
/
gradient_properties.sql
179 lines (153 loc) · 6.88 KB
/
gradient_properties.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#standardSQL
CREATE TEMPORARY FUNCTION getGradientUsageBeyondBg(css STRING)
RETURNS ARRAY<STRING>
LANGUAGE js
OPTIONS (library = "gs://httparchive/lib/css-utils.js")
AS '''
try {
function compute(ast) {
let ret = {
functions: {}, // usage by gradient function
properties: {}, // usage by property
max_stops: 0,
max_stops_gradient: [],
two_positions: 0,
hints: 0,
hard_stops: 0
};
let stopCount = [];
const keywords = [
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse",
"chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta",
"darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet",
"deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray",
"green", "greenyellow", "grey", "honeydew", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
"lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey",
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen",
"mediumslateblue", "mediumspringgreen", "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navy", "oldlace",
"olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum",
"powderblue", "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
"slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke",
"yellow", "yellowgreen", "transparent", "currentcolor",
"ActiveBorder", "ActiveCaption", "AppWorkspace", "Background", "ButtonFace", "ButtonHighlight", "ButtonShadow", "ButtonText", "CaptionText",
"GrayText", "Highlight", "HighlightText", "InactiveBorder", "InactiveCaption", "InactiveCaptionText", "InfoBackground", "InfoText",
"Menu", "MenuText", "Scrollbar", "ThreeDDarkShadow", "ThreeDFace", "ThreeDHighlight", "ThreeDLightShadow", "ThreeDShadow", "Window", "WindowFrame", "WindowText"
];
const keywordRegex = RegExp(`\\b(?<!\\-)(?:${keywords.join("|")})\\b`, "gi");
walkDeclarations(ast, ({property, value}) => {
if (value.length > 1000) return;
for (let gradient of extractFunctionCalls(value, {names: /-gradient$/})) {
let {name, args} = gradient;
incrementByKey(ret.functions, name);
incrementByKey(ret.properties, property.indexOf("--") === 0? "--*" : property);
// Light color stop parsing
// Collapse nested function calls into empty function calls
for (let i=0, lastIndex; (i = args.indexOf("(", lastIndex + 1)) > -1; ) {
let a = parsel.gobbleParens(args, i);
args = args.substring(0, i) + "()" + args.substring(i + a.length);
lastIndex = i;
}
let stops = args.split(/\\s*,\\s*/);
// Remove first arg if it's params and not a color stop
if (/^(at|to|from)\\s|ellipse|circle|(?:farthest|closest)-(?:side|corner)|[\\d.]+(deg|grad|rad|turn)/.test(stops[0])) {
stops.shift();
}
stopCount.push(stops.length);
if (ret.max_stops < stops.length) {
ret.max_stops = stops.length;
ret.max_stops_gradient = [];
}
if (ret.max_stops === stops.length) {
ret.max_stops_gradient.push(value.substring(...gradient.pos));
}
// The rest will fail if we have variables with fallbacks in the args so let's just skip those altogether for now
if (/\\bvar\\(/.test(args)) {
continue;
}
// Separate color and position(s)
stops = stops.map(s => {
if (/\\s/.test(s)) {
// Even though the spec doesn't mandate an order, all browsers implement the older grammar
// with the position after the color, so placing the position before the color must be extremely rare.
let parts = s.split(/\\s+/);
return {color: parts[0], pos: parts.slice(1)};
}
// We only have one thing, is it a color or a position?
if (/#[a-f0-9]+|(?:rgba?|hsla?|color)\\(/.test(s) || keywordRegex.test(s)) {
keywordRegex.lastIndex = 0;
return {color: s};
}
return {pos: s};
});
for (let i=0; i<stops.length; i++) {
let s = stops[i];
if (s.pos && s.pos.length > 1) {
ret.two_positions++;
}
if (!s.color) {
// No color, it must be a hint
ret.hints++;
continue;
}
let prev = stops[i - 1];
// Calculate hard stops
if (prev && prev.pos && s.pos && !s.pos.join("").includes("calc()")) {
let pos = s.pos[0];
let prevPos = prev.pos[prev.pos.length === 1? 0 : 1];
if (parseFloat(pos) === 0 || pos === prevPos) {
ret.hard_stops++;
}
}
}
}
}, {
properties: /^--|-image$|^background$|^content$/
});
// Calculate average and max number of stops
stopCount = stopCount.sort((a, b) => b - a);
ret.avg_stops = stopCount.reduce((a, c) => a + c, 0) / stopCount.length;
let mi = (stopCount.length - 1) / 2;
ret.median_stops = stopCount.length % 2? stopCount[mi] : (stopCount[Math.floor(mi)] + stopCount[Math.ceil(mi)]) / 2;
ret.stop_count = stopCount;
return ret;
}
const ast = JSON.parse(css);
let gradient = compute(ast);
return Object.keys(gradient.properties);
} catch (e) {
return [];
}
''';
SELECT
client,
property,
COUNT(DISTINCT page) AS pages,
total,
COUNT(DISTINCT page) / total AS pct
FROM (
SELECT DISTINCT
client,
page,
property
FROM
`httparchive.almanac.parsed_css`,
UNNEST(getGradientUsageBeyondBg(css)) AS property
WHERE
date = '2021-07-01' AND
property IS NOT NULL)
JOIN (
SELECT
_TABLE_SUFFIX AS client,
COUNT(0) AS total
FROM
`httparchive.summary_pages.2021_07_01_*`
GROUP BY
client)
USING
(client)
GROUP BY
client,
property,
total
ORDER BY
pct DESC