use std::time::Duration;
use chromiumoxide::cdp::browser_protocol::emulation::SetDeviceMetricsOverrideParams;
use chromiumoxide::Page;
use crate::cli::WcagLevel;
use crate::wcag::types::{RuleMetadata, Severity, Violation};
pub const REFLOW_RULE: RuleMetadata = RuleMetadata {
id: "1.4.10",
name: "Reflow",
level: WcagLevel::AA,
severity: Severity::High,
description: "Content reflows without horizontal scrolling at 320 CSS pixels width",
help_url: "https://www.w3.org/WAI/WCAG21/Understanding/reflow.html",
axe_id: "css-overflow-hidden",
tags: &["wcag2aa", "wcag1410", "cat.sensory-and-visual-cues"],
};
const FIND_OVERFLOW_JS: &str = r#"
(function() {
const vw = window.innerWidth;
// Check if the document itself overflows horizontally
if (document.documentElement.scrollWidth <= vw + 5) return null;
// Find the widest element to give a useful selector hint
let worst = null;
let worstRight = vw + 5;
for (const el of document.querySelectorAll('*')) {
try {
const r = el.getBoundingClientRect();
if (r.right > worstRight) {
worstRight = r.right;
const tag = el.tagName.toLowerCase();
const id = el.id ? '#' + el.id : '';
const cls = el.className && typeof el.className === 'string'
? '.' + el.className.trim().split(/\s+/)[0] : '';
worst = tag + id + cls;
}
} catch(e) {}
}
return worst || 'unknown element';
})()
"#;
pub async fn check_reflow_with_page(page: &Page) -> Vec<Violation> {
let narrow = SetDeviceMetricsOverrideParams::builder()
.mobile(false)
.width(320_i64)
.height(256_i64)
.device_scale_factor(1.0_f64)
.build()
.unwrap();
if page.execute(narrow).await.is_err() {
return vec![crate::wcag::technical_rule_failure(
&REFLOW_RULE,
"viewport_override_failed",
)];
}
tokio::time::sleep(Duration::from_millis(350)).await;
let result = match page.evaluate(FIND_OVERFLOW_JS).await {
Ok(r) => r,
Err(_) => {
return vec![crate::wcag::technical_rule_failure(
&REFLOW_RULE,
"page_evaluation_failed",
)]
}
};
let overflow_element = match result.value() {
Some(v) if !v.is_null() => v.as_str().unwrap_or("unknown element").to_string(),
_ => return vec![],
};
vec![Violation::new(
REFLOW_RULE.id,
REFLOW_RULE.name,
REFLOW_RULE.level,
Severity::High,
format!(
"Page requires horizontal scrolling at 320 CSS px width (overflowing element: {overflow_element}). \
Users who zoom to 400% on a 1280px display will need to scroll in two directions."
),
"document",
)
.with_selector(&overflow_element)
.with_fix(
"Use responsive CSS (max-width: 100%, flexbox/grid wrap, relative units). \
Avoid fixed pixel widths on containers. \
Test at 320px viewport width or 400% browser zoom.",
)
.with_rule_id(REFLOW_RULE.axe_id)
.with_help_url(REFLOW_RULE.help_url)]
}