diff --git a/lib/checks/mobile/css-orientation-lock.js b/lib/checks/mobile/css-orientation-lock.js index 60d92c5f54..5cf91c21c3 100644 --- a/lib/checks/mobile/css-orientation-lock.js +++ b/lib/checks/mobile/css-orientation-lock.js @@ -1,134 +1,251 @@ /* global context */ - -// extract asset of type `cssom` from context const { cssom = undefined } = context || {}; - -// if there is no cssom <- return incomplete +const { degreeThreshold = 0 } = options || {}; 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 +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} allCssom cssom + * @return {Object} + */ +function groupCssomByDocument(cssObjectModel) { + return cssObjectModel.reduce((out, { sheet, root, shadowId }) => { const key = shadowId ? shadowId : 'topDocument'; - // init property if does not exist + if (!out[key]) { - out[key] = { - root, - rules: [] - }; + 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. +/** + * Filter CSS Rules that target Orientation CSS Media Features + * @param {Array} cssRules + * @returns {Array} + */ +function isMediaRuleWithOrientation({ type, cssText }) { + /** + * Filter: + * CSSRule.MEDIA_Rule + * -> https://developer.mozilla.org/en-US/docs/Web/API/CSSMediaRule + */ + if (type !== 4) { + return false; + } -// extract styles for each orientation rule to verify transform is applied -let isLocked = false; -let relatedElements = []; + /** + * Filter: + * CSSRule with conditionText of `orientation` + */ + return ( + /orientation:\s*landscape/i.test(cssText) || + /orientation:\s*portrait/i.test(cssText) + ); +} -Object.keys(rulesGroupByDocumentFragment).forEach(key => { - const { root, rules } = rulesGroupByDocumentFragment[key]; +/** + * 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; + } - // 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; + const transformStyle = + style.transform || style.webkitTransform || style.msTransform || false; + if (!transformStyle) { + return false; } - // 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; + /** + * 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; } - 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 => { - // ensure selectorText exists - if (!cssRule.selectorText) { - return; - } - // ensure the given selector has styles declared (non empty selector) - if (cssRule.style.length <= 0) { - return; - } + 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; +} - // check if transform style exists (don't forget vendor prefixes) - const transformStyleValue = - cssRule.style.transform || - cssRule.style.webkitTransform || - cssRule.style.msTransform || - false; - // transformStyleValue -> is the value applied to property - // eg: "rotate(-90deg)" - if (!transformStyleValue) { +/** + * 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; + } +} - const rotate = transformStyleValue.match(/rotate\(([^)]+)deg\)/); - const deg = parseInt((rotate && rotate[1]) || 0); - const locked = deg % 90 === 0 && deg % 180 !== 0; +/** + * 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; + } - // 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); - } - } + 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); + } +} - // set locked boolean - isLocked = locked; - }); - }); -}); +/** + * 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); + } -if (!isLocked) { - // return - return true; + /** + * 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); } -// set relatedNodes -if (relatedElements.length) { - this.relatedNodes(relatedElements); +/** + * 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)); } -// return fail -return false; +/** + * 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)); +} diff --git a/lib/checks/mobile/css-orientation-lock.json b/lib/checks/mobile/css-orientation-lock.json index 8bdadb0a15..93acdb2bdf 100644 --- a/lib/checks/mobile/css-orientation-lock.json +++ b/lib/checks/mobile/css-orientation-lock.json @@ -1,6 +1,9 @@ { "id": "css-orientation-lock", "evaluate": "css-orientation-lock.js", + "options": { + "degreeThreshold": 2 + }, "metadata": { "impact": "serious", "messages": { diff --git a/lib/core/utils/preload-cssom.js b/lib/core/utils/preload-cssom.js index 30bf6c0dde..ffa230c159 100644 --- a/lib/core/utils/preload-cssom.js +++ b/lib/core/utils/preload-cssom.js @@ -33,9 +33,10 @@ axe.utils.preloadCssom = function preloadCssom({ treeRoot = axe._tree[0] }) { const convertDataToStylesheet = axe.utils.getStyleSheetFactory(dynamicDoc); - return getCssomForAllRootNodes(rootNodes, convertDataToStylesheet).then( - assets => flattenAssets(assets) - ); + return getCssomForAllRootNodes( + rootNodes, + convertDataToStylesheet + ).then(assets => flattenAssets(assets)); }; /** diff --git a/test/checks/mobile/css-orientation-lock.js b/test/checks/mobile/css-orientation-lock.js index 7d66892d31..cba6e8b131 100644 --- a/test/checks/mobile/css-orientation-lock.js +++ b/test/checks/mobile/css-orientation-lock.js @@ -2,32 +2,15 @@ describe('css-orientation-lock tests', function() { 'use strict'; var checkContext = axe.testUtils.MockCheckContext(); - var origCheck = checks['css-orientation-lock']; + var check = checks['css-orientation-lock']; var dynamicDoc = document.implementation.createHTMLDocument( 'Dynamic document for CSS Orientation Lock tests' ); afterEach(function() { - checks['css-orientation-lock'] = origCheck; checkContext.reset(); }); - var SHEET_DATA = { - BODY_STYLE: 'body { color: inherit; }', - MEDIA_STYLE_NON_ORIENTATION: - '@media (min-width: 400px) { background-color: red; }', - MEDIA_STYLE_ORIENTATION_EMPTY: - '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { }', - MEDIA_STYLE_ORIENTATION_WITHOUT_TRANSFORM: - '@media screen and (min-width: 1px) and (max-width: 2000px) and (orientation: portrait) { #mocha { color: red; } }', - MEDIA_STYLE_ORIENTATION_WITH_TRANSFORM_NOT_ROTATE: - '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { #mocha { transform: translateX(10px); -webkit-transform: translateX(10px); } }', - MEDIA_STYLE_ORIENTATION_WITH_TRANSFORM_ROTATE_180: - '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { body { transform: rotate(180deg); -webkit-transform: rotate(180deg); } }', - MEDIA_STYLE_ORIENTATION_WITH_TRANSFORM_ROTATE_90: - '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { #mocha { transform: rotate(270deg); -webkit-transform: rotate(270deg); } }' - }; - function getSheet(data) { var style = dynamicDoc.createElement('style'); style.type = 'text/css'; @@ -36,172 +19,126 @@ describe('css-orientation-lock tests', function() { return style.sheet; } - it('ensure that the check "css-orientation-lock" is invoked', function() { - checks['css-orientation-lock'] = { - evaluate: function() { - return 'invoked'; - } - }; - var actual = checks['css-orientation-lock'].evaluate.call( - checkContext, - document - ); - assert.equal(actual, 'invoked'); + it('returns undefined when CSSOM is undefined', function() { + var actual = check.evaluate.call(checkContext, document); + assert.isUndefined(actual); }); - it('returns undefined if context of check does not have CSSOM property', function() { - var actual = checks['css-orientation-lock'].evaluate.call( - checkContext, - document - ); + it('returns undefined when CSSOM is empty', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [] // pass context with cssom as empty + }); assert.isUndefined(actual); }); - it('returns undefined if CSSOM does not have any sheets', function() { - // pass context with cssom as empty - var actual = checks['css-orientation-lock'].evaluate.call( - checkContext, - document, - {}, - undefined, - { - cssom: [] - } - ); - assert.isUndefined(actual); + it('returns true when CSSOM has no rules', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: 'a', + sheet: {} // empty sheet, no css rules + } + ] + }); + assert.isTrue(actual); }); - it('returns true if CSSOM does not have sheet or rule(s) in the sheet(s)', function() { - // pass context with cssom but empty or no sheet - var actual = checks['css-orientation-lock'].evaluate.call( - checkContext, - document, - {}, - undefined, - { - cssom: [ - { - shadowId: 'a', - sheet: {} // empty sheet - }, - { - shadowId: 'a' - // NO SHEET -> this should never happen, but testing for iteration exit in check - } - ] - } - ); + it('returns true when CSSOM has no CSS media features', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: 'a', + sheet: getSheet('body { color: inherit; }') + } + ] + }); assert.isTrue(actual); }); - it('returns true if there are no MEDIA rule(s) in the CSSOM stylesheets', function() { - var sheet = getSheet(SHEET_DATA.BODY_STYLE); - var actual = checks['css-orientation-lock'].evaluate.call( - checkContext, - document, - {}, - undefined, - { - cssom: [ - { - shadowId: 'a', - sheet: sheet - } - ] - } - ); + it('returns true when CSSOM has no CSS media features targeting orientation', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: undefined, + sheet: getSheet('body { color: inherit; }') + }, + { + shadowId: 'a', + sheet: getSheet( + '@media (min-width: 400px) { background-color: red; }' + ) + } + ] + }); assert.isTrue(actual); }); - it('returns true if there are no ORIENTATION rule(s) within MEDIA rules in CSSOM stylesheets', function() { - var actual = checks['css-orientation-lock'].evaluate.call( - checkContext, - document, - {}, - undefined, - { - cssom: [ - { - shadowId: undefined, - sheet: getSheet(SHEET_DATA.BODY_STYLE) - }, - { - shadowId: 'a', - sheet: getSheet(SHEET_DATA.MEDIA_STYLE_NON_ORIENTATION) - } - ] - } - ); + it('returns true when CSSOM has empty Orientation CSS media features', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: undefined, + sheet: getSheet('body { color: inherit; }') + }, + { + shadowId: 'a', + sheet: getSheet( + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { }' + ) + } + ] + }); assert.isTrue(actual); }); - it('returns true if no styles within any of the ORIENTATION rule(s)', function() { - var actual = checks['css-orientation-lock'].evaluate.call( - checkContext, - document, - {}, - undefined, - { - cssom: [ - { - shadowId: undefined, - sheet: getSheet(SHEET_DATA.BODY_STYLE) - }, - { - shadowId: 'a', - sheet: getSheet(SHEET_DATA.MEDIA_STYLE_ORIENTATION_EMPTY) - } - ] - } - ); + it('returns true when CSSOM has Orientation CSS media features that does not target transform property', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: 'a', + sheet: getSheet( + '@media screen and (min-width: 1px) and (max-width: 2000px) and (orientation: portrait) { #mocha { color: red; } }' + ) + } + ] + }); assert.isTrue(actual); }); - it('returns true if there is no TRANSFORM style within any of the ORIENTATION rule(s)', function() { - var actual = checks['css-orientation-lock'].evaluate.call( - checkContext, - document, - {}, - undefined, - { - cssom: [ - { - shadowId: 'a', - sheet: getSheet( - SHEET_DATA.MEDIA_STYLE_ORIENTATION_WITHOUT_TRANSFORM - ) - } - ] - } - ); + it('returns true when CSSOM has Orientation CSS media features with transform property and transformation function of translateX, which does not affect rotation', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: undefined, + sheet: getSheet( + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { #mocha { transform: translateX(10px); -webkit-transform: translateX(10px); } }' + ) + } + ] + }); assert.isTrue(actual); }); - it('returns true if TRANSFORM style applied is not ROTATE', function() { - var actual = checks['css-orientation-lock'].evaluate.call( - checkContext, - document, - {}, - undefined, - { - cssom: [ - { - shadowId: undefined, - sheet: getSheet( - SHEET_DATA.MEDIA_STYLE_ORIENTATION_WITH_TRANSFORM_NOT_ROTATE - ) - } - ] - } - ); + it('returns true when CSSOM has Orientation CSS media features with transform property and tranformation function of rotate, which affects rotation but does not lock orientation (rotate(180deg))', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: 'a', + root: document, + sheet: getSheet( + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { body { transform: rotate(180deg); -webkit-transform: rotate(180deg); } }' + ) + } + ] + }); assert.isTrue(actual); }); - it('returns true if TRANSFORM style applied is ROTATE, but is divisible by 180', function() { - var actual = checks['css-orientation-lock'].evaluate.call( + it('returns true when CSSOM has Orientation CSS media features with transform property and tranformation function of rotate, which affects rotation but does not lock orientation (rotate(-178deg))', function() { + var actual = check.evaluate.call( checkContext, document, - {}, + { degreeThreshold: 3 }, undefined, { cssom: [ @@ -209,7 +146,7 @@ describe('css-orientation-lock tests', function() { shadowId: 'a', root: document, sheet: getSheet( - SHEET_DATA.MEDIA_STYLE_ORIENTATION_WITH_TRANSFORM_ROTATE_180 + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { body { transform: rotate(-178deg); -webkit-transform: rotate(-178deg); } }' ) } ] @@ -218,11 +155,89 @@ describe('css-orientation-lock tests', function() { assert.isTrue(actual); }); - it('returns false if TRANSFORM style applied is ROTATE, and is divisible by 90 and not divisible by 180', function() { - var actual = checks['css-orientation-lock'].evaluate.call( + it('returns true when CSSOM has Orientation CSS media features with transform property and tranformation function of rotateZ, which affects rotation but does not lock orientation (rotateZ(1turn))', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: 'a', + root: document, + sheet: getSheet( + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { body { transform: rotateZ(1turn); } }' + ) + } + ] + }); + assert.isTrue(actual); + }); + + it('returns true when CSSOM has Orientation CSS media features with transform property and tranformation function of rotate3d, which affects rotation but does not lock orientation (rotate3d(0,0,0,400grad))', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: 'a', + root: document, + sheet: getSheet( + // Note: values set on rotate3d cascasdes over rotate + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { body { transform: rotate(90deg) rotate3d(0,0,1, 400grad); } }' + ) + } + ] + }); + assert.isTrue(actual); + }); + + it('returns true when CSSOM has Orientation CSS media features with transform property and tranformation function of matrix3d, which affects rotation but does not lock orientation (matrix3d(-1,0,0.00,0,0.00,-1,0.00,0,0,0,1,0,0,0,0,1))', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: 'a', + root: document, + sheet: getSheet( + // Here the target is rotated by 180deg + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { body { transform: matrix3d(-1,0,0.00,0,0.00,-1,0.00,0,0,0,1,0,0,0,0,1); } }' + ) + } + ] + }); + assert.isTrue(actual); + }); + + it('returns false when CSSOM has Orientation CSS media features with transform property and transformation function of rotate, which affects rotation and locks orientation (rotate(270deg))', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: undefined, + root: document, + sheet: getSheet( + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { #mocha { transform: rotate(270deg); -webkit-transform: rotate(270deg); } }' + ) + } + ] + }); + assert.isFalse(actual); + }); + + it('returns false when CSSOM has Orientation CSS media features with transform property and transformation function of rotate3d, which affects rotation and locks orientation (rotate3d(0,0,1,90deg))', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: undefined, + root: document, + sheet: getSheet( + // apply 0 on the z-axis (3rd parameter) does not apply given rotation on z-axis + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { #mocha { transform: rotate3d(0,0,1,90deg); -webkit-transform: rotate3d(0,0,1,90deg) } }' + ) + } + ] + }); + assert.isFalse(actual); + }); + + it('returns false when CSSOM has Orientation CSS media features with transform property and transformation function of rotate3d, which affects rotation and locks orientation (rotate3d(0,0,1,93deg))', function() { + var actual = check.evaluate.call( checkContext, document, - {}, + { degreeThreshold: 3 }, undefined, { cssom: [ @@ -230,7 +245,8 @@ describe('css-orientation-lock tests', function() { shadowId: undefined, root: document, sheet: getSheet( - SHEET_DATA.MEDIA_STYLE_ORIENTATION_WITH_TRANSFORM_ROTATE_90 + // apply 0 on the z-axis (3rd parameter) does not apply given rotation on z-axis + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { #mocha { transform: rotate3d(0,0,1,93deg); -webkit-transform: rotate3d(0,0,1,93deg) } }' ) } ] @@ -239,6 +255,53 @@ describe('css-orientation-lock tests', function() { assert.isFalse(actual); }); + it('returns false when CSSOM has Orientation CSS media features with transform property and transformation function of rotate3d, which affects rotation and locks orientation (rotate3d(0,0,1,1.5708rad))', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: undefined, + root: document, + sheet: getSheet( + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { #mocha { transform: rotate3d(0,0,1,1.5708rad); -webkit-transform: rotate3d(0,0,1,1.5708rad) } }' + ) + } + ] + }); + assert.isFalse(actual); + }); + + it('returns false when CSSOM has Orientation CSS media features with transform property and transformation function of matrix, which affects rotation and locks orientation (matrix(0.00,1.00,-1.00,0.00,0,0))', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: undefined, + root: document, + sheet: getSheet( + // this rotates by 90deg + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { #mocha { transform:matrix(0.00,1.00,-1.00,0.00,0,0); -webkit-transform:matrix(0.00,1.00,-1.00,0.00,0,0); } }' + ) + } + ] + }); + assert.isFalse(actual); + }); + + it('returns false when CSSOM has Orientation CSS media features with transform property and transformation function of matrix3d, which affects rotation and locks orientation (matrix3d(0,-1,0.00,0,1.00,0,0.00,0,0,0,1,0,0,0,0,1);)', function() { + var actual = check.evaluate.call(checkContext, document, {}, undefined, { + cssom: [ + { + shadowId: undefined, + root: document, + sheet: getSheet( + // this rotates by 90deg + '@media screen and (min-width: 1px) and (max-width: 3000px) and (orientation: landscape) { #mocha { transform: matrix3d(0,-1,0.00,0,1.00,0,0.00,0,0,0,1,0,0,0,0,1); -webkit-transform: matrix3d(0,-1,0.00,0,1.00,0,0.00,0,0,0,1,0,0,0,0,1); } }' + ) + } + ] + }); + assert.isFalse(actual); + }); + // Note: // external stylesheets is tested in integration tests // shadow DOM is tested in integration tests diff --git a/test/integration/full/css-orientation-lock/violations.css b/test/integration/full/css-orientation-lock/violations.css index f4c5c45e9d..48556c6147 100644 --- a/test/integration/full/css-orientation-lock/violations.css +++ b/test/integration/full/css-orientation-lock/violations.css @@ -6,9 +6,9 @@ @media screen and (min-width: 10px) and (max-width: 3000px) and (orientation: landscape) { html { - transform: rotate(-90deg); + transform: rotateZ(0, 0, 1, 1.5708rad); } .someDiv { - transform: rotate(90deg); + transform: matrix3d(0,-1,0.00,0,1.00,0,0.00,0,0,0,1,0,0,0,0,1); } } diff --git a/test/integration/full/css-orientation-lock/violations.js b/test/integration/full/css-orientation-lock/violations.js index ce94d605fe..327e1c041b 100644 --- a/test/integration/full/css-orientation-lock/violations.js +++ b/test/integration/full/css-orientation-lock/violations.js @@ -82,7 +82,7 @@ describe('css-orientation-lock violations test', function() { var fixture = document.getElementById('shadow-fixture'); var shadow = fixture.attachShadow({ mode: 'open' }); shadow.innerHTML = - '' + + '' + '
green
' + '
red
';