From ab8957a3ba353c6db684a4b29eb816680fe3df35 Mon Sep 17 00:00:00 2001 From: sainthkh Date: Sat, 9 Nov 2019 05:49:56 +0900 Subject: [PATCH] Fixed backface visibility. (#5591) * Fixed backface visibility. * Show reason. * Reorganized logic. --- packages/driver/src/dom/visibility.js | 46 +++++++++++++++++++ .../integration/dom/visibility_spec.js | 45 +++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/packages/driver/src/dom/visibility.js b/packages/driver/src/dom/visibility.js index 6e1df389b89d..fcc7edbfa67b 100644 --- a/packages/driver/src/dom/visibility.js +++ b/packages/driver/src/dom/visibility.js @@ -61,6 +61,10 @@ const isHidden = (el, name = 'isHidden()') => { return true // is hidden } + if (elIsBackface($el)) { + return true + } + // we do some calculations taking into account the parents // to see if its hidden by a parent if (elIsHiddenByAncestors($el)) { @@ -112,6 +116,40 @@ const elHasVisibilityHidden = ($el) => { return $el.css('visibility') === 'hidden' } +const numberRegex = /-?[0-9]+(?:\.[0-9]+)?/g +// This is a simplified version of backface culling. +// https://en.wikipedia.org/wiki/Back-face_culling +// +// We defined view normal vector, (0, 0, -1), - eye to screen. +// and default normal vector, (0, 0, 1) +// When dot product of them are >= 0, item is visible. +const elIsBackface = ($el) => { + const el = $el[0] + const style = getComputedStyle(el) + const backface = style.getPropertyValue('backface-visibility') + const backfaceInvisible = backface === 'hidden' + const transform = style.getPropertyValue('transform') + + if (!backfaceInvisible || !transform.startsWith('matrix3d')) { + return false + } + + const m3d = transform.substring(8).match(numberRegex) + const defaultNormal = [0, 0, -1] + const elNormal = findNormal(m3d) + // Simplified dot product. + // [0] and [1] are always 0 + const dot = defaultNormal[2] * elNormal[2] + + return dot >= 0 +} + +const findNormal = (m) => { + const length = Math.sqrt(+m[8] * +m[8] + +m[9] * +m[9] + +m[10] * +m[10]) + + return [+m[8] / length, +m[9] / length, +m[10] / length] +} + const elHasVisibilityCollapse = ($el) => { return $el.css('visibility') === 'collapse' } @@ -272,6 +310,10 @@ const elIsHiddenByAncestors = function ($el, $origEl = $el) { return !elDescendentsHavePositionFixedOrAbsolute($parent, $origEl) } + if (elIsBackface($parent)) { + return true + } + // continue to recursively walk up the chain until we reach body or html return elIsHiddenByAncestors($parent, $origEl) } @@ -388,6 +430,10 @@ const getReasonIsHidden = function ($el) { return `This element '${node}' is not visible because it has an effective width and height of: '${width} x ${height}' pixels.` } + if (elIsBackface($el)) { + return `This element '${node}' is not visible because it is rotated and its backface is hidden.` + } + if ($parent = parentHasNoOffsetWidthOrHeightAndOverflowHidden($el.parent())) { parentNode = $elements.stringify($parent, 'short') width = elOffsetWidth($parent) diff --git a/packages/driver/test/cypress/integration/dom/visibility_spec.js b/packages/driver/test/cypress/integration/dom/visibility_spec.js index ec957c072180..9e289a7ca069 100644 --- a/packages/driver/test/cypress/integration/dom/visibility_spec.js +++ b/packages/driver/test/cypress/integration/dom/visibility_spec.js @@ -820,12 +820,53 @@ describe('src/cypress/dom/visibility', () => { }) describe('css backface-visibility', () => { + describe('element visibility by backface-visibility and rotation', () => { + const add = (el) => { + return $(el).appendTo(cy.$$('body')) + } + + it('is visible when there is no transform', () => { + const el = add('
No transform
') + + expect(el).to.be.visible + }) + + it('is visible when an element is rotated < 90 degrees', () => { + const el = add('
rotateX(45deg)
') + + expect(el).to.be.visible + + const el2 = add('
rotateY(-45deg)
') + + expect(el2).to.be.visible + }) + + it('is invisible when an element is rotated > 90 degrees', () => { + const el = add('
rotateX(135deg)
') + + expect(el).to.be.hidden + + const el2 = add('
rotateY(-135deg)
') + + expect(el2).to.be.hidden + }) + + it('is invisible when an element is rotated in 90 degrees', () => { + const el = add('
rotateX(90deg)
') + + expect(el).to.be.hidden + + const el2 = add('
rotateY(-90deg)
') + + expect(el2).to.be.hidden + }) + }) + it('is visible when backface not visible', function () { expect(this.$parentsWithBackfaceVisibilityHidden.find('#front')).to.be.visible }) - // TODO: why is this skipped? - it.skip('is hidden when backface visible', function () { + it('is hidden when backface visible', function () { expect(this.$parentsWithBackfaceVisibilityHidden.find('#back')).to.be.hidden }) })