use chromiumoxide::Page;
use crate::cli::WcagLevel;
use crate::wcag::types::{RuleMetadata, Severity, Violation};
pub const RULE_META_VIEWPORT_LARGE: RuleMetadata = RuleMetadata {
id: "1.4.4",
name: "Meta Viewport Large Scale",
level: WcagLevel::AA,
severity: Severity::Medium,
description: "The viewport meta tag must allow users to scale the page to at least 500%",
help_url: "https://www.w3.org/WAI/WCAG21/Understanding/resize-text.html",
axe_id: "meta-viewport-large",
tags: &["wcag2aa", "wcag144", "cat.sensory-and-visual-cues"],
};
const VIEWPORT_CONTENT_JS: &str =
"document.querySelector('meta[name=\"viewport\"]')?.getAttribute('content') || null";
pub async fn check_meta_viewport_large_with_page(page: &Page) -> Vec<Violation> {
let content = match read_viewport_content(page).await {
Some(c) => c,
None => return vec![],
};
if !is_viewport_restricted(&content, 5.0) || is_viewport_restricted(&content, 2.0) {
return vec![];
}
vec![Violation::new(
RULE_META_VIEWPORT_LARGE.id,
RULE_META_VIEWPORT_LARGE.name,
RULE_META_VIEWPORT_LARGE.level,
RULE_META_VIEWPORT_LARGE.severity,
"Viewport meta tag restricts zoom below 500% (maximum-scale < 5)",
"meta[name=viewport]",
)
.with_selector("meta[name=viewport]")
.with_fix("Set maximum-scale to at least 5 or remove the maximum-scale restriction")
.with_rule_id(RULE_META_VIEWPORT_LARGE.axe_id)
.with_help_url(RULE_META_VIEWPORT_LARGE.help_url)]
}
pub(super) async fn read_viewport_content(page: &Page) -> Option<String> {
page.evaluate(VIEWPORT_CONTENT_JS)
.await
.ok()
.and_then(|r| r.value().and_then(|v| v.as_str().map(str::to_owned)))
}
pub(super) fn is_viewport_restricted(content: &str, threshold: f64) -> bool {
let content = content.to_lowercase();
if content.contains("user-scalable=no") || content.contains("user-scalable=0") {
return true;
}
if let Some(pos) = content.find("maximum-scale=") {
let after = &content[pos + "maximum-scale=".len()..];
let value_str: String = after
.chars()
.take_while(|c| c.is_ascii_digit() || *c == '.')
.collect();
if let Ok(val) = value_str.parse::<f64>() {
return val < threshold;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_viewport_restricted_at_5() {
assert!(is_viewport_restricted("maximum-scale=1", 5.0));
assert!(is_viewport_restricted("maximum-scale=4.9", 5.0));
assert!(is_viewport_restricted("user-scalable=no", 5.0));
assert!(is_viewport_restricted("user-scalable=0", 5.0));
assert!(!is_viewport_restricted(
"width=device-width, initial-scale=1",
5.0
));
assert!(!is_viewport_restricted("maximum-scale=5", 5.0));
assert!(!is_viewport_restricted("maximum-scale=10", 5.0));
}
#[test]
fn test_is_viewport_restricted_at_2() {
assert!(is_viewport_restricted("maximum-scale=1", 2.0));
assert!(!is_viewport_restricted("maximum-scale=3", 2.0));
}
}