From 11a4835f8e2cd8005bb80847c61cf9e15429c16d Mon Sep 17 00:00:00 2001 From: KHeo Date: Wed, 11 Dec 2019 16:18:19 +0900 Subject: [PATCH] Fixed. --- packages/driver/src/dom/visibility.js | 159 ++++++++----- .../test/cypress/fixtures/issue-5682.html | 218 ++++++++---------- .../issues/{5682.spec.js => 5682_spec.js} | 43 ++-- 3 files changed, 217 insertions(+), 203 deletions(-) rename packages/driver/test/cypress/integration/issues/{5682.spec.js => 5682_spec.js} (63%) diff --git a/packages/driver/src/dom/visibility.js b/packages/driver/src/dom/visibility.js index f2fb94d81bdd..827f69a2f6d3 100644 --- a/packages/driver/src/dom/visibility.js +++ b/packages/driver/src/dom/visibility.js @@ -123,15 +123,7 @@ const elIsHiddenByTransform = ($el) => { const list = extractTransformInfoFromElements($el) if (existsInvisibleBackface(list)) { - let transformList - - if (existsPreserve3d(list)) { - transformList = mergeTransformInfo(list) - } else { - transformList = filterTransformInfo(list) - } - - return elIsBackface(transformList) ? 'backface' : 'visible' + return elIsBackface(list) ? 'backface' : 'visible' } return elIsTransformedToZero(list) ? 'transformed' : 'visible' @@ -156,7 +148,7 @@ const extractTransformInfo = ($el) => { return { el, backfaceVisibility: style.getPropertyValue('backface-visibility'), - transformStyle: style.getPropertyValue('transformStyle'), + transformStyle: style.getPropertyValue('transform-style'), transform: style.getPropertyValue('transform'), } } @@ -165,77 +157,116 @@ const existsInvisibleBackface = (list) => { return !!_.find(list, { backfaceVisibility: 'hidden' }) } -const existsPreserve3d = (list) => { - return !!_.find(list, { transformStyle: 'preserve-3d' }) -} +const numberRegex = /-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/g +const defaultNormal = [0, 0, 1] +// This function uses a simplified version of backface culling. +// https://en.wikipedia.org/wiki/Back-face_culling +// +// We defined view vector, (0, 0, -1), - eye to screen. +// and default normal vector of an element, (0, 0, 1) +// When dot product of them are >= 0, item is visible. +const elIsBackface = (list) => { + const nextPreserve3d = (i) => { + return i + 1 < list.length && + list[i + 1].transformStyle === 'preserve-3d' + } -const mergeTransformInfo = (list) => { - let transformList = [] + let i = 0 - for (let i = 0; i < list.length; i++) { - if (list[i].backfaceVisibility === 'hidden') { - if (list[i].transformStyle === 'preserve-3d') { - const transform = [] + const finalNormal = (startIndex) => { + i = startIndex + let normal = findNormal(list[i].transform) - while (list[i].transformStyle === 'preserve-3d' && - (i + 1 < list.length && list[i + 1].transformStyle === 'flat')) { - transform.push(list[i].transform) - i++ - } + while (nextPreserve3d(i)) { + i++ + normal = findNormal(list[i].transform, normal) + } - if (transform.length > 0) { - transformList.push(transform) - } - } + return normal + } - if (list[i].transform.startsWith('matrix3d')) { - transformList.push([list[i].transform]) - } + const skipToNextFlat = () => { + while (nextPreserve3d(i)) { + i++ } + + i++ } - return transformList -} + // + if (1 < list.length & list[1].transformStyle === 'preserve-3d') { + if (list[0].backfaceVisibility === 'hidden') { + let normal = finalNormal(0) -const filterTransformInfo = (list) => { - return list - .filter(({ backfaceVisibility, transform }) => { - return backfaceVisibility === 'hidden' && transform.startsWith('matrix3d') - }) - .map(({ transform }) => [transform]) -} + if (checkBackface(normal)) { + return true + } -const numberRegex = /-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/g -const elIsBackface = (transformList) => { - for (let i = 0; i < transformList.length; i++) { - if (isBackface(transformList[i])) { - return true + i++ + } else { + if (list[1].backfaceVisibility === 'visible') { + const { width, height } = list[0].el.getBoundingClientRect() + + if (width === 0 || height === 0) { + return true + } + + skipToNextFlat() + } else { + if (list[0].transform !== 'none') { + skipToNextFlat() + } else { + i++ + + let normal = finalNormal(i) + + if (checkBackface(normal)) { + return true + } + + i++ + } + } + } + } else { + for (; i < list.length; i++) { + if (i > 0 && list[i].transformStyle === 'preserve-3d') { + continue + } + + if (list[i].backfaceVisibility === 'hidden' && list[i].transform.startsWith('matrix3d')) { + let normal = findNormal(list[i].transform) + + if (checkBackface(normal)) { + return true + } + } } } return false } -// This is a simplified version of backface culling. -// https://en.wikipedia.org/wiki/Back-face_culling -// -// We defined view vector, (0, 0, -1), - eye to screen. -// and default normal vector of an element, (0, 0, 1) -// When dot product of them are >= 0, item is visible. -const isBackface = (transform) => { +const checkBackface = (normal) => { const viewVector = [0, 0, -1] - const elNormal = findNormal(transform.reverse()) + // Simplified dot product. - // [0] and [1] are always 0 - const dot = viewVector[2] * elNormal[2] + // viewVector[0] and viewVector[1] are always 0. So, they're ignored. + let dot = viewVector[2] * normal[2] + + // Because of the floating point number rounding error, + // cos(90deg) isn't 0. It's 6.12323e-17. + // And it sometimes causes errors when dot product value is something like -6.12323e-17. + // So, we're setting the dot product result to 0 when its absolute value is less than 1e-10(10^-10). + if (Math.abs(dot) < 1e-10) { + dot = 0 + } return dot >= 0 } -const findNormal = (transform, index = 0, normal = [0, 0, 1]) => { - const matrix = transform[index] - - if (!matrix) { +const findNormal = (matrix, normal = defaultNormal) => { + if (matrix === 'none') { return normal } @@ -254,9 +285,7 @@ const findNormal = (transform, index = 0, normal = [0, 0, 1]) => { m[2] * v[0] + m[6] * v[1] + m[10] * v[2], ] - const computedUnitNormal = toUnitVector(computedNormal) - - return findNormal(transform, index + 1, computedUnitNormal) + return toUnitVector(computedNormal) } const toMatrix3d = (m2d) => { @@ -590,11 +619,13 @@ 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 (elIsHiddenByTransform($el) === 'transformed') { + const transformResult = elIsHiddenByTransform($el) + + if (transformResult === 'transformed') { return `This element '${node}' is not visible because it is hidden by transform.` } - if (elIsBackface($el)) { + if (transformResult === 'backface') { return `This element '${node}' is not visible because it is rotated and its backface is hidden.` } diff --git a/packages/driver/test/cypress/fixtures/issue-5682.html b/packages/driver/test/cypress/fixtures/issue-5682.html index c436f2eb30ed..99ea3bf0ecee 100644 --- a/packages/driver/test/cypress/fixtures/issue-5682.html +++ b/packages/driver/test/cypress/fixtures/issue-5682.html @@ -31,6 +31,33 @@ width: 200px; height: 60px; } + + .container { + position: relative; + transform-style: preserve-3d; /* This breaks visible check */ + width: 200px; + height: 300px; + } + + .flipped { + transform: rotateY(180deg); + } + + .face { + backface-visibility: hidden; + position: absolute; + width: 100%; + height: 100%; + } + + .front { + background: blue; + } + + .back { + transform: rotateY(180deg); + background: red; + } @@ -85,161 +112,112 @@

Parent rotate 90deg + Child rotate 190deg with backface-invisible

-

CASE 2: No transform after preserve-3d

+

CASE 2: Direct parent preserve-3d elements

-

Parent Flipped + no transform

-
+

Parent Flipped visible + target hidden

+
Parent rotateX(180deg) -
No transform
+
No transform
-

Grandparent Flipped + no transform parent + no transform child

-
- Grandparent rotateX(180deg) -
- No transform -
No transform
+

Parents rotated 60deg each visible + target hidden

+
+ Grandparent rotateX(60deg) +
+ Parent rotateX(60deg) +
No transform
-

Parent Flipped + Identity Transform

-
+

Parent Flipped hidden + target hidden

+
Parent rotateX(180deg) -
Target
+
No transform
-

Grandparent Flipped + Parent no transform + Identity Transform

-
- Grandparent rotateX(180deg) -
- Parent no transform -
Target
-
+

Parent 60deg visible + target hidden 60deg

+
+ Parent rotateX(60deg) +
rotateX(60deg)
-

Grandparent Flipped + Parent identity transform + No Transform

-
- Grandparent rotateX(180deg) -
- Parent no transform -
Target
-
-
- -

Grandparent Flipped + Parent no transform with hidden + Child Indentity

-
- Grandparent rotateX(180deg) -
- Parent no transform -
Target
-
+

Parent Flipped visible + target visible

+
+ Parent rotateX(180deg) +
No transform
-

Grandgrandparent Flipped + Grandparent no transform + Parent no transform with hidden + Child Indentity

-
- Grandparent rotateX(180deg) -
- Grandparent -
- Parent no transform -
Target
-
+

Parents Flipped visible + target visible

+
+ Parent rotateX(60deg) +
+ Parent rotateX(60deg) +
No transform
-

Grandgrandparent Flipped + Grandparent Identity + Parent no transform with hidden + Child Indentity

-
- Grandparent rotateX(180deg) -
- Grandparent -
- Parent no transform -
Target
-
+

Parents Flipped 45deg visible + target visible

+
+ Grandparent rotateX(45deg) +
+ Parent rotateX(45deg) +
No transform
-

Parent rotate 180deg + Child backface-invisible

-
+

Parent Flipped hidden + identity transform

+
Parent rotateX(180deg) -
Target
+
Identity transform
-

Parent rotate 45deg + Child rotate 45deg with backface-invisible

-
- Parent rotateX(45deg) -
Target rotateX(45deg)
+

Parent Flipped hidden + no transform

+
+ Parent rotateX(180deg) +
No transform
-

GrandParent 30deg + Parent rotate 30deg + Child 30deg with backface-invisible

-
- Grandparent rotateX(30deg) -
- Parent rotateX(30deg) -
Target rotateX(30deg)
+

Grandparent rotated 45deg + parent rotated 45deg hidden + target visible

+
+ Grandparent rotateX(45deg) +
+ Parent rotateX(45deg) +
No transform
+
-

Parent rotate 90deg + Child rotate 190deg with backface-invisible

-
- Parent rotateX(90deg) -
Target rotateX(190deg)
-
- -

Grandparent rotateX 180deg with preserve-3d + Parent rotate 180deg + Child backface-invisible

-
- Grandparent rotateX(180deg) -
- Parent rotateX(180deg) -
Target
+
+

CASE 3: Others

+ +

flat after preserve-3d

+
+ Grandgrandparent rotateX(180deg) +
+ Grandparent rotateX(30deg) +
+ Parent rotateX(30deg) +
No transform
+
-
-

CASE 3: flat + preserver-3d

- -

Parent rotate 180deg + Child backface-invisible with flat

-
- Parent rotateX(180deg) -
Target
-
- -

Parent BI rotateX 180deg + Child Identidy with flat

-
- Parent rotateX(180deg) -
Target
-
- -

Grandparent rotateX 60deg with flat + Parent rotate 60deg + Child backface-invisible

-
- Grandparent rotateX(60deg) -
- Parent rotateX(60deg) -
Target
+
+
+ Front
-
- -

Grandparent rotateX 180deg with flat + Parent rotate 180deg + Child backface-invisible

-
- Grandparent rotateX(180deg) -
- Parent rotateX(180deg) -
Target
-
-
- -

Grandgrandparent rotateX 60deg with preserve3d + Grandparent rotateX 60deg with flat + Parent rotate 60deg with preserve3d + Child backface-invisible

-
- Grandgrandparent rotateX(60deg) -
- Grandparent rotateX(60deg) -
- Parent rotateX(60deg) -
Target
-
+
+ Back
+ +
diff --git a/packages/driver/test/cypress/integration/issues/5682.spec.js b/packages/driver/test/cypress/integration/issues/5682_spec.js similarity index 63% rename from packages/driver/test/cypress/integration/issues/5682.spec.js rename to packages/driver/test/cypress/integration/issues/5682_spec.js index f4d988b93069..b0fe6f9cc399 100644 --- a/packages/driver/test/cypress/integration/issues/5682.spec.js +++ b/packages/driver/test/cypress/integration/issues/5682_spec.js @@ -48,33 +48,38 @@ describe('issue #5682 - backface visibility', () => { }) describe('CASE 2: No transform after preserve-3d', () => { - it('is invisible when target is not transformed but parent is rotated > 90deg', () => { - cy.get('#a2-1-1').should('not.be.visible') - cy.get('#a2-1-2').should('not.be.visible') + it('target hidden + parents', () => { + cy.get('#a2-1').should('not.be.visible') + cy.get('#a2-2').should('not.be.visible') + cy.get('#a2-3').should('not.be.visible') + cy.get('#a2-4').should('not.be.visible') }) - it('is always visible when target is transformed in identity and visible', () => { - cy.get('#a2-2-1').should('be.visible') - cy.get('#a2-2-2').should('be.visible') - cy.get('#a2-2-3').should('be.visible') - cy.get('#a2-2-4').should('be.visible') + it('target visible + parent visible', () => { + cy.get('#a2-5').should('be.visible') + cy.get('#a2-6').should('be.visible') + cy.get('#a2-7').should('not.be.visible') }) - it('is invisible when an element is backface-invisible whose parent is rotated > 90deg', () => { - cy.get('#a2-5').should('not.be.visible') - }) - - it('is invisible when an element is rotated 45deg and its parent is 45deg', () => { - cy.get('#a2-6').should('not.be.visible') + it('target visible + parent hidden', () => { + cy.get('#a2-8').should('be.visible') + cy.get('#a2-9').should('not.be.visible') + cy.get('#a2-10').should('not.be.visible') }) + }) - it('is visible when target 30deg + parent 30deg + grandparent 30deg', () => { - cy.get('#a2-7').should('be.visible') + describe('CASE 3: Others', () => { + it('ignores and returns visible when flat appears after preserve-3d', () => { + cy.get('#a3-1').should('be.visible') }) + }) - it('is visible when an element is rotated 190deg whose parent is roated 90deg', () => { - cy.get('#a2-8').should('be.visible') - }) + it('issue case', () => { + cy.get('.front').should('be.visible') + cy.get('.back').should('not.be.visible') + cy.get('.container').click() + cy.get('.front').should('not.be.visible') + cy.get('.back').should('be.visible') }) }) })