Skip to content

Commit 886d051

Browse files
authored
Origin Trial metadata and validation (#35)
1 parent 17ec025 commit 886d051

File tree

3 files changed

+102
-30
lines changed

3 files changed

+102
-30
lines changed

capo.js

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ function isPrefetchPrerender(element) {
139139
return element.matches('link:is([rel=prefetch], [rel=dns-prefetch], [rel=prerender])');
140140
}
141141

142+
function isOriginTrial(element) {
143+
return element.matches('meta[http-equiv="origin-trial"i]');
144+
}
145+
142146
function getWeight(element) {
143147
for ([id, detector] of Object.entries(ElementDetectors)) {
144148
if (detector(element)) {
@@ -208,6 +212,48 @@ function getLoggableElement(element) {
208212
return element;
209213
}
210214

215+
// Adapted from https://glitch.com/~ot-decode.
216+
function decodeToken(token) {
217+
const buffer = new Uint8Array([...atob(token)].map(a => a.charCodeAt(0)));
218+
const view = new DataView(buffer.buffer)
219+
const length = view.getUint32(65, false)
220+
const payload = JSON.parse((new TextDecoder()).decode(buffer.slice(69, 69 + length)));
221+
payload.expiry = new Date(payload.expiry * 1000);
222+
return payload;
223+
}
224+
225+
function logElement({viz, weight, element, isValid, omitPrefix = false}) {
226+
if (!omitPrefix) {
227+
viz.visual = `${LOGGING_PREFIX}${viz.visual}`;
228+
}
229+
230+
let loggingLevel = 'log';
231+
const args = [viz.visual, viz.style, weight + 1, element];
232+
233+
if (isStaticHead && !isValid) {
234+
loggingLevel = 'warn';
235+
args.push('❌ invalid element');
236+
}
237+
238+
if (isOriginTrial(element)) {
239+
const token = element.getAttribute('content');
240+
try {
241+
const payload = decodeToken(token);
242+
args.push(payload);
243+
244+
if (payload.expiry < new Date()) {
245+
loggingLevel = 'warn';
246+
args.push('❌ expired');
247+
}
248+
} catch {
249+
loggingLevel = 'warn';
250+
args.push('❌ invalid token');
251+
}
252+
}
253+
254+
console[loggingLevel](...args);
255+
}
256+
211257
function logWeights() {
212258
const headWeights = getHeadWeights();
213259
const actualViz = visualizeWeights(headWeights.map(([_, weight]) => weight));
@@ -219,11 +265,7 @@ function logWeights() {
219265
console.groupCollapsed(`${LOGGING_PREFIX}Actual %c<head>%c order\n${actualViz.visual}`, 'font-family: monospace', 'font-family: inherit', ...actualViz.styles);
220266
headWeights.forEach(([element, weight, isValid]) => {
221267
const viz = visualizeWeight(weight);
222-
if (isStaticHead && !isValid) {
223-
console.warn(viz.visual, viz.style, weight + 1, element, '❌ invalid element');
224-
} else {
225-
console.log(viz.visual, viz.style, weight + 1, element);
226-
}
268+
logElement({viz, weight, element, isValid, omitPrefix: true});
227269
});
228270
console.log('Actual %c<head>%c element', 'font-family: monospace', 'font-family: inherit', head);
229271
console.groupEnd();
@@ -237,11 +279,7 @@ function logWeights() {
237279
const sortedHead = document.createElement('head');
238280
sortedWeights.forEach(([element, weight, isValid]) => {
239281
const viz = visualizeWeight(weight);
240-
if (isStaticHead && !isValid) {
241-
console.warn(viz.visual, viz.style, weight + 1, element, '❌ invalid element');
242-
} else {
243-
console.log(viz.visual, viz.style, weight + 1, element);
244-
}
282+
logElement({viz, weight, element, isValid, omitPrefix: true});
245283
sortedHead.appendChild(element.cloneNode(true));
246284
});
247285
console.log('Sorted %c<head>%c element', 'font-family: monospace', 'font-family: inherit', sortedHead);

crx/capo.js

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ async function handleCapoClick(event) {
4444
await chrome.scripting.executeScript({
4545
target: {tabId: tab.id},
4646
args: [{
47-
fn: 'logElement',
47+
fn: 'logElementFromSelector',
4848
args: [weight, selector, innerHTML, isValid]
4949
}],
5050
func: capo
@@ -53,7 +53,7 @@ async function handleCapoClick(event) {
5353

5454
async function capo({fn, args}={}) {
5555
const FN_EXPORTS = {
56-
logElement
56+
logElementFromSelector
5757
};
5858

5959
const ElementWeights = {
@@ -196,6 +196,10 @@ async function capo({fn, args}={}) {
196196
function isPrefetchPrerender(element) {
197197
return element.matches('link:is([rel=prefetch], [rel=dns-prefetch], [rel=prerender])');
198198
}
199+
200+
function isOriginTrial(element) {
201+
return element.matches('meta[http-equiv="origin-trial"i]');
202+
}
199203

200204
function getWeight(element) {
201205
for ([id, detector] of Object.entries(ElementDetectors)) {
@@ -230,19 +234,57 @@ async function capo({fn, args}={}) {
230234

231235
return {visual, style};
232236
}
233-
237+
238+
// Adapted from https://glitch.com/~ot-decode.
239+
function decodeToken(token) {
240+
const buffer = new Uint8Array([...atob(token)].map(a => a.charCodeAt(0)));
241+
const view = new DataView(buffer.buffer)
242+
const length = view.getUint32(65, false)
243+
const payload = JSON.parse((new TextDecoder()).decode(buffer.slice(69, 69 + length)));
244+
payload.expiry = new Date(payload.expiry * 1000);
245+
return payload;
246+
}
247+
248+
function logElement({viz, weight, element, isValid, omitPrefix = false}) {
249+
if (!omitPrefix) {
250+
viz.visual = `${LOGGING_PREFIX}${viz.visual}`;
251+
}
252+
253+
let loggingLevel = 'log';
254+
const args = [viz.visual, viz.style, weight + 1, element];
255+
256+
if (isStaticHead && !isValid) {
257+
loggingLevel = 'warn';
258+
args.push('❌ invalid element');
259+
}
260+
261+
if (isOriginTrial(element)) {
262+
const token = element.getAttribute('content');
263+
try {
264+
const payload = decodeToken(token);
265+
args.push(payload);
266+
267+
if (payload.expiry < new Date()) {
268+
loggingLevel = 'warn';
269+
args.push('❌ expired');
270+
}
271+
} catch {
272+
loggingLevel = 'warn';
273+
args.push('❌ invalid token');
274+
}
275+
}
276+
277+
console[loggingLevel](...args);
278+
}
279+
234280
function logWeights() {
235281
const headWeights = getHeadWeights();
236282
const actualViz = visualizeWeights(headWeights.map(([_, weight]) => weight));
237283

238284
console.groupCollapsed(`${LOGGING_PREFIX}Actual %c<head>%c order\n${actualViz.visual}`, 'font-family: monospace', 'font-family: inherit', ...actualViz.styles);
239285
headWeights.forEach(([element, weight, isValid]) => {
240286
const viz = visualizeWeight(weight);
241-
if (isStaticHead && !isValid) {
242-
console.warn(viz.visual, viz.style, weight + 1, element, '❌ invalid element');
243-
} else {
244-
console.log(viz.visual, viz.style, weight + 1, element);
245-
}
287+
logElement({viz, weight, element, isValid, omitPrefix: true});
246288
});
247289
console.log('Actual %c<head>%c element', 'font-family: monospace', 'font-family: inherit', head);
248290
console.groupEnd();
@@ -256,11 +298,7 @@ async function capo({fn, args}={}) {
256298
const sortedHead = document.createElement('head');
257299
sortedWeights.forEach(([element, weight, isValid]) => {
258300
const viz = visualizeWeight(weight);
259-
if (isStaticHead && !isValid) {
260-
console.warn(viz.visual, viz.style, weight + 1, element, '❌ invalid element');
261-
} else {
262-
console.log(viz.visual, viz.style, weight + 1, element);
263-
}
301+
logElement({viz, weight, element, isValid, omitPrefix: true});
264302
sortedHead.appendChild(element.cloneNode(true));
265303
});
266304
console.log('Sorted %c<head>%c element', 'font-family: monospace', 'font-family: inherit', sortedHead);
@@ -329,18 +367,14 @@ async function capo({fn, args}={}) {
329367
return element;
330368
}
331369

332-
function logElement(weight, selector, innerHTML, isValid) {
370+
function logElementFromSelector(weight, selector, innerHTML, isValid) {
333371
weight = +weight;
334372
const viz = visualizeWeight(weight);
335373
let element = createElementFromSelector(selector);
336374
element.innerHTML = innerHTML;
337375
element = getLoggableElement(element);
338376

339-
if (isValid) {
340-
console.log(`${LOGGING_PREFIX}${viz.visual}`, viz.style, weight + 1, element);
341-
} else {
342-
console.warn(`${LOGGING_PREFIX}${viz.visual}`, viz.style, weight + 1, element, '❌ invalid element');
343-
}
377+
logElement({viz, weight, element, isValid});
344378
}
345379

346380
function isValidElement(element) {

crx/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifest_version": 3,
33
"name": "Capo: get your ﹤𝚑𝚎𝚊𝚍﹥ in order",
44
"description": "Visualize the optimal ordering of ﹤𝚑𝚎𝚊𝚍﹥ elements on any web page",
5-
"version": "1.2.1",
5+
"version": "1.3.0",
66
"permissions": [
77
"scripting",
88
"activeTab"

0 commit comments

Comments
 (0)