// WAI's browser sink (extensions/browser-sink.md) in this page, as `wai conform sink --suite // browser` holds a sink to its corpus (live-conformance/browser, §12): each call is given with // its answer withheld, and the page answers it with its own code, the normative parts by // wai-live-web's wasm (`routeReport`, `chooseReport`, `evaluateConstraints`, `readBinding`, // `frameTier`, `frameState`) and the side table by the page's own JavaScript // (web/room/binding.js, `runSequence`). The answer is an object of the members the corpus states // for the call's family, written as one JSON text in RFC 8785 form and a line feed. // // e2e/conform_sink.mjs drives this page in a browser and serves its answers to `wai conform`. // Everything it reads is on `window.waiBrowserSink`: `ready` (a promise), `answer(call)` and // `version`. import initWai, * as wai from "../pkg/wai_live_web.js"; import { runSequence } from "../room/binding.js"; const parse = (s) => JSON.parse(s); const text = (v) => JSON.stringify(v); // A binding's canonical form (§7.1), as the page writes it into the side table. const canonicalOf = (b) => parse(wai.readBinding(text(b))).canonical; // The page's answer to one call: the members the corpus states for its family. export function answer(call) { switch (call.call) { case "routes": { const r = parse(wai.routeReport(text(call.report))); return r.outcome === "routed" ? { outcome: r.outcome, reason: null, table: r.table } : { outcome: r.outcome, reason: r.reason, table: null }; } case "choices": { const c = parse(wai.chooseReport(text(call.report))); if (c.outcome !== "chosen") return { outcome: c.outcome, reason: c.reason, table: null, choices: null }; const r = parse(wai.routeReport(text(call.report))); return { outcome: c.outcome, reason: null, table: r.outcome === "routed" ? r.table : null, choices: c.choices }; } case "constraints": { const e = parse(wai.evaluateConstraints(text(call.sets), text(call.params))); return { outcome: e.outcome, reason: e.reason }; } case "bindings": { const b = parse(wai.readBinding(text(call.value))); return b.outcome === "read" ? { outcome: b.outcome, reason: null, canonical: b.canonical, tier: b.tier } : { outcome: b.outcome, reason: b.reason, canonical: null, tier: null }; } case "frame_tiers": return { tier: wai.frameTier(call.route, call.route_tier, call.format_null, call.altered) }; case "frames": return { result: parse(wai.frameState(text(call.binding), call.object, call.chain, call.provenance)) }; case "tables": return { result: runSequence(call.bound, call.ops, canonicalOf) }; default: throw new Error(`${call.call}: no call of this sink`); } } // RFC 8785: no insignificant whitespace, members sorted by their names' UTF-16 code units // (JavaScript's own order of strings), and numbers and strings as ECMAScript writes them. export function jcs(v) { if (v === null || typeof v !== "object") { if (typeof v === "number" && !Number.isFinite(v)) throw new Error(`${v}: a number RFC 8785 does not write`); if (v === undefined) throw new Error("undefined: not a JSON value"); return JSON.stringify(v); } if (Array.isArray(v)) return `[${v.map(jcs).join(",")}]`; return `{${Object.keys(v).filter((k) => v[k] !== undefined).sort().map((k) => `${JSON.stringify(k)}:${jcs(v[k])}`).join(",")}}`; } const ready = initWai().then(() => true); window.waiBrowserSink = { ready, version: navigator.userAgent, answer: (call) => `${jcs(answer(call))}\n`, };