-
Notifications
You must be signed in to change notification settings - Fork 791
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rule): css-orientation-lock (wcag21) (#1081)
**Note: This PR was branched from previous PR - #971 to have a cleaner commit history (without all the trials to fix CI failures). All reviews/ comments from the above PR has been tacked.** New rule: - WCAG 2.1 rule - Rule Id: `css-orientation-lock` - Description: Ensures that the content is not locked to any specific display orientation, and functionality of the content is operable in all display orientations (portrait/ landscape). Closes Issue: - #851 ## Reviewer checks **Required fields, to be filled out by PR reviewer(s)** - [ ] Follows the commit message policy, appropriate for next version - [ ] Has documentation updated, a DU ticket, or requires no documentation change - [ ] Includes new tests, or was unnecessary - [ ] Code is reviewed for security by: << Name here >>
- Loading branch information
1 parent
6bfff2b
commit 4ae4ea0
Showing
23 changed files
with
1,138 additions
and
333 deletions.
There are no files selected for viewing
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
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
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,132 @@ | ||
/* global context */ | ||
|
||
// extract asset of type `cssom` from context | ||
const { cssom = undefined } = context || {}; | ||
|
||
// if there is no cssom <- return incomplete | ||
if (!cssom || !cssom.length) { | ||
return undefined; | ||
} | ||
|
||
// combine all rules from each sheet into one array | ||
const rulesGroupByDocumentFragment = cssom.reduce( | ||
(out, { sheet, root, shadowId }) => { | ||
// construct key based on shadowId or top level document | ||
const key = shadowId ? shadowId : 'topDocument'; | ||
// init property if does not exist | ||
if (!out[key]) { | ||
out[key] = { | ||
root, | ||
rules: [] | ||
}; | ||
} | ||
// check if sheet and rules exist | ||
if (!sheet || !sheet.cssRules) { | ||
//return | ||
return out; | ||
} | ||
const rules = Array.from(sheet.cssRules); | ||
// add rules into same document fragment | ||
out[key].rules = out[key].rules.concat(rules); | ||
|
||
//return | ||
return out; | ||
}, | ||
{} | ||
); | ||
|
||
// Note: | ||
// Some of these functions can be extracted to utils, but best to do it when other cssom rules are authored. | ||
|
||
// extract styles for each orientation rule to verify transform is applied | ||
let isLocked = false; | ||
let relatedElements = []; | ||
|
||
Object.keys(rulesGroupByDocumentFragment).forEach(key => { | ||
const { root, rules } = rulesGroupByDocumentFragment[key]; | ||
|
||
// filter media rules from all rules | ||
const mediaRules = rules.filter(r => { | ||
// doc: https://developer.mozilla.org/en-US/docs/Web/API/CSSMediaRule | ||
// type value of 4 (CSSRule.MEDIA_RULE) pertains to media rules | ||
return r.type === 4; | ||
}); | ||
if (!mediaRules || !mediaRules.length) { | ||
return; | ||
} | ||
|
||
// narrow down to media rules with `orientation` as a keyword | ||
const orientationRules = mediaRules.filter(r => { | ||
// conditionText exists on media rules, which contains only the @media condition | ||
// eg: screen and (max-width: 767px) and (min-width: 320px) and (orientation: landscape) | ||
const cssText = r.cssText; | ||
return ( | ||
/orientation:\s+landscape/i.test(cssText) || | ||
/orientation:\s+portrait/i.test(cssText) | ||
); | ||
}); | ||
if (!orientationRules || !orientationRules.length) { | ||
return; | ||
} | ||
|
||
orientationRules.forEach(r => { | ||
// r.cssRules is a RULEList and not an array | ||
if (!r.cssRules.length) { | ||
return; | ||
} | ||
// cssRules ia a list of rules | ||
// a media query has framents of css styles applied to various selectors | ||
// iteration through cssRules and see if orientation lock has been applied | ||
Array.from(r.cssRules).forEach(cssRule => { | ||
/* eslint max-statements: ["error", 20], complexity: ["error", 15] */ | ||
|
||
// ensure selectorText exists | ||
if (!cssRule.selectorText) { | ||
return; | ||
} | ||
// ensure the given selector has styles declared (non empty selector) | ||
if (cssRule.style.length <= 0) { | ||
return; | ||
} | ||
|
||
// check if transform style exists | ||
const transformStyleValue = cssRule.style.transform || false; | ||
// transformStyleValue -> is the value applied to property | ||
// eg: "rotate(-90deg)" | ||
if (!transformStyleValue) { | ||
return; | ||
} | ||
|
||
const rotate = transformStyleValue.match(/rotate\(([^)]+)deg\)/); | ||
const deg = parseInt((rotate && rotate[1]) || 0); | ||
const locked = deg % 90 === 0 && deg % 180 !== 0; | ||
|
||
// if locked | ||
// and not root HTML | ||
// preserve as relatedNodes | ||
if (locked && cssRule.selectorText.toUpperCase() !== 'HTML') { | ||
const selector = cssRule.selectorText; | ||
const elms = Array.from(root.querySelectorAll(selector)); | ||
if (elms && elms.length) { | ||
relatedElements = relatedElements.concat(elms); | ||
} | ||
} | ||
|
||
// set locked boolean | ||
isLocked = locked; | ||
}); | ||
}); | ||
}); | ||
|
||
if (!isLocked) { | ||
// return | ||
return true; | ||
} | ||
|
||
// set relatedNodes | ||
if (relatedElements.length) { | ||
this.relatedNodes(relatedElements); | ||
} | ||
|
||
// return fail | ||
return false; |
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,11 @@ | ||
{ | ||
"id": "css-orientation-lock", | ||
"evaluate": "css-orientation-lock.js", | ||
"metadata": { | ||
"impact": "serious", | ||
"messages": { | ||
"pass": "Display is operable, and orientation lock does not exist", | ||
"fail": "CSS Orientation lock is applied, and makes display inoperable" | ||
} | ||
} | ||
} |
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
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,20 @@ | ||
{ | ||
"id": "css-orientation-lock", | ||
"selector": "html", | ||
"tags": [ | ||
"cat.structure", | ||
"wcag262", | ||
"wcag21aa", | ||
"experimental" | ||
], | ||
"metadata": { | ||
"description": "Ensures content is not locked to any specific display orientation, and the content is operable in all display orientations", | ||
"help": "CSS Media queries are not used to lock display orientation" | ||
}, | ||
"all": [ | ||
"css-orientation-lock" | ||
], | ||
"any": [], | ||
"none": [], | ||
"preload": true | ||
} |
Oops, something went wrong.
4ae4ea0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://dequeuniversity.com/rules/axe/3.1/css-orientation-lock created; content needs work.