@@ -2,6 +2,7 @@ import { AXE_TO_FPC, FPC_LABELS, FPC_SVGS, FPC_DESCRIPTIONS } from '../data/axe-
22import { getFpcPrevalenceRates , CENSUS_DISABILITY_STATS } from '../data/census-disability-stats.js' ;
33import { getPolicyNarrative , getHeuristicsForAxeRule } from '../data/axe-impact-loader.js' ;
44import { NNG_HEURISTICS } from '../data/nng-heuristics.js' ;
5+ import { getThirdPartyServiceMeta } from '../scanners/tech-detector.js' ;
56
67const GITHUB_URL = 'https://github.com/mgifford/daily-dap' ;
78const DASHBOARD_URL = 'https://mgifford.github.io/daily-dap/docs/reports/index.html' ;
@@ -848,12 +849,25 @@ function renderSharedStyles() {
848849 }
849850 .tech-badge-cms { background-color: hsl(200 60% 88%); color: hsl(200 60% 25%); }
850851 .tech-badge-uswds { background-color: hsl(225 70% 88%); color: hsl(225 70% 25%); }
852+ .tech-badge-3p { background-color: hsl(0 0% 88%); color: hsl(0 0% 25%); }
851853 :root:not([data-color-scheme="light"]) .tech-badge-cms { background-color: hsl(200 40% 25%); color: hsl(200 40% 85%); }
852854 :root:not([data-color-scheme="light"]) .tech-badge-uswds { background-color: hsl(225 40% 25%); color: hsl(225 40% 85%); }
855+ :root:not([data-color-scheme="light"]) .tech-badge-3p { background-color: hsl(0 0% 25%); color: hsl(0 0% 85%); }
853856 html[data-color-scheme="dark"] .tech-badge-cms { background-color: hsl(200 40% 25%); color: hsl(200 40% 85%); }
854857 html[data-color-scheme="dark"] .tech-badge-uswds { background-color: hsl(225 40% 25%); color: hsl(225 40% 85%); }
858+ html[data-color-scheme="dark"] .tech-badge-3p { background-color: hsl(0 0% 25%); color: hsl(0 0% 85%); }
855859 html[data-color-scheme="light"] .tech-badge-cms { background-color: hsl(200 60% 88%); color: hsl(200 60% 25%); }
856860 html[data-color-scheme="light"] .tech-badge-uswds { background-color: hsl(225 70% 88%); color: hsl(225 70% 25%); }
861+ html[data-color-scheme="light"] .tech-badge-3p { background-color: hsl(0 0% 88%); color: hsl(0 0% 25%); }
862+ /* Privacy-concern indicator inside third-party service table */
863+ .privacy-concern {
864+ font-size: 0.75em;
865+ font-weight: 600;
866+ color: hsl(30 80% 35%);
867+ }
868+ :root:not([data-color-scheme="light"]) .privacy-concern { color: hsl(30 80% 70%); }
869+ html[data-color-scheme="dark"] .privacy-concern { color: hsl(30 80% 70%); }
870+ html[data-color-scheme="light"] .privacy-concern { color: hsl(30 80% 35%); }
857871
858872 /* ---------- URL cells ---------- */
859873 .url-cell {
@@ -1865,6 +1879,7 @@ function renderTechUrlTooltip(visibleText, urls, ariaLabel, preEscaped = false)
18651879/**
18661880 * Render small inline technology badges for a single URL row.
18671881 * Shows CMS name and/or USWDS version if detected.
1882+ * Shows a count badge for third-party services if any are detected.
18681883 *
18691884 * @param {object|null } tech - detected_technologies object
18701885 * @returns {string } HTML string
@@ -1885,6 +1900,13 @@ function renderTechBadges(tech) {
18851900 parts . push ( `<span class="tech-badge tech-badge-uswds" title="U.S. Web Design System${ tech . uswds . version ? ` v${ escapeHtml ( tech . uswds . version ) } ` : '' } ">${ label } </span>` ) ;
18861901 }
18871902
1903+ const services = tech . third_party_services ?? [ ] ;
1904+ if ( services . length > 0 ) {
1905+ const serviceList = services . map ( escapeHtml ) . join ( ', ' ) ;
1906+ const label = `${ services . length } 3rd-party` ;
1907+ parts . push ( `<span class="tech-badge tech-badge-3p" title="Third-party services: ${ serviceList } ">${ label } </span>` ) ;
1908+ }
1909+
18881910 return parts . join ( ' ' ) ;
18891911}
18901912
@@ -1900,10 +1922,20 @@ function renderTechSummarySection(report) {
19001922 return '' ;
19011923 }
19021924
1903- const { cms_counts = { } , cms_urls = { } , uswds_count = 0 , uswds_versions = [ ] , uswds_version_urls = { } , total_scanned = 0 } = summary ;
1925+ const {
1926+ cms_counts = { } ,
1927+ cms_urls = { } ,
1928+ uswds_count = 0 ,
1929+ uswds_versions = [ ] ,
1930+ uswds_version_urls = { } ,
1931+ total_scanned = 0 ,
1932+ third_party_service_counts = { } ,
1933+ third_party_service_urls = { }
1934+ } = summary ;
19041935 const cmsEntries = Object . entries ( cms_counts ) . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] ) ;
1936+ const thirdPartyEntries = Object . entries ( third_party_service_counts ) . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] ) ;
19051937
1906- if ( cmsEntries . length === 0 && uswds_count === 0 ) {
1938+ if ( cmsEntries . length === 0 && uswds_count === 0 && thirdPartyEntries . length === 0 ) {
19071939 return '' ;
19081940 }
19091941
@@ -1924,6 +1956,49 @@ function renderTechSummarySection(report) {
19241956 } ) . join ( ', ' ) } . The latest release is available at <a href="https://github.com/uswds/uswds/releases" target="_blank" rel="noreferrer">github.com/uswds/uswds/releases</a>.</p>`
19251957 : '' ;
19261958
1959+ const thirdPartySection = thirdPartyEntries . length > 0
1960+ ? ( ( ) => {
1961+ const rows = thirdPartyEntries
1962+ . map ( ( [ name , count ] ) => {
1963+ const meta = getThirdPartyServiceMeta ( name ) ;
1964+ const category = meta ? escapeHtml ( meta . category ) : '' ;
1965+ const urls = third_party_service_urls [ name ] ?? [ ] ;
1966+ const countCell = renderTechUrlTooltip ( String ( count ) , urls , `${ count } URL${ count !== 1 ? 's' : '' } using ${ name } ` ) ;
1967+ const shareCell = `${ total_scanned > 0 ? Math . round ( ( count / total_scanned ) * 100 ) : 0 } %` ;
1968+ const privacyCell = meta ?. privacy_concern
1969+ ? `<span class="privacy-concern"><span aria-hidden="true">⚠</span> tracking</span>`
1970+ : '' ;
1971+ return `<tr>
1972+ <td data-label="Service">${ escapeHtml ( name ) } </td>
1973+ <td data-label="Category">${ category } </td>
1974+ <td data-label="URLs">${ countCell } </td>
1975+ <td data-label="Share">${ shareCell } </td>
1976+ <td data-label="Privacy">${ privacyCell } </td>
1977+ </tr>` ;
1978+ } )
1979+ . join ( '\n' ) ;
1980+ const trackingCount = thirdPartyEntries . filter ( ( [ name ] ) => getThirdPartyServiceMeta ( name ) ?. privacy_concern ) . length ;
1981+ const trackingNote = trackingCount > 0
1982+ ? `<p><strong>${ trackingCount } </strong> service${ trackingCount !== 1 ? 's' : '' } marked <span class="privacy-concern"><span aria-hidden="true">⚠</span> tracking</span> send user data to third-party servers and may have privacy implications for site visitors.</p>`
1983+ : '' ;
1984+ return `
1985+ <h3 id="third-party-js-heading">Third-Party JavaScript Services${ renderAnchorLink ( 'third-party-js-heading' , 'Third-Party JavaScript Services' ) } </h3>
1986+ <p>Third-party scripts detected from network requests loaded by scanned pages. These services may affect page performance, user privacy, and security posture.</p>
1987+ ${ trackingNote }
1988+ ${ wrapTable ( `<table>
1989+ <caption>Third-party services detected across ${ total_scanned } successfully scanned URLs</caption>
1990+ <thead><tr>
1991+ <th scope="col">Service</th>
1992+ <th scope="col">Category</th>
1993+ <th scope="col">URLs</th>
1994+ <th scope="col">Share</th>
1995+ <th scope="col">Privacy</th>
1996+ </tr></thead>
1997+ <tbody>${ rows } </tbody>
1998+ </table>` ) } `;
1999+ } ) ( )
2000+ : '' ;
2001+
19272002 return `
19282003 <section aria-labelledby="tech-summary-heading">
19292004 <h2 id="tech-summary-heading">Detected Technologies${ renderAnchorLink ( 'tech-summary-heading' , 'Detected Technologies' ) } </h2>
@@ -1935,6 +2010,7 @@ function renderTechSummarySection(report) {
19352010 </table>` ) : '' }
19362011 <p>USWDS detected on <strong>${ uswds_count } </strong> of <strong>${ total_scanned } </strong> scanned URL${ total_scanned !== 1 ? 's' : '' } ${ total_scanned > 0 ? ` (${ Math . round ( ( uswds_count / total_scanned ) * 100 ) } %)` : '' } .</p>
19372012 ${ uswdsVersionList }
2013+ ${ thirdPartySection }
19382014 </section>` ;
19392015}
19402016
0 commit comments