// Chrome 154 vs 155: paste into the DevTools console on any https:// page and press Enter.
// Each line prints "<change> <check> = <value>". Expected values: clearcotelabs.com/chrome-releases/155
(async () => {
const checks = [
["grease-brand", () => navigator.userAgentData.brands.map((b) => `${b.brand};v=${b.version}`).join(', ')],
["jpeg-xl", () => ImageDecoder.isTypeSupported('image/jxl')],
["webcrypto-modern-algorithms", () => typeof SubtleCrypto.supports],
["webcrypto-modern-algorithms", () => crypto.subtle.generateKey('ChaCha20-Poly1305', false, ['encrypt']).then((k) => k.algorithm.name, (e) => e.name)],
["webcrypto-modern-algorithms", () => Object.getOwnPropertyNames(SubtleCrypto.prototype).length],
["new-css-properties", () => CSS.supports('margin-trim', 'block')],
["new-css-properties", () => CSS.supports('text-decoration-skip-spaces', 'all')],
["permissions-policy-features", () => ['digital-credentials-create', 'media-playback-while-not-visible', 'speaker-selection'].map((f) => document.featurePolicy.features().includes(f)).join()],
["permissions-policy-features", () => (() => { let read = false; navigator.credentials.create({ get digital() { read = true; } }).catch(() => {}); return read; })()],
["temporal-cjk-format", () => Temporal.PlainDate.from('2026-08-27').toLocaleString('ja-JP', { dateStyle: 'long' })],
["temporal-cjk-format", () => ((f) => f.format(Temporal.PlainDate.from('2026-08-27')) === f.format(new Date(Date.UTC(2026, 7, 27, 12))))(new Intl.DateTimeFormat('ja-JP', { dateStyle: 'long', timeZone: 'UTC' }))],
["dynamic-range-boolean", () => matchMedia('(dynamic-range)').matches],
["css-symbols-function", () => CSS.supports('list-style-type', 'symbols(cyclic "*")')],
["css-alpha-requires-alpha", () => CSS.supports('color', 'alpha(from red)')],
["color-mix-zero-percent", () => CSS.supports('color', 'color-mix(in srgb, red 0%, blue 0%)')],
["intersection-observer-units", () => (() => { try { return new IntersectionObserver(() => {}, { rootMargin: '1in' }).rootMargin; } catch (e) { return e.name; } })()],
["blob-url-range", () => fetch(URL.createObjectURL(new Blob(['abc'])), { headers: { Range: 'bytes=x' } }).then((r) => r.status, (e) => e.name)],
["audio-buffer-loop", () => (async () => { const c = new OfflineAudioContext(1, 16, 48e3); const b = c.createBuffer(1, 7, 48e3); b.getChannelData(0).set([1, 2, 3, 4, 5, 6, 7]); const s = c.createBufferSource(); s.buffer = b; s.loop = true; s.connect(c.destination); s.start(); const r = await c.startRendering(); return Array.from(r.getChannelData(0)).join(); })()],
["svg-a-referrerpolicy", () => (() => { const a = document.createElementNS('http://www.w3.org/2000/svg', 'a'); a.setAttribute('referrerpolicy', 'bogus'); return a.referrerPolicy; })()],
["dtmf-event-init", () => RTCDTMFToneChangeEvent.length],
["intl-locale-variants", () => ((f) => { try { return f(); } catch (e) { return e.name; } })(() => new Intl.Locale('en', { variants: 'fonipa_scouse' }).toString())],
["intl-week-year", () => 'year' in new Intl.DateTimeFormat('te-u-ca-buddhist', { year: 'numeric', month: 'long' }).resolvedOptions()],
["harfbuzz-u180f", () => (() => { const x = document.createElement('canvas').getContext('2d'); x.font = '20px sans-serif'; return x.measureText('a' + String.fromCharCode(6159) + 'b').width === x.measureText('ab').width; })()],
["document-named-object", () => (() => { const o = document.body.appendChild(document.createElement('object')); o.name = 'cc155probe'; o.appendChild(document.createElement('embed')); const r = document.cc155probe === o; o.remove(); return r; })()],
["svg-empty-attribute", () => (() => { const t = document.createElementNS('http://www.w3.org/2000/svg', 'feTurbulence'); t.setAttribute('numOctaves', ''); return t.numOctaves.baseVal; })()],
["css-perspective-clamp", () => new CSSPerspective(CSS.px(0.5)).toMatrix().m34],
["fetchlater-empty-body", () => (() => { const c = new AbortController(); try { fetchLater(location.href, { method: 'POST', body: '', signal: c.signal }); return 'ok'; } catch (e) { return e.name; } finally { c.abort(); } })()],
["first-line-metrics", () => (() => { const h = document.body.appendChild(document.createElement('div')); const r = h.attachShadow({ mode: 'open' }); const s = new CSSStyleSheet(); s.replaceSync('p { font: 16px serif; margin: 0 } p::first-line { font-size: 40px; line-height: 10px }'); r.adoptedStyleSheets = [s]; const p = r.appendChild(document.createElement('p')); p.textContent = 'x'; const v = p.getBoundingClientRect().height === 10; h.remove(); return v; })()],
["text-decoration-thickness-transition", () => (() => { const d = document.body.appendChild(document.createElement('div')); d.style.textDecorationThickness = '0px'; getComputedStyle(d).textDecorationThickness; d.style.transition = 'all 10s'; d.style.textDecorationThickness = '10px'; const n = d.getAnimations().length; d.remove(); return n; })()],
["stroke-dasharray-additive", () => (() => { const s = document.body.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'svg')); s.style.strokeDasharray = '1'; const a = s.animate({ strokeDasharray: ['2', '2'] }, { duration: 1e3, composite: 'add' }); a.pause(); const v = getComputedStyle(s).strokeDasharray; a.cancel(); s.remove(); return v; })()],
["body-margin-pixel-length", () => new Promise((r) => { const f = document.createElement('iframe'); f.srcdoc = '<body marginwidth="10.5">'; f.onload = () => { r(getComputedStyle(f.contentDocument.body).marginLeft); f.remove(); }; document.body.appendChild(f); })],
["webgpu-snorm10", () => navigator.gpu.requestAdapter().then((a) => a.requestDevice()).then(async (d) => { d.pushErrorScope('validation'); const m = d.createShaderModule({ code: '@vertex fn v(@location(0) p: vec4f) -> @builtin(position) vec4f { return p; } @fragment fn f() -> @location(0) vec4f { return vec4f(1); }' }); d.createRenderPipeline({ layout: 'auto', vertex: { module: m, buffers: [{ arrayStride: 4, attributes: [{ format: 'snorm10-10-10-2', offset: 0, shaderLocation: 0 }] }] }, fragment: { module: m, targets: [{ format: 'rgba8unorm' }] } }); const e = await d.popErrorScope(); d.destroy(); return e ? e.constructor.name : 'ok'; })],
["gamepad-slots", () => navigator.getGamepads().length],
["soft-navigation-order", () => Object.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('PerformanceTiming') + 1]],
["soft-navigation-order", () => Object.getOwnPropertyNames(window)[Object.getOwnPropertyNames(window).indexOf('InterestEvent') + 1]],
["soft-navigation-order", () => Object.getOwnPropertyNames(PerformanceEntry.prototype).indexOf('navigationId') < Object.getOwnPropertyNames(PerformanceEntry.prototype).indexOf('toJSON')],
];
const out = [];
for (const [id, f] of checks) {
let v;
try { v = await f(); } catch (e) { v = "throws " + e.name; }
const expr = f.toString().replace(/^\(\)\s*=>\s*/, "");
out.push(id.padEnd(28) + expr + " = " + (typeof v === "string" ? JSON.stringify(v) : String(v)));
}
console.log(out.join("\n"));
})();