UNPKG

axe-core

Version:

Accessibility engine for automated Web UI testing

255 lines (225 loc) • 7.37 kB
function cssOrientationLockEvaluate(node, options, virtualNode, context) { const { cssom = undefined } = context || {}; const { degreeThreshold = 0 } = options || {}; if (!cssom || !cssom.length) { return undefined; } let isLocked = false; let relatedElements = []; const rulesGroupByDocumentFragment = groupCssomByDocument(cssom); for (const key of Object.keys(rulesGroupByDocumentFragment)) { const { root, rules } = rulesGroupByDocumentFragment[key]; const orientationRules = rules.filter(isMediaRuleWithOrientation); if (!orientationRules.length) { continue; } orientationRules.forEach(({ cssRules }) => { Array.from(cssRules).forEach(cssRule => { const locked = getIsOrientationLocked(cssRule); // if locked and not root HTML, preserve as relatedNodes if (locked && cssRule.selectorText.toUpperCase() !== 'HTML') { const elms = Array.from(root.querySelectorAll(cssRule.selectorText)) || []; relatedElements = relatedElements.concat(elms); } isLocked = isLocked || locked; }); }); } if (!isLocked) { return true; } if (relatedElements.length) { this.relatedNodes(relatedElements); } return false; /** * Group given cssom by document/ document fragment * @param {Array<Object>} allCssom cssom * @return {Object} */ function groupCssomByDocument(cssObjectModel) { return cssObjectModel.reduce((out, { sheet, root, shadowId }) => { const key = shadowId ? shadowId : 'topDocument'; if (!out[key]) { out[key] = { root, rules: [] }; } if (!sheet || !sheet.cssRules) { return out; } const rules = Array.from(sheet.cssRules); out[key].rules = out[key].rules.concat(rules); return out; }, {}); } /** * Filter CSS Rules that target Orientation CSS Media Features * @param {Array<Object>} cssRules * @returns {Array<Object>} */ function isMediaRuleWithOrientation({ type, cssText }) { /** * Filter: * CSSRule.MEDIA_Rule * -> https://developer.mozilla.org/en-US/docs/Web/API/CSSMediaRule */ if (type !== 4) { return false; } /** * Filter: * CSSRule with conditionText of `orientation` */ return ( /orientation:\s*landscape/i.test(cssText) || /orientation:\s*portrait/i.test(cssText) ); } /** * Interpolate a given CSS Rule to ascertain if orientation is locked by use of any transformation functions that affect rotation along the Z Axis * @param {Object} cssRule given CSS Rule * @property {String} cssRule.selectorText selector text targetted by given cssRule * @property {Object} cssRule.style style * @return {Boolean} */ function getIsOrientationLocked({ selectorText, style }) { if (!selectorText || style.length <= 0) { return false; } const transformStyle = style.transform || style.webkitTransform || style.msTransform || false; if (!transformStyle) { return false; } /** * get last match/occurence of a transformation function that can affect rotation along Z axis */ const matches = transformStyle.match( /(rotate|rotateZ|rotate3d|matrix|matrix3d)\(([^)]+)\)(?!.*(rotate|rotateZ|rotate3d|matrix|matrix3d))/ ); if (!matches) { return false; } const [, transformFn, transformFnValue] = matches; let degrees = getRotationInDegrees(transformFn, transformFnValue); if (!degrees) { return false; } degrees = Math.abs(degrees); /** * When degree is a multiple of 180, it is not considered an orientation lock */ if (Math.abs(degrees - 180) % 180 <= degreeThreshold) { return false; } return Math.abs(degrees - 90) % 90 <= degreeThreshold; } /** * Interpolate rotation along the z axis from a given value to a transform function * @param {String} transformFunction CSS transformation function * @param {String} transformFnValue value applied to a transform function (contains a unit) * @returns {Number} */ function getRotationInDegrees(transformFunction, transformFnValue) { switch (transformFunction) { case 'rotate': case 'rotateZ': return getAngleInDegrees(transformFnValue); case 'rotate3d': const [, , z, angleWithUnit] = transformFnValue .split(',') .map(value => value.trim()); if (parseInt(z) === 0) { // no transform is applied along z axis -> ignore return; } return getAngleInDegrees(angleWithUnit); case 'matrix': case 'matrix3d': return getAngleInDegreesFromMatrixTransform(transformFnValue); default: return; } } /** * Get angle in degrees from a transform value by interpolating the unit of measure * @param {String} angleWithUnit value applied to a transform function (Eg: 1turn) * @returns{Number|undefined} */ function getAngleInDegrees(angleWithUnit) { const [unit] = angleWithUnit.match(/(deg|grad|rad|turn)/) || []; if (!unit) { return; } const angle = parseFloat(angleWithUnit.replace(unit, ``)); switch (unit) { case 'rad': return convertRadToDeg(angle); case 'grad': return convertGradToDeg(angle); case 'turn': return convertTurnToDeg(angle); case 'deg': default: return parseInt(angle); } } /** * Get angle in degrees from a transform value applied to `matrix` or `matrix3d` transform functions * @param {String} transformFnValue value applied to a transform function (contains a unit) * @returns {Number} */ function getAngleInDegreesFromMatrixTransform(transformFnValue) { const values = transformFnValue.split(','); /** * Matrix 2D * Notes: https://css-tricks.com/get-value-of-css-rotation-through-javascript/ */ if (values.length <= 6) { const [a, b] = values; const radians = Math.atan2(parseFloat(b), parseFloat(a)); return convertRadToDeg(radians); } /** * Matrix 3D * Notes: https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix */ const sinB = parseFloat(values[8]); const b = Math.asin(sinB); const cosB = Math.cos(b); const rotateZRadians = Math.acos(parseFloat(values[0]) / cosB); return convertRadToDeg(rotateZRadians); } /** * Convert angle specified in unit radians to degrees * See - https://drafts.csswg.org/css-values-3/#rad * @param {Number} radians radians * @return {Number} */ function convertRadToDeg(radians) { return Math.round(radians * (180 / Math.PI)); } /** * Convert angle specified in unit grad to degrees * See - https://drafts.csswg.org/css-values-3/#grad * @param {Number} grad grad * @return {Number} */ function convertGradToDeg(grad) { grad = grad % 400; if (grad < 0) { grad += 400; } return Math.round((grad / 400) * 360); } /** * Convert angle specifed in unit turn to degrees * See - https://drafts.csswg.org/css-values-3/#turn * @param {Number} turn * @returns {Number} */ function convertTurnToDeg(turn) { return Math.round(360 / (1 / turn)); } } export default cssOrientationLockEvaluate;