Download scripts/cascade_audit.py from User1342/distinct: direct link, hf CLI and curl.
- Browser
- Download file 5.37 kB
-
https://huggingface.co/spaces/User1342/distinct/resolve/main/scripts/cascade_audit.py
- Command line
-
hf download hf://spaces/User1342/distinct/scripts/cascade_audit.py
-
curl -L -o cascade_audit.py https://huggingface.co/spaces/User1342/distinct/resolve/main/scripts/cascade_audit.py
5.37 kB
| """Does the stylesheet the browser computed match the stylesheet we wrote? | |
| This exists because the same bug shipped twice. Gradio's reset is scoped to a | |
| version-stamped container class, so rules like ``.gradio-container-6-24-0 p`` | |
| and ``.gradio-container-6-24-0 [type="radio"]`` outrank a plain component | |
| selector. Worse, the sheets are injected in a different order on Spaces than in | |
| local dev, so a rule can win on a laptop and lose in production -- which is how | |
| a redesign reached the Space looking, in the reviewer's words, identical. | |
| :mod:`distinct_server.presentation` now hardens every ``.c-`` declaration at | |
| import time, and ``tests/test_stylesheet_hardening.py`` proves that pass is | |
| lossless. Neither can prove the result survived *this* browser and *this* | |
| injection order. That is what this does, and why it is run against production | |
| after every push rather than only against a local server. | |
| Run it directly:: | |
| python scripts/cascade_audit.py http://127.0.0.1:7860 | |
| A finding is only reported when the value that won cannot be produced by any | |
| ``.c-`` rule matching the element. A base rule beaten by one of our own variant | |
| or state rules (``.c-chip--outline``, ``.c-libcard.is-on``, the mobile | |
| ``@media`` block) is ordinary cascade and is filtered out. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import sys | |
| # Properties whose computed form legitimately differs from the declared one, or | |
| # where a mismatch says nothing about the cascade. | |
| _SKIP = [ | |
| "content", "transition", "animation", "cursor", "pointer-events", | |
| "user-select", "will-change", "contain", "isolation", "background", | |
| "background-image", "transform", "filter", "box-shadow", "clip", | |
| "clip-path", "aspect-ratio", "flex", "inset", "all", "font-family", | |
| ] | |
| AUDIT_JS = """() => { | |
| const SKIP=new Set(%s); | |
| const bad=[];let n=0; | |
| for (const sheet of document.styleSheets){ let rules;try{rules=sheet.cssRules}catch(e){continue} | |
| for (const rule of rules){ | |
| if(!rule.selectorText||!rule.style||!rule.selectorText.includes('.c-'))continue; | |
| let els;try{els=document.querySelectorAll(rule.selectorText)}catch(e){continue} | |
| for(const p of rule.style){ | |
| if(SKIP.has(p)||p.startsWith('--')||p.startsWith('-webkit'))continue; | |
| const want=rule.style.getPropertyValue(p).trim(); if(!want)continue; | |
| for(const el of els){ n++; | |
| // The intended value is read in the element's OWN box and font context by | |
| // forcing the declaration inline: an em-based line-height or a ch-based | |
| // max-width computes to a different number on a probe element, and | |
| // comparing against that number invents findings that are not there. | |
| const got=getComputedStyle(el).getPropertyValue(p); | |
| const prev=el.style.getPropertyValue(p),pri=el.style.getPropertyPriority(p); | |
| el.style.setProperty(p,want,'important'); | |
| const intended=getComputedStyle(el).getPropertyValue(p); | |
| if(prev)el.style.setProperty(p,prev,pri);else el.style.removeProperty(p); | |
| if(got!==intended)bad.push({el,sel:rule.selectorText.slice(0,80),prop:p,want:intended,got}); | |
| }}}} | |
| const real=bad.filter(b=>{ | |
| const el=b.el; let ours=false; | |
| for(const sheet of document.styleSheets){ let rules;try{rules=sheet.cssRules}catch(e){continue} | |
| for(const r of rules){ if(!r.selectorText||!r.style)continue; | |
| if(!r.selectorText.includes('.c-'))continue; | |
| let m=false;try{m=el.matches(r.selectorText)}catch(e){} | |
| if(!m)continue; | |
| // Apply the whole rule rather than one longhand: a variant usually writes | |
| // the shorthand (border, margin), and asking it for `border-top-style` | |
| // or `margin-top` on its own can come back empty. | |
| const keep=el.getAttribute('style'); | |
| el.setAttribute('style',(keep||'')+';'+r.style.cssText.replace(/;/g,' !important;')); | |
| const c=getComputedStyle(el).getPropertyValue(b.prop); | |
| if(keep===null)el.removeAttribute('style');else el.setAttribute('style',keep); | |
| if(c===b.got){ours=true;break;} | |
| } if(ours)break; } | |
| return !ours; | |
| }); | |
| const u={};real.forEach(b=>u[b.sel+'|'+b.prop]={sel:b.sel,prop:b.prop,want:b.want,got:b.got}); | |
| return {checked:n,differ:bad.length,overridden:Object.keys(u).length, | |
| list:Object.values(u).slice(0,40)}; | |
| }""" % json.dumps(_SKIP) | |
| def audit(page) -> dict: | |
| """Run the audit on an open Playwright page and return the report.""" | |
| return page.evaluate(AUDIT_JS) | |
| def describe(report: dict) -> str: | |
| line = ( | |
| f"{report['checked']} declarations checked, {report['differ']} differ from " | |
| f"the rule that declared them, {report['overridden']} decided from outside " | |
| f"our sheet" | |
| ) | |
| if not report["overridden"]: | |
| return line | |
| rows = "\n".join( | |
| f" {row['sel']} :: {row['prop']} wanted {row['want']}, got {row['got']}" | |
| for row in report["list"] | |
| ) | |
| return line + "\n" + rows | |
| def main(url: str) -> int: | |
| from playwright.sync_api import sync_playwright | |
| with sync_playwright() as pw: | |
| browser = pw.chromium.launch() | |
| page = browser.new_page(viewport={"width": 1280, "height": 900}) | |
| page.goto(url, wait_until="load") | |
| page.wait_for_timeout(2000) | |
| report = audit(page) | |
| browser.close() | |
| print(describe(report)) | |
| return 1 if report["overridden"] else 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main(sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:7860")) | |