Skip to content

Commit a1daf85

Browse files
authored
fix(engine): For a_target_warning, check aria-describedby for notifying users of new windows (#2448)
* Various optimizations * Fix build * Debug info on large pages * RulePass, RuleFail needs to be in body of main run function * Remove some redundancy in calls to selector match and mix mem issue * Short circuit the text_spacing_check if the computed style passes * More performance tweaks * Instead of calling "contains" twice, call compareDocumentPosition * Test updates * For getDefinedStyles, only check styles that have the properties we're looking for * Consider aria-describedby for 'new window' notices
1 parent 88b14a0 commit a1daf85

3 files changed

Lines changed: 283 additions & 0 deletions

File tree

accessibility-checker-engine/src/v4/rules/a_target_warning.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import { CommonUtil } from "../util/CommonUtil";
1515
import { VisUtil } from "../util/VisUtil";
16+
import { FragmentUtil } from "../../v2/checker/accessibility/util/fragment";
1617

1718
import { Rule, RuleResult, RuleContext, RulePotential, RulePass, RuleContextHierarchy } from "../api/IRule";
1819
import { eRulePolicy, eToolkitLevel } from "../api/IRule";
@@ -66,6 +67,21 @@ export const a_target_warning: Rule = {
6667
let textStr = CommonUtil.getInnerText(ruleContext);
6768
if (ruleContext.hasAttribute("title"))
6869
textStr += " " + ruleContext.getAttribute("title");
70+
71+
// Check aria-describedby for warning text
72+
if (ruleContext.hasAttribute("aria-describedby")) {
73+
let describedbyIds = ruleContext.getAttribute("aria-describedby").trim().split(/\s+/);
74+
for (let id of describedbyIds) {
75+
let referencedElem = FragmentUtil.getById(ruleContext, id);
76+
if (referencedElem) {
77+
let describedText = CommonUtil.getInnerText(referencedElem);
78+
if (describedText && describedText.trim().length > 0) {
79+
textStr += " " + describedText;
80+
}
81+
}
82+
}
83+
}
84+
6985
for (let i = 0; !passed && i < params.paramWinText.value.length; ++i)
7086
if (textStr.indexOf(params.paramWinText.value[i]) != -1) passed = true;
7187
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<!DOCTYPE html>
2+
3+
<!--
4+
/******************************************************************************
5+
Copyright:: 2026- IBM, Inc
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*****************************************************************************/
19+
-->
20+
21+
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
22+
23+
<head>
24+
<title>aria-describedby invalid test cases</title>
25+
</head>
26+
27+
<body>
28+
<h2>Test case: aria-describedby-invalid.html</h2>
29+
30+
<p>Testing aria-describedby without proper warning text (should trigger potential warnings)</p>
31+
32+
<!-- Hidden element without warning text -->
33+
<div hidden>
34+
<span id="no-warning">This is additional information</span>
35+
<span id="external-info">External resource</span>
36+
</div>
37+
38+
<!-- Test 1: Link with aria-describedby but no warning text (should trigger POTENTIAL) -->
39+
<a href="https://example.com" target="_blank" aria-describedby="no-warning">
40+
Example Link 1
41+
</a>
42+
43+
<!-- Test 2: Link with aria-describedby pointing to non-existent ID (should trigger POTENTIAL) -->
44+
<a href="https://example.com" target="_blank" aria-describedby="non-existent-id">
45+
Example Link 2
46+
</a>
47+
48+
<!-- Test 3: Link with target="_blank" but no warning at all (should trigger POTENTIAL) -->
49+
<a href="https://example.com" target="_blank">
50+
Example Link 3
51+
</a>
52+
53+
<!-- Test 4: Link with aria-describedby but empty referenced element (should trigger POTENTIAL) -->
54+
<span id="empty-desc"></span>
55+
<a href="https://example.com" target="_blank" aria-describedby="empty-desc">
56+
Example Link 4
57+
</a>
58+
59+
<script type="text/javascript">
60+
UnitTest = {
61+
ruleIds: ["a_target_warning"],
62+
results: [{
63+
"ruleId": "a_target_warning",
64+
"category": "Accessibility",
65+
"value": [
66+
"INFORMATION", "POTENTIAL"
67+
],
68+
"path": {
69+
"dom": "/html[1]/body[1]/a[1]",
70+
"aria": "/document[1]/link[1]"
71+
},
72+
"reasonId": "potential_warn",
73+
"message": "Inform the user when their input action will open a new window",
74+
"messageArgs": [],
75+
"apiArgs": []
76+
}, {
77+
"ruleId": "a_target_warning",
78+
"category": "Accessibility",
79+
"value": [
80+
"INFORMATION", "POTENTIAL"
81+
],
82+
"path": {
83+
"dom": "/html[1]/body[1]/a[2]",
84+
"aria": "/document[1]/link[2]"
85+
},
86+
"reasonId": "potential_warn",
87+
"message": "Inform the user when their input action will open a new window",
88+
"messageArgs": [],
89+
"apiArgs": []
90+
}, {
91+
"ruleId": "a_target_warning",
92+
"category": "Accessibility",
93+
"value": [
94+
"INFORMATION", "POTENTIAL"
95+
],
96+
"path": {
97+
"dom": "/html[1]/body[1]/a[3]",
98+
"aria": "/document[1]/link[3]"
99+
},
100+
"reasonId": "potential_warn",
101+
"message": "Inform the user when their input action will open a new window",
102+
"messageArgs": [],
103+
"apiArgs": []
104+
}, {
105+
"ruleId": "a_target_warning",
106+
"category": "Accessibility",
107+
"value": [
108+
"INFORMATION", "POTENTIAL"
109+
],
110+
"path": {
111+
"dom": "/html[1]/body[1]/a[4]",
112+
"aria": "/document[1]/link[4]"
113+
},
114+
"reasonId": "potential_warn",
115+
"message": "Inform the user when their input action will open a new window",
116+
"messageArgs": [],
117+
"apiArgs": []
118+
}]
119+
}
120+
</script>
121+
</body>
122+
123+
</html>
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<!DOCTYPE html>
2+
3+
<!--
4+
/******************************************************************************
5+
Copyright:: 2026- IBM, Inc
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*****************************************************************************/
19+
-->
20+
21+
<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
22+
23+
<head>
24+
<title>aria-describedby valid test cases</title>
25+
</head>
26+
27+
<body>
28+
<h2>Test case: aria-describedby-valid.html</h2>
29+
30+
<p>Testing aria-describedby support for a_target_warning rule (Issue #545)</p>
31+
32+
<!-- Hidden element with warning text (as per issue #545) -->
33+
<div hidden>
34+
<span id="a11y-new-window">Opens in a new window</span>
35+
<span id="a11y-new-tab">Opens in a new tab</span>
36+
</div>
37+
38+
<!-- Test 1: Link with aria-describedby pointing to "new window" text (should PASS) -->
39+
<a href="https://example.com" target="_blank" aria-describedby="a11y-new-window" rel="noreferrer noopener">
40+
Example Link 1
41+
</a>
42+
43+
<!-- Test 2: Link with aria-describedby pointing to "new tab" text (should PASS) -->
44+
<a href="https://example.com" target="_blank" aria-describedby="a11y-new-tab">
45+
Example Link 2
46+
</a>
47+
48+
<!-- Test 3: Link with aria-describedby and multiple IDs (should PASS) -->
49+
<span id="external-link">External link</span>
50+
<a href="https://example.com" target="_blank" aria-describedby="external-link a11y-new-window">
51+
Example Link 3
52+
</a>
53+
54+
<!-- Test 4: Link with visible aria-describedby element (should PASS) -->
55+
<a href="https://example.com" target="_blank" aria-describedby="visible-desc">
56+
Example Link 4
57+
</a>
58+
<span id="visible-desc">This link opens in a new window</span>
59+
60+
<!-- Test 5: Area element with aria-describedby (should PASS) -->
61+
<img src="../support/nav_home.gif" usemap="#TestMap" alt="Test Navigation" />
62+
<map name="TestMap">
63+
<area coords="15,7,94,23" shape="rect" href="test.htm" alt="Test Area" target="_blank" aria-describedby="a11y-new-window" />
64+
</map>
65+
66+
<script type="text/javascript">
67+
UnitTest = {
68+
ruleIds: ["a_target_warning"],
69+
results: [{
70+
"ruleId": "a_target_warning",
71+
"category": "Accessibility",
72+
"value": [
73+
"INFORMATION", "PASS"
74+
],
75+
"path": {
76+
"dom": "/html[1]/body[1]/a[1]",
77+
"aria": "/document[1]/link[1]"
78+
},
79+
"reasonId": "pass",
80+
"message": "The user is warned in advance that the input action opens a new window",
81+
"messageArgs": [],
82+
"apiArgs": []
83+
}, {
84+
"ruleId": "a_target_warning",
85+
"category": "Accessibility",
86+
"value": [
87+
"INFORMATION", "PASS"
88+
],
89+
"path": {
90+
"dom": "/html[1]/body[1]/a[2]",
91+
"aria": "/document[1]/link[2]"
92+
},
93+
"reasonId": "pass",
94+
"message": "The user is warned in advance that the input action opens a new window",
95+
"messageArgs": [],
96+
"apiArgs": []
97+
}, {
98+
"ruleId": "a_target_warning",
99+
"category": "Accessibility",
100+
"value": [
101+
"INFORMATION", "PASS"
102+
],
103+
"path": {
104+
"dom": "/html[1]/body[1]/a[3]",
105+
"aria": "/document[1]/link[3]"
106+
},
107+
"reasonId": "pass",
108+
"message": "The user is warned in advance that the input action opens a new window",
109+
"messageArgs": [],
110+
"apiArgs": []
111+
}, {
112+
"ruleId": "a_target_warning",
113+
"category": "Accessibility",
114+
"value": [
115+
"INFORMATION", "PASS"
116+
],
117+
"path": {
118+
"dom": "/html[1]/body[1]/a[4]",
119+
"aria": "/document[1]/link[4]"
120+
},
121+
"reasonId": "pass",
122+
"message": "The user is warned in advance that the input action opens a new window",
123+
"messageArgs": [],
124+
"apiArgs": []
125+
}, {
126+
"ruleId": "a_target_warning",
127+
"category": "Accessibility",
128+
"value": [
129+
"INFORMATION", "PASS"
130+
],
131+
"path": {
132+
"dom": "/html[1]/body[1]/map[1]/area[1]",
133+
"aria": "/document[1]/link[5]"
134+
},
135+
"reasonId": "pass",
136+
"message": "The user is warned in advance that the input action opens a new window",
137+
"messageArgs": [],
138+
"apiArgs": []
139+
}]
140+
}
141+
</script>
142+
</body>
143+
144+
</html>

0 commit comments

Comments
 (0)