1use crate::engine::rules::rule::{Rule, Validation};
2use crate::engine::rules::techniques::Techniques;
3use crate::engine::rules::utils::nodes::{
4 get_unique_selector, has_alt, has_alt_prop, has_prop, has_prop_value, validate_empty_nodes,
5 validate_missing_attr,
6};
7use crate::engine::rules::wcag_base::{Guideline, IssueType, Principle};
8use crate::i18n::locales::get_message_i18n_str_raw;
9use accessibility_scraper::{ElementRef, Selector};
10use selectors::Element;
11use std::collections::BTreeMap;
12use std::collections::HashMap;
13use std::ops::Add;
14
15lazy_static! {
16 pub static ref RULES_A: BTreeMap<&'static str, Vec<Rule>> =
18 vec![
19 ("html", Vec::from([
20 Rule::new(Techniques::H57.into(), IssueType::Error, Principle::Understandable, Guideline::Readable, "1", |nodes, _auditor| {
21 let n = nodes[0].0;
22 Validation::new_issue(!n.attr("lang").unwrap_or_default().is_empty() || !n.attr("xml:lang").unwrap_or_default().is_empty(), "2").into()
23 }),
24 Rule::new(Techniques::H57.into(), IssueType::Error, Principle::Understandable, Guideline::Readable, "1", |nodes, _auditor| {
25 let lang = nodes[0].0.attr("lang").unwrap_or_default();
26 let alphabetic = lang.chars().all(|x| x.is_alphabetic());
27 Validation::new_issue(if lang.len() > 3 {
29 let mut c = lang.chars();
30 let has_underscore = c.nth(2).unwrap_or_default() == '_' || lang.len() >= 4 && c.nth(1).unwrap_or_default() == '_';
31 alphabetic && has_underscore && lang.len() < 12
32 } else {
33 alphabetic && lang.len() < 12
34 }, "3.Lang").into()
35 }),
36 Rule::new(Techniques::H57.into(), IssueType::Error, Principle::Understandable, Guideline::Readable, "1", |nodes, _auditor| {
37 let lang = nodes[0].0.attr("xml:lang").unwrap_or_default();
38 let alphabetic = lang.chars().all(|x| x == '_' || x.is_alphabetic());
39 Validation::new_issue(if lang.len() > 3 {
41 let mut c = lang.chars();
42 let has_underscore = c.nth(2).unwrap_or_default() == '_' || lang.len() >= 4 && c.nth(1).unwrap_or_default() == '_';
43 alphabetic && has_underscore && lang.len() < 12
44 } else {
45 alphabetic && lang.len() < 12
46 }, "3.XmlLang").into()
47 }),
48 Rule::new(Techniques::H25.into(), IssueType::Error, Principle::Operable, Guideline::Navigable, "2", |nodes, _auditor| {
49 let selector = unsafe { Selector::parse("head > title").unwrap_unchecked() };
50
51 Validation::new_issue(nodes[0].0.select(&selector).count() >= 1, "1.NoTitleEl").into()
52 })
53 ])),
54 ("meta", Vec::from([
55 Rule::new(Techniques::F40.into(), IssueType::Error, Principle::Operable, Guideline::EnoughTime, "1", |nodes, _auditor| {
56 let mut valid = true;
57
58 for node in nodes {
59 let element = node.0;
60 let meta_refresh = element.attr("http-equiv").unwrap_or_default();
61 if meta_refresh == "refresh" {
62 let content = element.attr("content").unwrap_or_default();
63 if content.contains("url") {
64 valid = content.starts_with("0;");
65 }
66 }
67 }
68
69 Validation::new_issue(valid, "2").into()
70 }),
71 Rule::new(Techniques::F41.into(), IssueType::Error, Principle::Understandable, Guideline::EnoughTime, "1", |nodes, _auditor| {
72 let mut valid = true;
73
74 for node in nodes {
75 let element = node.0;
76 let meta_refresh = element.attr("http-equiv").unwrap_or_default();
77 if meta_refresh == "refresh" {
78 let content = element.attr("content").unwrap_or_default();
79 if !content.is_empty() {
80 valid = content == "0";
81 }
82 }
83 }
84
85 Validation::new_issue(valid, "2").into()
86 }),
87 ])),
88 ("title", Vec::from([
89 Rule::new(Techniques::H25.into(), IssueType::Error, Principle::Operable, Guideline::Navigable, "2", |nodes, _auditor| {
90 let mut valid = true;
91 for node in nodes {
92 let e = node.0.inner_html().is_empty();
93 if e {
94 valid = false;
95 }
96 }
97 Validation::new_issue(!nodes.is_empty() || valid, "1.EmptyTitle").into()
98 }),
99 ])),
100 ("body", Vec::from([
101 Rule::new(Techniques::G18.into(), IssueType::Error, Principle::Perceivable, Guideline::Distinguishable, "1", |nodes, auditor| {
102 use rgb::RGB8;
103 let mut validation_errors = Vec::new();
104
105 for node in nodes {
107 if node.0.has_children() {
108 let mut children = node.0.children();
109
110 while let Some(el) = children.next() {
111 match ElementRef::wrap(el) {
112 Some(element) => {
113 if vec![
114 "h1",
115 "h2",
116 "h3",
117 "h4",
118 "h5",
119 "h6",
120 "a",
121 "button",
122 "p",
123 "img",
124 "span",
125 "div",
126 "li",
127 "ol",
128 "td",
129 "th",
130 "tr",
131 "textarea",
132 "select",
133 "input"].contains(&element.value().name()) {
134 let style = accessibility_tree::style::cascade::style_for_element_ref(
135 &element,
136 &auditor.author,
137 &auditor.document
138 );
139
140 let font_size = style.font.font_size.0;
141 let text_color = style.color.color;
142
143 match element.parent() {
144 Some(parent_node) => {
145 match ElementRef::wrap(parent_node) {
146 Some(parent_element) => {
147 let parent_style = accessibility_tree::style::cascade::style_for_element_ref(
148 &parent_element,
149 &auditor.author,
150 &auditor.document,
151 );
152
153 match parent_style.background.background_color {
154 cssparser::Color::RGBA(c) => {
155 let parent_element_background_color = RGB8::from([c.red, c.green, c.blue]);
156 let current_element_text_color = RGB8::from([text_color.red, text_color.green, text_color.blue]);
157 let contrast_ratio = contrast::contrast::<_, f32>(parent_element_background_color, current_element_text_color);
158 let min_contrast = if font_size.px <= 16.00 { 4.00 } else { 3.00 };
159
160 if contrast_ratio <= min_contrast {
161 let message = t!(
162 &get_message_i18n_str_raw(
163 &Guideline::Distinguishable,
164 "",
165 "3_G18_or_G145.Fail",
166 ""),
167 locale = auditor.locale,
168 required = min_contrast.to_string(),
169 value = contrast_ratio.to_string());
170
171 validation_errors.push(Validation::new_custom_issue(false, "", message).into())
172 }
173 }
174 _ => ()
175 }
176 }
177 _ => ()
178 }
179 }
180 _ => ()
181 }
182 }
183 }
184 _ => ()
185 }
186 }
187 }
188 }
189
190 crate::engine::rules::rule::RuleValidation::Multi(validation_errors)
191 }),
192 ])),
193 ("iframe", Vec::from([
194 Rule::new(Techniques::H64.into(), IssueType::Error, Principle::Operable, Guideline::Navigable, "1", |nodes, _auditor| {
195 validate_missing_attr(nodes, "title", "1").into()
196 }),
197 ])),
198 ("frame", Vec::from([
199 Rule::new(Techniques::H64.into(), IssueType::Error, Principle::Operable, Guideline::Navigable, "1", |nodes, _auditor| {
200 validate_missing_attr(nodes, "title", "1").into()
201 }),
202 ])),
203 ("form", Vec::from([
204 Rule::new(Techniques::H32.into(), IssueType::Error, Principle::Operable, Guideline::Predictable, "2", |nodes, _auditor| {
205 let mut valid = false;
206 let mut elements = Vec::new();
207 let selector = unsafe { Selector::parse("button[type=submit]").unwrap_unchecked() };
208
209 for ele in nodes {
210 let ele = ele.0;
211 let e = ele.select(&selector);
212 let c = e.count();
213
214 if c == 1 {
215 valid = true;
216 } else {
217 valid = false;
218 elements.push(get_unique_selector(&ele))
219 }
220 }
221
222 Validation::new(valid, "2", elements, Default::default()).into()
223 }),
224 Rule::new(Techniques::H36.into(), IssueType::Error, Principle::Perceivable, Guideline::TextAlternatives, "1", |nodes, _auditor| {
225 let mut valid = false;
226 let mut elements = Vec::new();
227 let selector = unsafe { Selector::parse("input[type=image][name=submit]").unwrap_unchecked() };
228
229 for ele in nodes {
230 let ele = ele.0;
231 let mut e = ele.select(&selector);
232
233 while let Some(el) = e.next() {
234 let alt = has_alt(el);
235 if !alt {
236 elements.push(get_unique_selector(&ele))
237 }
238 valid = alt;
239 }
240 }
241
242 Validation::new(valid, "", elements, Default::default()).into()
243 }),
244 ])),
245 ("a", Vec::from([
246 Rule::new(Techniques::H2.into(), IssueType::Error, Principle::Perceivable, Guideline::TextAlternatives, "1", |nodes, _auditor| {
247 let mut valid = true;
248 let selector = unsafe { Selector::parse("img").unwrap_unchecked() };
249 let mut elements = Vec::new();
250
251 for ele in nodes {
252 let ele = ele.0;
253 let mut e = ele.select(&selector);
254
255 while let Some(el) = e.next() {
256 let alt = match el.attr("alt") {
257 Some(s) => s,
258 _ => "",
259 };
260
261 let text = ele.text().collect::<Vec<_>>().join("");
262 let text = text.trim();
263
264 if alt == text {
265 valid = false;
266 elements.push(get_unique_selector(&ele))
267 }
268 }
269 }
270
271 Validation::new(valid, "EG5", elements, Default::default()).into()
272 }),
273 Rule::new(Techniques::H30.into(), IssueType::Error, Principle::Perceivable, Guideline::TextAlternatives, "1", |nodes, _auditor| {
274 let mut valid = true;
275 let selector = unsafe { Selector::parse("img").unwrap_unchecked() };
276 let mut elements = Vec::new();
277
278 for ele in nodes {
279 let ele = ele.0;
280 let mut e = ele.select(&selector);
281
282 while let Some(el) = e.next() {
283 let alt = has_alt(el);
284 if !alt {
285 elements.push(get_unique_selector(&ele))
286 }
287 valid = alt;
288 }
289 }
290
291 Validation::new(valid, "2", elements, Default::default()).into()
292 }),
293 Rule::new(Techniques::H91.into(), IssueType::Error, Principle::Robust, Guideline::Compatible, "2", |nodes, _auditor| {
294 let mut valid = true;
295 let mut elements = Vec::new();
296
297 for ele in nodes {
298 let ele = ele.0;
299 match ele.attr("href") {
300 Some(_) => {
301 let empty = ele.inner_html().trim().is_empty();
302 if empty {
303 elements.push(get_unique_selector(&ele))
304 }
305 valid = !empty
306 }
307 _ => ()
308 }
309 }
310 Validation::new(valid, "A.NoContent", elements, Default::default()).into()
311 }),
312 Rule::new(Techniques::H91.into(), IssueType::Error, Principle::Robust, Guideline::Compatible, "2", |nodes, _auditor| {
313 let mut valid = true;
314 let mut elements = Vec::new();
315 for ele in nodes {
316 let ele = ele.0;
317 let v = !ele.is_empty() || ele.has_attribute("id") || ele.has_attribute("href");
318 if !v {
319 elements.push(get_unique_selector(&ele))
320 }
321 valid = v;
322 }
323 Validation::new(valid, "A.EmptyNoId", elements, Default::default()).into()
324 }),
325 ])),
326 ("img", Vec::from([
327 Rule::new(Techniques::H37.into(), IssueType::Error, Principle::Perceivable, Guideline::TextAlternatives, "1", |nodes, _auditor| {
328 let mut valid = true;
329 let mut elements = Vec::new();
330
331 for ele in nodes {
332 let ele = ele.0;
333 let alt = has_alt(ele);
334 if !alt {
335 elements.push(get_unique_selector(&ele))
336 }
337 valid = alt;
338 }
339
340 Validation::new(valid, "", elements, Default::default()).into()
341 }),
342 Rule::new(Techniques::H67.into(), IssueType::Error, Principle::Perceivable, Guideline::TextAlternatives, "1", |nodes, _auditor| {
343 let mut valid = true;
344 let mut elements = Vec::new();
345
346 for ele in nodes {
347 let ele = ele.0;
348 if has_prop(ele, "alt") && has_prop_value(ele, "title") {
349 valid = false;
350 elements.push(get_unique_selector(&ele))
351 }
352 }
353
354 Validation::new(valid, "1", elements, Default::default()).into()
355 }),
356 ])),
357 ("h1", Vec::from([
358 Rule::new(Techniques::H42.into(), IssueType::Error, Principle::Perceivable, Guideline::Adaptable, "1", |nodes, _auditor| {
359 validate_empty_nodes(nodes, "2").into()
360 }),
361 ])),
362 ("h2", Vec::from([
363 Rule::new(Techniques::H42.into(), IssueType::Error, Principle::Perceivable, Guideline::Adaptable, "1", |nodes, _auditor| {
364 validate_empty_nodes(nodes, "2").into()
365 }),
366 ])),
367 ("h3", Vec::from([
368 Rule::new(Techniques::H42.into(), IssueType::Error, Principle::Perceivable, Guideline::Adaptable, "1", |nodes, _auditor| {
369 validate_empty_nodes(nodes, "2").into()
370 }),
371 ])),
372 ("h4", Vec::from([
373 Rule::new(Techniques::H42.into(), IssueType::Error, Principle::Perceivable, Guideline::Adaptable, "1", |nodes, _auditor| {
374 validate_empty_nodes(nodes, "2").into()
375 }),
376 ])),
377 ("h5", Vec::from([
378 Rule::new(Techniques::H42.into(), IssueType::Error, Principle::Perceivable, Guideline::Adaptable, "1", |nodes, _auditor| {
379 validate_empty_nodes(nodes, "2").into()
380 }),
381 ])),
382 ("h6", Vec::from([
383 Rule::new(Techniques::H42.into(), IssueType::Error, Principle::Perceivable, Guideline::Adaptable, "1", |nodes, _auditor| {
384 validate_empty_nodes(nodes, "2").into()
385 }),
386 ])),
387 ("label", Vec::from([
388 Rule::new(Techniques::H93.into(), IssueType::Error, Principle::Perceivable, Guideline::Adaptable, "1", |nodes, _auditor| {
389 let mut valid = true;
390 let mut elements = Vec::new();
391 let mut id_map: HashMap<&str, u8> = HashMap::new();
392
393 for ele in nodes {
394 match ele.0.attr("for") {
395 Some(s) => {
396 if id_map.contains_key(s) {
397 let u = id_map.get(s);
398 match u {
399 Some(u) => {
400 valid = false;
401 id_map.insert(s, u.add(1));
402 elements.push(get_unique_selector(&ele.0))
403 }
404 _ => ()
405 }
406 } else {
407 id_map.insert(s, 1);
408 }
409 }
410 _ => ()
411 }
412 }
413
414 Validation::new(valid, "1", elements, Default::default()).into()
415 }),
416 Rule::new(Techniques::H44.into(), IssueType::Error, Principle::Perceivable, Guideline::Adaptable, "1", |nodes, _auditor| {
417 let mut valid = true;
418 let mut elements = Vec::new();
419
420 for ele in nodes {
421 let has_valid_aria_label = ele.0.attr("aria-label").map_or(false, |s| !s.trim().is_empty());
422 let mut has_valid_text_match = false;
423
424 if !has_valid_aria_label && ele.0.text().next().is_some() {
425 for child in ele.0.children() {
426 match ElementRef::wrap(child) {
427 Some(child_element) => {
428 let name = child_element.value().name();
429
430 if vec!["textareas", "select"].contains(&name) {
431 has_valid_text_match = true;
432 } else if name == "input" {
433 match child_element.attr("type") {
434 Some(s) => {
435 if vec!["text", "file", "password"].contains(&s) {
436 has_valid_text_match = true;
437 }
438 }
439 _ => ()
440 }
441 }
442
443 if has_valid_text_match {
444 break;
445 }
446 }
447 _ => ()
448 }
449 }
450 }
451
452 if !has_valid_aria_label && !has_valid_text_match {
453 match ele.0.attr("for") {
454 Some(s) => {
455 let selector = unsafe { Selector::parse(&("#".to_string() + &s)).unwrap_unchecked() };
456 let root_tree = ele.0.tree().root();
457
458 match ElementRef::new(root_tree) {
459 t => {
460 let e = t.select(&selector);
461
462 if e.count() == 0 {
463 valid = false;
464 elements.push(get_unique_selector(&ele.0))
465 }
466 }
467 }
468 }
469 _ => ()
470 }
471 }
472 }
473
474 Validation::new(valid, "NonExistent", elements, Default::default()).into()
475 })
476 ])),
477 ("input", Vec::from([
478 Rule::new(Techniques::H91.into(), IssueType::Error, Principle::Robust, Guideline::Compatible, "2", |nodes, auditor| {
479 let mut valid = true;
480 let mut elements = Vec::new();
481
482 for ele in nodes {
483 let ele = ele.0;
484 match ele.attr("type") {
485 Some(t) => {
486 if t == "submit" || t == "reset" || t == "button" {
487 let is_valid = match ele.attr("value") {
488 Some(_) => true,
489 _ => false
490 };
491
492 if !is_valid {
493 valid = false;
494 elements.push(get_unique_selector(&ele))
495 }
496 }
497 }
498 _ => ()
499 }
500 }
501
502 let message = if !valid { t!(&get_message_i18n_str_raw( &Guideline::Compatible, "", "2_msg_pattern", ""), locale = auditor.locale, msgNodeType = r#""input""#, builtAttrs = r#""value""#) } else { Default::default() };
503
504 Validation::new(valid, "", elements, message).into()
505 }),
506 Rule::new(Techniques::H91.into(), IssueType::Error, Principle::Robust, Guideline::Compatible, "2", |nodes, auditor| {
507 let mut valid = true;
508 let mut elements = Vec::new();
509
510 for ele in nodes {
511 let ele = ele.0;
512 match ele.attr("type") {
513 Some(t) => {
514 if t == "submit" || t == "reset" || t == "button" {
515 let is_valid = match ele.attr("value") {
516 Some(v) => {
517 if v.trim().is_empty() {
518 false
519 } else {
520 true
521 }
522 }
523 _ => false
524 };
525
526 if !is_valid {
527 valid = false;
528 elements.push(get_unique_selector(&ele))
529 }
530 }
531 }
532 _ => ()
533 }
534 }
535
536 let message = if !valid { t!(&get_message_i18n_str_raw( &Guideline::Compatible, "", "2_msg_pattern2", ""), locale = auditor.locale, msgNodeType = r#""input""#, builtAttrs = r#""value="something" ""#) } else { Default::default() };
537
538 Validation::new(valid, "", elements, message).into()
539 }),
540 ])),
541 ("blink", Vec::from([
542 Rule::new(Techniques::F47.into(), IssueType::Error, Principle::Operable, Guideline::EnoughTime, "2", |nodes, _auditor| {
543 Validation::new_issue(nodes.is_empty(), "").into()
544 }),
545 ])),
546 ("object", Vec::from([
547 Rule::new(Techniques::F47.into(), IssueType::Error, Principle::Perceivable, Guideline::TextAlternatives, "1", |nodes, _auditor| {
548 let mut valid = true;
549 let mut elements = Vec::new();
550
551 for ele in nodes {
552 let ele = ele.0;
553 let empty = ele.text();
554 if empty.count() >= 1 {
555 valid = false;
556 elements.push(get_unique_selector(&ele))
557 }
558 }
559
560 Validation::new(valid, "", elements, Default::default()).into()
561 }),
562 ])),
563 ("area",Vec::from([
564 Rule::new(Techniques::H24.into(), IssueType::Error, Principle::Perceivable, Guideline::TextAlternatives, "1", |nodes, _auditor| {
565 let mut valid = true;
566 let mut elements = Vec::new();
567
568 for ele in nodes {
569 let ele = ele.0;
570 if !has_alt_prop(ele) {
571 valid = false;
572 elements.push(get_unique_selector(&ele));
573 }
574 }
575
576 Validation::new(valid, "ImageMapAreaNoAlt", elements, Default::default()).into()
577 })
578 ])),
579 ("map",Vec::from([
580 Rule::new(Techniques::H24.into(), IssueType::Error, Principle::Perceivable, Guideline::TextAlternatives, "1", |nodes, _auditor|{
581 let mut valid = true;
582 let mut elements = Vec::new();
583
584 for ele in nodes{
585 let ele = ele.0;
586 if !has_alt_prop(ele){
587 valid = false;
588 elements.push(get_unique_selector(&ele));
589 }
590 }
591
592 Validation::new(valid,"ImageMapNoAlt",elements, Default::default()).into()
593 })
594 ])),
595 ("fieldset", Vec::from([
596 Rule::new(Techniques::H71.into(), IssueType::Error, Principle::Perceivable, Guideline::Adaptable, "1", |nodes, _auditor| {
597 let mut valid = true;
598 let selector = unsafe { Selector::parse("legend").unwrap_unchecked() };
599 let mut elements = Vec::new();
600
601 for ele in nodes {
602 let ele = ele.0;
603 let mut e = ele.select(&selector);
604 let mut has_legend = false;
605
606 while let Some(el) = e.next() {
607 has_legend = true;
608 if el.text().count() == 0 {
609 valid = false;
610 elements.push(get_unique_selector(&ele))
611 }
612 }
613 if valid && !has_legend {
614 valid = false;
615 }
616 }
617
618 Validation::new(valid, "NoLegend", elements, Default::default()).into()
619 }),
620 ])),
621 ("applet", Vec::from([
622 Rule::new(Techniques::H35.into(), IssueType::Error, Principle::Perceivable, Guideline::TextAlternatives, "1", |nodes, _auditor| {
623 let mut valid = true;
624 let mut elements = Vec::new();
625
626 for ele in nodes {
627 let ele = ele.0;
628 if !has_alt_prop(ele) {
629 valid = false;
630 elements.push(get_unique_selector(&ele))
631 }
632 }
633
634 Validation::new(valid, "2", elements, Default::default()).into()
635 }),
636 Rule::new(Techniques::H35.into(), IssueType::Error, Principle::Perceivable, Guideline::TextAlternatives, "1", |nodes, _auditor| {
637 let mut valid = true;
638 let mut elements = Vec::new();
639
640 for ele in nodes {
641 let ele = ele.0;
642 let empty = ele.has_children() || !ele.inner_html().trim().is_empty();
643 if !empty {
644 valid = false;
645 elements.push(get_unique_selector(&ele))
646 }
647 }
648
649 Validation::new(valid, "3", elements, Default::default()).into()
650 }),
651 ])),
652 ]
653 .into_iter()
654 .collect();
655}