-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1287 from IBMa/dev-1154
newrule(iframe_interactive_tabbable) Iframe with interactive content should not be excluded from tab order using tabindex
- Loading branch information
Showing
29 changed files
with
1,173 additions
and
4 deletions.
There are no files selected for viewing
107 changes: 107 additions & 0 deletions
107
accessibility-checker-engine/help-v4/en-US/element_scrollable_tabbable.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
<html lang="en-US"> | ||
<head> | ||
<!-- | ||
/****************************************************************************** | ||
Copyright:: 2022- IBM, Inc | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*****************************************************************************/ | ||
--> | ||
<!-- Title and messages generated at build time --> | ||
<link rel="icon" href="https://ibm.com/able/favicon-32x32.png" type="image/png"> | ||
<link rel="icon" href="https://ibm.com/able/favicon.svg" type="image/svg+xml"> | ||
<link rel="stylesheet" href="../common/help.css" /> | ||
<script type="module"> | ||
import "https://1.www.s81c.com/common/carbon/web-components/tag/latest/code-snippet.min.js"; | ||
import "https://1.www.s81c.com/common/carbon/web-components/tag/latest/list.min.js"; | ||
</script> | ||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | ||
<script src="../common/help.js"></script> | ||
</head> | ||
<body> | ||
<div class="bx--grid toolHelp"> | ||
<div class="bx--row"> | ||
<div class="bx--col-sm-4 bx--col-md-8 bx--col-lg-16 toolHead"> | ||
<!-- Group message injected here --> | ||
<h3 id="ruleMessage"></h3> | ||
<!-- Severity level injected here --> | ||
<div id="locLevel"></div> | ||
<!-- Rule specific message injected here --> | ||
<p id="groupLabel"></p> | ||
</div> | ||
</div> | ||
<div class="bx--row"> | ||
<div class="bx--col-sm-4 bx--col-md-5 bx--col-lg-8 toolMain"> | ||
<!-- Start main panel --> | ||
<mark-down><script type="text/plain"> | ||
|
||
### Why is this important? | ||
|
||
Many users need to be able to use keyboard and keyboard-like emulators to navigate to and operate – such as scrolling up-down and left-right with arrow keys – scrollable elements to reveal all the visible content. | ||
Only some browsers (e.g. Firefox) will automatically include _**any**_ scrollable element in the tab-order (tabbable sequence) to ensure keyboard accessibility. | ||
Browsers will automatically make scrollable elements tabbable when they _**contain**_ tabbable elements (e.g. buttons) without requiring an explicit `tabindex`attribute on the container element. | ||
However, setting the `tabindex` attribute on a scrollable container itself to a negative value excludes it and its tabbable content from the tab-order, | ||
which effectively makes it keyboard inaccessible and blocks many users. | ||
|
||
Note: this rule is not applicable to purely decorative content and whitespace. | ||
Although occasionally this rule may fail scrollable elements with alternate scroll controls such as through a button or customer scrollbar, | ||
it’s strongly recommended that developers still include the standard keyboard behavior that users expect. | ||
|
||
<!-- the "detected element" and the "unallowed semantics" will be displayed in the rule message above, so the “Why important” paragraph assumes the reader will understand it in context --> | ||
|
||
<!-- This is where the code snippet that triggered the rule is injected --> | ||
<div id="locSnippet"></div> | ||
|
||
### What to do | ||
|
||
* Ensure scrollable elements that contain visible non-tabbable interactive elements are not excluded from the tab-order by adding a `tabindex` attribute value greater than or equal to `”0”` to the container element | ||
* **Or*, if the scrollable element contains tabbable content, ensure that container elements does not have a negative `tabindex` that effectively removes the scrollable element from the tab-order | ||
|
||
Example of a keyboard scrollable element: | ||
``` | ||
<div style="height: 50px; width: 300px; overflow: scroll;" tabindex="0"> | ||
<h1>Important but lengthy content</h1> | ||
<p>This is important content that the designer decided to not always display **all* the content at the same time when the area is restricted in a relatively small space. When scrollbars appear, the developer needs to ensure the element will be scrollable via the arrow keys.</p> | ||
<p>Lorem ipsum… but no buttons, links, or other tabbable content.</p> | ||
</div> | ||
``` | ||
|
||
</script></mark-down> | ||
<!-- End main panel --> | ||
<!-- This is where the rule id is injected --> | ||
<div id="ruleInfo"></div> | ||
</div> | ||
<div class="bx--col-sm-4 bx--col-md-3 bx--col-lg-4 toolSide"> | ||
<!-- Start side panel --> | ||
<mark-down><script type="text/plain"> | ||
|
||
### About this requirement | ||
|
||
* [IBM 2.1.1 Keyboard](https://www.ibm.com/able/requirements/requirements/#2_1_1) | ||
* [WCAG technique G202: Ensuring keyboard control for all functionality](https://www.w3.org/WAI/WCAG21/Techniques/general/G202) | ||
* [ARIA practices - Developing a keyboard interface]( https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/) | ||
* [ARIA practices - The tab sequence]( https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#keyboardnavigationbetweencomponents(thetabsequence)) | ||
* [HTML - The tabindex attribute]( https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute) | ||
* [Verify phase – Test with a keyboard](https://www.ibm.com/able/toolkit/verify/manual/#test-with-a-keyboard) | ||
|
||
|
||
### Who does this affect? | ||
|
||
* People who physically cannot use a pointing device | ||
* People using a screen reader, including blind, low vision, and neurodivergent people | ||
* People using a keyboard and alternate keyboards and input devices that act as keyboard emulators like speech input software or on-screen keyboards | ||
|
||
</script></mark-down> | ||
<!-- End side panel --> | ||
</div> | ||
</div> | ||
</div> | ||
</body> | ||
</html> |
98 changes: 98 additions & 0 deletions
98
accessibility-checker-engine/help-v4/en-US/iframe_interactive_tabbable.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<html lang="en-US"> | ||
<head> | ||
<!-- | ||
/****************************************************************************** | ||
Copyright:: 2022- IBM, Inc | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*****************************************************************************/ | ||
--> | ||
<!-- Title and messages generated at build time --> | ||
<link rel="icon" href="https://ibm.com/able/favicon-32x32.png" type="image/png"> | ||
<link rel="icon" href="https://ibm.com/able/favicon.svg" type="image/svg+xml"> | ||
<link rel="stylesheet" href="../common/help.css" /> | ||
<script type="module"> | ||
import "https://1.www.s81c.com/common/carbon/web-components/tag/latest/code-snippet.min.js"; | ||
import "https://1.www.s81c.com/common/carbon/web-components/tag/latest/list.min.js"; | ||
</script> | ||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | ||
<script src="../common/help.js"></script> | ||
</head> | ||
<body> | ||
<div class="bx--grid toolHelp"> | ||
<div class="bx--row"> | ||
<div class="bx--col-sm-4 bx--col-md-8 bx--col-lg-16 toolHead"> | ||
<!-- Group message injected here --> | ||
<h3 id="ruleMessage"></h3> | ||
<!-- Severity level injected here --> | ||
<div id="locLevel"></div> | ||
<!-- Rule specific message injected here --> | ||
<p id="groupLabel"></p> | ||
</div> | ||
</div> | ||
<div class="bx--row"> | ||
<div class="bx--col-sm-4 bx--col-md-5 bx--col-lg-8 toolMain"> | ||
<!-- Start main panel --> | ||
<mark-down><script type="text/plain"> | ||
|
||
### Why is this important? | ||
|
||
Many users need to be able to use the keyboard and keyboard-like emulators to navigate to and interact with content and web applications even within any `<iframe>` elements. | ||
Setting the `tabindex` attribute on an `<iframe>` element to a negative value excludes its content from the tab-order. | ||
For example, a `button` should be in the tab-order within an `iframe`, | ||
but if the `<iframe>` itself is taken out of the tab-order, | ||
the `button` is removed from the tab-order and effectively is keyboard inaccessible and blocks some users. | ||
|
||
<!-- the "detected element" and the "unallowed semantics" will be displayed in the rule message above, so the "Why important" paragraph assumes the reader will understand it in context --> | ||
|
||
<!-- This is where the code snippet that triggered the rule is injected --> | ||
|
||
<div id="locSnippet"></div> | ||
|
||
### What to do | ||
|
||
* Ensure visible interactive elements are not excluded from the tab-order by adding a `tabindex` attribute value greater than or equal to `"0"` to the `<iframe>` element | ||
* **Or**, if not intended to be interactable, remove or hide the content from within the `<iframe>` element | ||
|
||
Example of a tabbable `<iframe>`: | ||
``` | ||
<iframe title="Inline example" srcdoc="<a href="http://xyz">link text</a>" tabindex="0" /> | ||
``` | ||
|
||
</script></mark-down> | ||
<!-- End main panel --> | ||
<!-- This is where the rule id is injected --> | ||
<div id="ruleInfo"></div> | ||
</div> | ||
<div class="bx--col-sm-4 bx--col-md-3 bx--col-lg-4 toolSide"> | ||
<!-- Start side panel --> | ||
<mark-down><script type="text/plain"> | ||
|
||
### About this requirement | ||
|
||
* [IBM 2.1.1 Keyboard](https://www.ibm.com/able/requirements/requirements/#2_1_1) | ||
* [ARIA practices - Developing a keyboard interface]( https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/) | ||
* [ARIA practices - The tab sequence]( https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#keyboardnavigationbetweencomponents(thetabsequence)) | ||
* [HTML - The tabindex attribute]( https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute) | ||
* [Verify phase – Test with a keyboard](https://www.ibm.com/able/toolkit/verify/manual/#test-with-a-keyboard) | ||
|
||
### Who does this affect? | ||
|
||
* People who physically cannot use a pointing device | ||
* People using a screen reader, including blind, low vision, and neurodivergent people | ||
* People using a keyboard and alternate keyboards and input devices that act as keyboard emulators like speech input software and on-screen keyboards | ||
|
||
</script></mark-down> | ||
<!-- End side panel --> | ||
</div> | ||
</div> | ||
</div> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
accessibility-checker-engine/src/v4/rules/element_scrollable_tabbable.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/****************************************************************************** | ||
Copyright:: 2022- IBM, Inc | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*****************************************************************************/ | ||
|
||
import { RPTUtil } from "../../v2/checker/accessibility/util/legacy"; | ||
import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy } from "../api/IRule"; | ||
import { eRulePolicy, eToolkitLevel } from "../api/IRule"; | ||
import { VisUtil } from "../../v2/dom/VisUtil"; | ||
import { getComputedStyle, getPixelsFromStyle } from "..//util/CSSUtil"; | ||
|
||
export let element_scrollable_tabbable: Rule = { | ||
id: "element_scrollable_tabbable", | ||
context: "dom:*", | ||
dependencies: [], | ||
help: { | ||
"en-US": { | ||
"group": "element_scrollable_tabbable.html", | ||
"pass_tabbable": "element_scrollable_tabbable.html", | ||
"pass_interactive": "element_scrollable_tabbable.html", | ||
"fail_scrollable": "element_scrollable_tabbable.html" | ||
} | ||
}, | ||
messages: { | ||
"en-US": { | ||
"group": "Scrollable elements should be tabbable or contain tabbable content", | ||
"pass_tabbable": "The scrollable element is tabbable", | ||
"pass_interactive": "The scrollable element has tabbable content", | ||
"fail_scrollable": "The scrollable element <{0}> with non-interactive content is not tabbable" | ||
} | ||
}, | ||
rulesets: [{ | ||
id: ["IBM_Accessibility", "WCAG_2_1", "WCAG_2_0"], | ||
num: ["2.1.1"], | ||
level: eRulePolicy.VIOLATION, | ||
toolkitLevel: eToolkitLevel.LEVEL_ONE | ||
}], | ||
act: ["ossw9k"], | ||
run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { | ||
const ruleContext = context["dom"].node as HTMLElement; | ||
//skip the check if the element is hidden or disabled | ||
if (!VisUtil.isNodeVisible(ruleContext) || RPTUtil.isNodeDisabled(ruleContext)) | ||
return; | ||
|
||
//skip elements | ||
if (RPTUtil.getAncestor(ruleContext, ["iframe", "svg", "script", "meta"])) | ||
return null; | ||
|
||
//skip if no visible content | ||
if (!RPTUtil.hasInnerContent(ruleContext)) | ||
return null; | ||
|
||
const nodeName = ruleContext.nodeName.toLowerCase(); | ||
const styles = getComputedStyle(ruleContext); | ||
// not scrollable, inapplicable | ||
if ((styles.overflowX === 'visible' || styles.overflowX === 'hidden') | ||
&& (styles.overflowY === 'visible' || styles.overflowY === 'hidden')) | ||
return null; | ||
|
||
// ignore if the overall scrollable element (clientWidth + scrollbarWidth and clientHeight + scrollbarHeight) is too small to be visible on screen | ||
if (Math.max(ruleContext.offsetWidth, ruleContext.offsetHeight) < 30 || Math.min(ruleContext.offsetWidth, ruleContext.offsetHeight) < 15) | ||
return null; | ||
|
||
// ignore if both x and y scroll distances < element's horizontal/vertical padding | ||
const padding_x = getPixelsFromStyle(styles.paddingLeft, ruleContext) + getPixelsFromStyle(styles.paddingRight, ruleContext); | ||
const padding_y = getPixelsFromStyle(styles.paddingTop, ruleContext) + getPixelsFromStyle(styles.paddingBottom, ruleContext); | ||
if (ruleContext.scrollWidth - ruleContext.clientWidth < 1 + padding_x | ||
&& ruleContext.scrollHeight - ruleContext.clientHeight < 1+ padding_y) | ||
return null; | ||
|
||
// pass iframe element has a tabindex attribute value that is not negative | ||
if (ruleContext.hasAttribute("tabindex") && parseInt(ruleContext.getAttribute("tabindex")) >= 0) | ||
return RulePass("pass_tabbable"); | ||
|
||
// check if element content is tabbable | ||
const count = RPTUtil.getTabbableChildren(ruleContext); | ||
if (count > 0) | ||
return RulePass("pass_interactive"); | ||
|
||
// ignore in Firefox if no tabindex at all (not tested in embedded or any simulator) | ||
if (!ruleContext.hasAttribute("tabindex") && navigator.userAgent.indexOf("Firefox") > -1) | ||
return null; | ||
|
||
return RuleFail("fail_scrollable", [nodeName]); | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
accessibility-checker-engine/src/v4/rules/iframe_interactive_tabbable.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/****************************************************************************** | ||
Copyright:: 2022- IBM, Inc | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*****************************************************************************/ | ||
|
||
import { RPTUtil } from "../../v2/checker/accessibility/util/legacy"; | ||
import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy } from "../api/IRule"; | ||
import { eRulePolicy, eToolkitLevel } from "../api/IRule"; | ||
import { VisUtil } from "../../v2/dom/VisUtil"; | ||
|
||
export let iframe_interactive_tabbable: Rule = { | ||
id: "iframe_interactive_tabbable", | ||
context: "dom:iframe", | ||
dependencies: [], | ||
help: { | ||
"en-US": { | ||
"group": "iframe_interactive_tabbable.html", | ||
"pass": "iframe_interactive_tabbable.html", | ||
"fail_invalid": "iframe_interactive_tabbable.html" | ||
} | ||
}, | ||
messages: { | ||
"en-US": { | ||
"group": "Iframe with interactive content should not be excluded from tab order using tabindex", | ||
"pass": "The iframe with interactive content is not excluded from the tab order using tabindex", | ||
"fail_invalid": "The <iframe> with interactive content is excluded from tab order using tabindex" | ||
} | ||
}, | ||
rulesets: [{ | ||
id: ["IBM_Accessibility", "WCAG_2_1", "WCAG_2_0"], | ||
num: ["2.1.1"], | ||
level: eRulePolicy.VIOLATION, | ||
toolkitLevel: eToolkitLevel.LEVEL_ONE | ||
}], | ||
act: ["akn7bn"], | ||
run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => { | ||
const ruleContext = context["dom"].node as HTMLElement; | ||
//skip the check if the element is hidden or disabled | ||
if (VisUtil.isNodeHiddenFromAT(ruleContext) || RPTUtil.isNodeDisabled(ruleContext)) | ||
return; | ||
|
||
const bounds = context["dom"].bounds; | ||
//in case the bounds not available | ||
if (!bounds) return null; | ||
|
||
// ignore if iframe is too small to be visible on screen | ||
if (Math.max(bounds['height'], bounds['width']) < 30 || Math.min(bounds['height'], bounds['width']) < 15) | ||
return null; | ||
|
||
// pass iframe element does not have a tabindex attribute value that is a negative number | ||
if (!ruleContext.hasAttribute("tabindex") || parseInt(ruleContext.getAttribute("tabindex")) >= 0) | ||
return RulePass("pass"); | ||
|
||
// check iframe content | ||
const iframElem = ruleContext as HTMLIFrameElement; | ||
if (!iframElem || !iframElem.contentDocument || !iframElem.contentDocument.documentElement) | ||
return null; | ||
|
||
const count = RPTUtil.getTabbableChildren(ruleContext); | ||
if (count > 0) | ||
return RuleFail("fail_invalid"); | ||
|
||
return null; | ||
} | ||
} |
Oops, something went wrong.