Vendor axe-core, drop the Skypack CDN dependency - #14677
Conversation
Pins AXE_JS in configuration and adds a download block to update-html-dependencies.ts (axe.min.js plus the MPL-2.0 LICENSE and LICENSE-3RD-PARTY.txt, which must ship alongside it). Files are vendored byte-for-byte from npm via unpkg; the /*! banner in axe.min.js carries the required Deque copyright and MPL notice.
Injects axe.min.js as a classic script ahead of axe-check.js (a deferred module), so window.axe is set before the checker runs. Replaces the pinned Skypack dynamic import, making the axe overlay work offline and removing the CDN request from every reader's browser. Same axe version (4.10.3), identical scan results. Also waits for the window load event before the first scan. Deferred modules run before DOMContentLoaded, but the dashboard layout runs on DOMContentLoaded and keeps <html> hidden until then, so an immediate scan sees only hidden elements and reports nothing. The Skypack import's network latency masked this pre-existing race; removing it exposed the bug (8 dashboard Playwright tests).
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
This will be a really nice feature to have! |
The overlay is injected after the page scan, so it never audits itself and nothing else guards against new violations as the report UI grows. Scan it with the vendored axe-core build the page already loaded (window.axe), on the long-report page where the overlay is scrollable. Requires the vendored axe-core from #14677, which this branch is now stacked on. Verified the test fails with scrollable-region-focusable when the tabindex fix is removed.
There was a problem hiding this comment.
Looks good.
Some ideas to not forget about quarto-web PR
- Add to list of dependencies: https://quarto.org/license.html#dependencies and https://quarto.org/docs/faq/#what-are-quartos-dependencies - but probably the later should link to the former
|
Good catch, PR for docs at quarto-dev/quarto-web#2106 |
|
Cool ! Is this ready to merge ? Do we want this in v1.10 or should we release v1.10 and make this new feature for v1.11 first pre-release ? |
|
@cderv Ready to merge! Most of the PRs that I have queued up for after this are improving/fixing the The bigger site audit |
The overlay is injected after the page scan, so it never audits itself and nothing else guards against new violations as the report UI grows. Scan it with the vendored axe-core build the page already loaded (window.axe), on the long-report page where the overlay is scrollable. Requires the vendored axe-core from #14677, which this branch is now stacked on. Verified the test fails with scrollable-region-focusable when the tabindex fix is removed.
Recent axe-core changes (#14607, #14655, #14667, #14677, #14680) reworked the accessibility-report feature repeatedly with no dedicated regression tests guarding several of its behaviors. A coverage audit found three gaps; this closes them. - No test guarded against reintroducing a CDN dependency for axe-core, despite #14677 vendoring it specifically to drop the Skypack CDN. - The report overlay's keyboard-scrollable region (added in #14680) was only checked for static tabindex/role attributes, never an actual keypress. - The self-scan pattern (added in #14680) only covered the plain-HTML overlay, not the RevealJS report slide or dashboard offcanvas that render the same violation markup. Extending the self-scan to RevealJS and the dashboard surfaced a real pre-existing issue: both report-chrome regions are scrollable but not keyboard-focusable once they overflow, the same class of gap already fixed for the plain-HTML overlay. Filed as #14710, not fixed here — the dashboard test filters that one known node out of the scrollable-region-focusable violation (not the whole violation), so it still catches other regressions in that scope. Related to #14710
quarto-dev/quarto-cli#14677 vendors axe-core (MPL-2.0) as a bundled resource, so it now belongs in the license page's dependency table. The FAQ's dependency list now points to that table rather than growing its own copy.
Adds a section on scoping accessibility checks to a WCAG conformance level (quarto-dev/quarto-cli#14607, quarto-dev/quarto-cli#14682) and updates the CDN note now that axe-core is bundled with Quarto 1.10 (quarto-dev/quarto-cli#14677). Also adds fig-alt to the two example screenshots — an axe site audit of this page flagged them as missing alt text (the bracket text of a figure becomes the caption, not the alt).
* Document axe standard and best-practice options Adds a section on scoping accessibility checks to a WCAG conformance level (quarto-dev/quarto-cli#14607, quarto-dev/quarto-cli#14682) and updates the CDN note now that axe-core is bundled with Quarto 1.10 (quarto-dev/quarto-cli#14677). Also adds fig-alt to the two example screenshots — an axe site audit of this page flagged them as missing alt text (the bracket text of a figure becomes the caption, not the alt). * Clarify that choosing a standard never adds deprecated rules * Update axe overlay fig-alt to describe severity and conformance label * Add prerelease callout for 1.10 axe options * Remove prerelease callout shortcode * Replace introduced-in callout notes with prerelease-callout shortcodes * Simplify * Dedent best-practice YAML example left over from removed bullet list * Reorganize accessibility page around interactive and site-wide checks - Promote the interactive workflow to its own section and move the example up into it, with a new DevTools console screenshot for the default axe: true output - Rename Customization to Output location and attach the existing overlay screenshots to output: document - Reframe Planned work as Site-wide accessibility checks - Add capture-axe-devtools.mjs (window-ID capture of headed Chromium with DevTools open) and a console.qmd example page * Register manual captures in the screenshot manifest Adds a manual array to manifest.json for screenshots capture.js can't produce (currently axe-console, which needs the DevTools panel). The schema validates entries, --list and list.js show them, and --name <manual-entry> points at the producing script instead of failing.
* Document axe standard and best-practice options Adds a section on scoping accessibility checks to a WCAG conformance level (quarto-dev/quarto-cli#14607, quarto-dev/quarto-cli#14682) and updates the CDN note now that axe-core is bundled with Quarto 1.10 (quarto-dev/quarto-cli#14677). Also adds fig-alt to the two example screenshots — an axe site audit of this page flagged them as missing alt text (the bracket text of a figure becomes the caption, not the alt). * Clarify that choosing a standard never adds deprecated rules * Update axe overlay fig-alt to describe severity and conformance label * Add prerelease callout for 1.10 axe options * Remove prerelease callout shortcode * Replace introduced-in callout notes with prerelease-callout shortcodes * Simplify * Dedent best-practice YAML example left over from removed bullet list * Reorganize accessibility page around interactive and site-wide checks - Promote the interactive workflow to its own section and move the example up into it, with a new DevTools console screenshot for the default axe: true output - Rename Customization to Output location and attach the existing overlay screenshots to output: document - Reframe Planned work as Site-wide accessibility checks - Add capture-axe-devtools.mjs (window-ID capture of headed Chromium with DevTools open) and a console.qmd example page * Register manual captures in the screenshot manifest Adds a manual array to manifest.json for screenshots capture.js can't produce (currently axe-console, which needs the DevTools panel). The schema validates entries, --list and list.js show them, and --name <manual-entry> points at the producing script instead of failing. (cherry picked from commit 6979d02) Co-authored-by: Charlotte Wickham <charlotte.wickham@posit.co>
Description
Vendors axe-core as a bundled HTML resource, so the
axe:accessibility option stops loading it from the Skypack CDN in the reader's browser.What changes
configurationpinsAXE_JS=4.10.3— exactly the version the CDN import already pinned, so this PR changes where axe comes from, not which axe runs. Scan results are identical. A follow-up PR will bump to current axe-core (4.12.1), keeping "different source" and "different results" in separate diffs.update-html-dependencies.tsdownloadsaxe.min.jsplus axe-core'sLICENSE(MPL-2.0) andLICENSE-3RD-PARTY.txtfrom the npm package via unpkg —https://unpkg.com/axe-core@4.10.3/<file>, the sameupdateUnpkgDependencyhelper used for anchor-js, popper, and tippy — intosrc/resources/formats/html/axe/. To verify the committed bytes,curlthose three URLs and compare (or rerunquarto-bld update-html-dependencies). The files are vendored byte-for-byte: the/*!banner inaxe.min.jscarries the Deque copyright/MPL notice that must ship with every copy. Per-directory LICENSE files follow the existing convention (src/resources/formats/pdf/pdfjs/LICENSE).format-html-axe.tsinjectsaxe.min.jsas a classic script ahead of theaxe-check.jsmodule, sowindow.axeis set before the checker runs.axe-check.jsreplaces the pinned Skypack dynamic import withwindow.axe.Why
cdn.skypack.devfrom every reader's browser.quarto axescanner and the overlay will provably run the same axe version from the same file.A race this exposed (also fixed here):
quarto-dashboard.jskeeps<html>hidden until itsDOMContentLoadedhandler runs, but deferred modules execute before that event — so with the Skypack network latency gone, the first scan ran against a fully hidden document and dashboards reported no violations.axe-check.jsnow waits for the windowloadevent before the first scan. The race predates this PR (a sufficiently fast CDN response could have triggered it); vendoring just made it deterministic.Size: ~573 KB committed across three files (541 KB of it
axe.min.js; ~145 KB gzipped on the wire). Precedent: pdf.js, MathJax, and reveal.js are vendored the same way.Out of scope: any behavior change to scanning, report rendering, or options (#14607
standard, sort order, overlay polish, #14668 rescan) — those are follow-up PRs. This PR produces identical scan results, just sourced locally.Verification
axe-accessibility.spec.ts,axe-code-line-numbers.spec.ts): 27/27 pass on chromium.quarto-bld update-html-dependenciesrerun reproduces the committed axe files byte-for-byte.axe-conformance.test.tsunit tests: 9/9 pass.Checklist
I have (if applicable):
AI-assisted PR