auditmysite 1.2.0

Resource-efficient WCAG 2.1 Accessibility Checker in Rust
Documentation
//! WCAG 1.1.1 - Server-side Image Map
//!
//! axe-core rule: `server-side-image-map`
//! Server-side image maps are inaccessible because the coordinates are sent
//! to the server and keyboard users cannot interact with them meaningfully.
//! Recommends replacing with client-side image maps or text links.
//!
//! DOM-level rule: `ismap` is a boolean HTML attribute, never exposed as an
//! AX property — an earlier tree-based implementation of this check read a
//! non-existent AX property and never fired in production (#QA-030).

use chromiumoxide::Page;
use tracing::warn;

use crate::cli::WcagLevel;
use crate::wcag::types::{RuleMetadata, Severity, Violation};

pub const RULE_SERVER_SIDE_IMAGE_MAP: RuleMetadata = RuleMetadata {
    id: "1.1.1",
    name: "Server-side Image Map",
    level: WcagLevel::A,
    severity: Severity::Medium,
    description:
        "Server-side image maps must not be used; use client-side image maps or text links instead",
    help_url: "https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html",
    axe_id: "server-side-image-map",
    tags: &["wcag2a", "wcag111", "cat.images"],
};

const SERVER_SIDE_IMAGE_MAP_CAP: usize = 250;

const SERVER_SIDE_IMAGE_MAP_BODY: &str = r#"
  var issues = [];
  var images = document.querySelectorAll('img[ismap]');
  for (var i = 0; i < images.length && issues.length < CAP; i++) {
    issues.push({ selector: __amsCssSelector(images[i]) });
  }
  return { issues: issues };
"#;

/// Check for server-side image maps (`<img ismap>`).
pub async fn check_server_side_image_map_with_page(page: &Page) -> Vec<Violation> {
    let js = [
        "(function() {",
        crate::accessibility::js_helpers::CSS_SELECTOR_JS,
        &SERVER_SIDE_IMAGE_MAP_BODY.replace("CAP", &SERVER_SIDE_IMAGE_MAP_CAP.to_string()),
        "})()",
    ]
    .concat();

    let result = match page.evaluate(js.as_str()).await {
        Ok(r) => r,
        Err(e) => {
            warn!("server-side-image-map JS failed: {}", e);
            return vec![crate::wcag::technical_rule_failure_for(
                "server-side-image-map",
                crate::cli::WcagLevel::A,
                "page_evaluation_failed",
            )];
        }
    };

    let val = match result.value() {
        Some(v) => v.clone(),
        None => {
            return vec![crate::wcag::technical_rule_failure_for(
                "server-side-image-map",
                crate::cli::WcagLevel::A,
                "missing_evaluation_value",
            )]
        }
    };

    let issues = match val.get("issues").and_then(|v| v.as_array()) {
        Some(arr) => arr.clone(),
        None => return vec![],
    };

    issues
        .iter()
        .filter_map(|issue| {
            let selector = issue.get("selector")?.as_str()?.to_string();

            Some(
                Violation::new(
                    RULE_SERVER_SIDE_IMAGE_MAP.id,
                    RULE_SERVER_SIDE_IMAGE_MAP.name,
                    RULE_SERVER_SIDE_IMAGE_MAP.level,
                    RULE_SERVER_SIDE_IMAGE_MAP.severity,
                    "Image uses a server-side image map (ismap attribute), which is inaccessible to keyboard and screen reader users",
                    selector.clone(),
                )
                .with_selector(selector)
                .with_fix("Replace server-side image map with client-side image map or text links")
                .with_rule_id(RULE_SERVER_SIDE_IMAGE_MAP.axe_id)
                .with_help_url(RULE_SERVER_SIDE_IMAGE_MAP.help_url),
            )
        })
        .collect()
}