Browser Support Test — JavaScript source
Audit your real browser against 30 modern web capabilities - WebGPU, WASM, WebAuthn, Service Workers and more. Get a weighted readiness score, copy-paste detection snippets, and a shareable link.
This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
/**
* Browser Support Test - feature probes, weighted score, and share codec.
*
* Language: JavaScript (ES module, browser globals + base64url)
* CosmoDev polyglot showcase port of the `browser-support-test` tool.
* Ported from src/tools/BrowserSupportTest.tsx (+ browser-support/* modules) -
* display source, part of CosmoDev's polyglot tool pages.
*
* Probes a {nav, win, doc} env for web capabilities, reduces the verdicts to a
* weighted readiness score (0-100) + tier, and packs them into a compact
* base64url share token. Detection is pure API-presence probing; a throwing
* probe degrades to 'unknown'. This is the browser-native logic - the
* Go/Rust/PHP/Python ports in this set show the analogous runtime pattern.
* Catalog below is a representative subset (the live tool ships ~30 checks).
*/
// status → contribution (0..1) × weight.
const CONTRIBUTION = { supported: 1, limited: 0.5, 'needs-https': 0.3, unsupported: 0, unknown: 0 };
/** Weighted readiness 0-100; ids absent from `checks` are ignored. */
export function computeScore(results, checks) {
const wm = new Map(checks.map((c) => [c.id, c.weight]));
let earned = 0, possible = 0;
for (const r of results) {
const w = wm.get(r.id);
if (w === undefined) continue;
earned += CONTRIBUTION[r.status] * w;
possible += w;
}
return possible === 0 ? 0 : Math.round((earned / possible) * 100);
}
// One char per check, in catalog order → a full audit fits in a URL.
const TO_CHAR = { supported: 's', limited: 'l', 'needs-https': 'h', unsupported: 'x', unknown: '?' };
const FROM_CHAR = { s: 'supported', l: 'limited', h: 'needs-https', x: 'unsupported', '?': 'unknown' };
export const packStatuses = (results) => results.map((r) => TO_CHAR[r.status] ?? '?').join('');
export const unpackStatuses = (s) => [...s].map((c) => FROM_CHAR[c] ?? 'unknown');
// base64url of UTF-8 bytes. btoa/atob + TextEncoder are global in browsers + Node ≥ 20.
function toBase64Url(str) {
const bytes = new TextEncoder().encode(str);
const bin = Array.from(bytes, (b) => String.fromCharCode(b)).join('');
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
function fromBase64Url(b64url) {
const bin = atob(b64url.replace(/-/g, '+').replace(/_/g, '/'));
return new TextDecoder().decode(Uint8Array.from(bin, (c) => c.charCodeAt(0)));
}
export function encodeResults(report) { return toBase64Url(JSON.stringify(report)); }
export function decodeResults(enc) {
if (!enc) return null;
try {
const p = JSON.parse(fromBase64Url(enc));
if (p && p.v === 1 && typeof p.s === 'string' && typeof p.score === 'number') return p;
return null;
} catch { return null; }
}
/** Readiness tier from a 0-100 score. */
export function tierFor(score) {
if (score >= 90) return { label: 'Fully modern', tone: 'text-success' };
if (score >= 75) return { label: 'Very capable', tone: 'text-success' };
if (score >= 55) return { label: 'Capable, minor gaps', tone: 'text-accent' };
if (score >= 35) return { label: 'Showing its age', tone: 'text-fg-muted' };
return { label: 'Legacy browser', tone: 'text-danger' };
}
/** Parse a UA string into "Chrome 130 · macOS" (display label only). */
export function browserLabel(ua) {
if (!ua) return 'Your browser';
let name = '', ver = '';
const pick = (re, n) => {
const m = re.exec(ua);
if (m) { name = n; ver = m[1]; return true; }
return false;
};
pick(/(?:firefox|fxios)\/(\d+)/i, 'Firefox') ||
pick(/(?:edg|edge)\/(\d+)/i, 'Edge') ||
pick(/(?:opr|opera)\/(\d+)/i, 'Opera') ||
pick(/(?:chrome|crios)\/(\d+)/i, 'Chrome') ||
pick(/version\/(\d+)[.\d]* safari/i, 'Safari') ||
pick(/safari\/(\d+)/i, 'Safari');
const os =
/mac os x/i.test(ua) ? 'macOS' : /windows nt/i.test(ua) ? 'Windows'
: /android/i.test(ua) ? 'Android' : /(iphone|ipad|cpu os)/i.test(ua) ? 'iOS'
: /linux/i.test(ua) ? 'Linux' : '';
const main = name ? `${name}${ver ? ' ' + ver : ''}` : 'Your browser';
return os ? `${main} · ${os}` : main;
}
export const createEnv = () => ({ nav: globalThis.navigator, win: globalThis, doc: globalThis.document });
const S = 'supported', X = 'unsupported';
function canvasCtx(env, type) {
try {
const c = env.doc.createElement('canvas');
return !!c && typeof c.getContext === 'function' && !!c.getContext(type);
} catch { return false; }
}
export const FEATURE_CHECKS = [
{ id: 'canvas-2d', title: 'Canvas 2D', category: 'Graphics', weight: 2, explainer: '2D drawing surface.', codeSnippet: `!!document.createElement('canvas').getContext('2d')`, detect: (e) => (canvasCtx(e, '2d') ? S : X) },
{ id: 'webgpu', title: 'WebGPU', category: 'Graphics', weight: 3, explainer: 'Next-gen GPU + compute API.', codeSnippet: `'gpu' in navigator`, detect: (e) => ('gpu' in e.nav ? S : X) },
{ id: 'webassembly', title: 'WebAssembly', category: 'Compute', weight: 3, explainer: 'Native-speed compiled code.', codeSnippet: `typeof WebAssembly === 'object'`, detect: (e) => (typeof e.win.WebAssembly === 'object' ? S : X) },
{ id: 'indexeddb', title: 'IndexedDB', category: 'Storage', weight: 3, explainer: 'Client-side database.', codeSnippet: `'indexedDB' in window`, detect: (e) => ('indexedDB' in e.win ? S : X) },
{ id: 'service-worker', title: 'Service Worker', category: 'Connectivity', weight: 3, explainer: 'Offline background scripts.', codeSnippet: `'serviceWorker' in navigator`, detect: (e) => ('serviceWorker' in e.nav ? S : X) },
{ id: 'crypto-subtle', title: 'WebCrypto (subtle)', category: 'Security', weight: 3, explainer: 'Native hashing/signing.', codeSnippet: `!!window.crypto?.subtle`, detect: (e) => (e.win.crypto?.subtle ? S : X) },
{ id: 'secure-context', title: 'Secure Context (HTTPS)', category: 'Security', weight: 2, explainer: 'Unlocks camera, SW, crypto.', codeSnippet: `window.isSecureContext`, detect: (e) => (e.win.isSecureContext === true ? S : { status: 'needs-https' }) },
{ id: 'css-has', title: ':has() Selector', category: 'Platform', weight: 2, explainer: 'Parent selector.', codeSnippet: `CSS.supports('selector', ':has(*)')`, detect: (e) => (typeof e.win.CSS?.supports === 'function' && e.win.CSS.supports('selector', ':has(*)') ? S : X) },
];
// Run probes; a throw degrades to 'unknown' so one failure can't abort the audit.
function resolve(id, out) {
return typeof out === 'string' ? { id, status: out } : { id, status: out.status, ...(out.detail ? { detail: out.detail } : {}) };
}
export function runChecks(checks, env) {
return checks.map((c) => {
try { return resolve(c.id, c.detect(env)); } catch { return { id: c.id, status: 'unknown' }; }
});
}
export const runAllChecks = (env) => runChecks(FEATURE_CHECKS, env);
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →