pub const VARS_JS: &str = "// The numbers an emitter hands the stylesheet, set where a policy allows it.\n//\n// A bar\'s value, a row\'s depth, a track\'s place and a split\'s share are custom\n// properties the rules read. A `style-src` without `\'unsafe-inline\'` refuses\n// every style attribute, so the emitters write the declarations into\n// `data-vars` instead, and this sets each one through the CSSOM, which the\n// policy does not police.\n//\n// A classic script in the head, not deferred, on purpose: the observer is\n// running before the parser reaches the body, so every element gets its\n// properties as it is inserted and the first paint is already right. A deferred\n// script would paint every chart flat and then grow it.\n//\n// Only custom properties. Applying any declaration found in markup would hand\n// back exactly what the policy refused, so a name that does not start with\n// `--` is skipped.\n//\n// It also keeps this device\'s density choice, for the same reason it is not\n// deferred. The choice lives in the browser rather than with the app, because\n// it is about what is pointing at this screen, and it is worn as an attribute\n// on the root that the geometry sheet keys a block on. A picker carrying\n// `data-density-choice` and no name is how a reader changes it: nothing is\n// submitted, the store is written, and the page redraws at the new density.\n(() => {\n \"use strict\";\n\n /** The attribute the emitters write: `makeover_webview::VARS_ATTR`. */\n const ATTR = \"data-vars\";\n\n /** `makeover_webview::DENSITY_STORE_KEY`. */\n const DENSITY_KEY = \"makeover-density\";\n /** `makeover_webview::DENSITY_CHOICE_ATTR`. */\n const DENSITY_CHOICE = \"data-density-choice\";\n /** `makeover_geometry::DENSITY_ATTRIBUTE`. */\n const DENSITY = \"data-density\";\n /** Every `makeover_geometry::Density::attribute_value`. */\n const DENSITIES = [\"pointer\", \"touch\"];\n\n /**\n * The stored choice, or \"\" for none.\n *\n * Storage can throw (a private window, a blocked origin) and can hold\n * anything, so both read as no choice: the page keeps what it detects.\n */\n const chosen = () => {\n try {\n const value = localStorage.getItem(DENSITY_KEY) ?? \"\";\n return DENSITIES.includes(value) ? value : \"\";\n } catch {\n return \"\";\n }\n };\n\n /** Wear a choice on the root, or hand the root back to detection. */\n const wear = (value) => {\n const root = document.documentElement;\n if (value) root.setAttribute(DENSITY, value);\n else root.removeAttribute(DENSITY);\n };\n\n /** Show this device\'s choice on a picker that has just arrived. */\n const show = (picker) => {\n picker.value = chosen();\n };\n\n wear(chosen());\n\n document.addEventListener(\"change\", (event) => {\n const picker = event.target;\n if (!(picker instanceof Element) || !picker.hasAttribute(DENSITY_CHOICE)) return;\n const value = DENSITIES.includes(picker.value) ? picker.value : \"\";\n try {\n if (value) localStorage.setItem(DENSITY_KEY, value);\n else localStorage.removeItem(DENSITY_KEY);\n } catch {\n // Unstored, still worn: the choice holds for this page.\n }\n wear(value);\n });\n\n /** What this script set on each element, so an update can take it back. */\n const set = new WeakMap();\n\n /** `--a: 1; --b: 2` as pairs, keeping only custom properties. */\n const pairs = (text) => {\n const found = new Map();\n for (const part of text.split(\";\")) {\n const at = part.indexOf(\":\");\n if (at < 0) continue;\n const name = part.slice(0, at).trim();\n if (!name.startsWith(\"--\")) continue;\n found.set(name, part.slice(at + 1).trim());\n }\n return found;\n };\n\n /**\n * Make the element\'s properties what its attribute says now.\n *\n * A morph that changes the attribute in place leaves the element, so a\n * property the new text no longer names is removed rather than kept from\n * the old one.\n */\n const apply = (el) => {\n const next = pairs(el.getAttribute(ATTR) ?? \"\");\n for (const name of set.get(el) ?? []) {\n if (!next.has(name)) el.style.removeProperty(name);\n }\n for (const [name, value] of next) el.style.setProperty(name, value);\n set.set(el, [...next.keys()]);\n };\n\n /** An inserted subtree: the node itself and everything under it. */\n const within = (node) => {\n if (node.nodeType !== Node.ELEMENT_NODE) return;\n if (node.hasAttribute(ATTR)) apply(node);\n for (const el of node.querySelectorAll(`[${ATTR}]`)) apply(el);\n if (node.hasAttribute(DENSITY_CHOICE)) show(node);\n for (const el of node.querySelectorAll(`[${DENSITY_CHOICE}]`)) show(el);\n };\n\n new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === \"attributes\") apply(record.target);\n else record.addedNodes.forEach(within);\n }\n }).observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [ATTR],\n });\n\n // Whatever the parser had already written before this ran.\n within(document.documentElement);\n})();\n";Expand description
The script that applies VARS_ATTR, for a host to serve beside its other
assets.
Without it every number the rules read from that attribute is missing: bars and charts flat, tree rows unindented, a split screen at even halves.
It also restores this device’s density choice onto the root element
(DENSITY_STORE_KEY into
DENSITY_ATTRIBUTE) and answers a
DENSITY_CHOICE_ATTR picker, for the same reason it is not deferred: a
choice applied after first paint is a page that jumps. Serve
it as a classic script in the head, not deferred, so its observer is
running before the parser reaches the body and the first paint already has
every property.
std::fs::write("static/vars.js", makeover_webview::VARS_JS)?;