diff --git a/packages/driver/src/dom/transform.ts b/packages/driver/src/dom/transform.ts index 88b3ffbd36cd..f4076497db6b 100644 --- a/packages/driver/src/dom/transform.ts +++ b/packages/driver/src/dom/transform.ts @@ -69,6 +69,9 @@ const identityMatrix3D: Matrix3D = [ 0, 0, 0, 1, ] +// It became 1e-5 from 1e-10. Because 30deg + 30deg + 30deg is 6.0568e-7 and it caused a false negative. +const TINY_NUMBER = 1e-5 + const nextPreserve3d = (i: number, list: TransformInfo[]) => { return i + 1 < list.length && list[i + 1].transformStyle === 'preserve-3d' @@ -86,14 +89,10 @@ const finalNormal = (startIndex: number, list: TransformInfo[]) => { return normal } -// 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: TransformInfo[]) => { - if (1 < list.length && list[1].transformStyle === 'preserve-3d') { + // When the direct parent of the target has style, preserve-3d + if (list.length > 1 && list[1].transformStyle === 'preserve-3d') { + // When the target is backface-invisible a2-1-1 ~ a2-1-4 if (list[0].backfaceVisibility === 'hidden') { let normal = finalNormal(0, list) @@ -101,29 +100,27 @@ const elIsBackface = (list: TransformInfo[]) => { return true } } else { - if (list[1].backfaceVisibility === 'visible') { - const { width, height } = list[0].el.getBoundingClientRect() - - if (width === 0 || height === 0) { - return true + // When the direct parent of the target is backface-invisible + if (list[1].backfaceVisibility === 'hidden') { + // If it is not none, it is visible. Check a2-3-1 + if (list[0].transform === 'none') { + let normal = finalNormal(1, list) + + if (checkBackface(normal)) { + return true + } } - } else { - if (list[0].transform !== 'none') { - // TODO: check 90 deg. - return false - } - - let normal = finalNormal(1, list) + } - if (checkBackface(normal)) { - return true - } + // Check 90deg a2-2-3, a2-2-4. + let normal = finalNormal(0, list) - return false - } + return isElementOrthogonalWithView(normal) } } else { for (let i = 0; i < list.length; i++) { + // Ignore preserve-3d when it is not a direct parent. + // Why? -> https://github.com/cypress-io/cypress/pull/5916 if (i > 0 && list[i].transformStyle === 'preserve-3d') { continue } @@ -141,6 +138,12 @@ const elIsBackface = (list: TransformInfo[]) => { return false } +// 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 checkBackface = (normal: Vector3) => { // Simplified dot product. // viewVector[0] and viewVector[1] are always 0. So, they're ignored. @@ -149,8 +152,8 @@ const checkBackface = (normal: Vector3) => { // 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) { + // So, we're setting the dot product result to 0 when its absolute value is less than SMALL_NUMBER(10^-10). + if (Math.abs(dot) < TINY_NUMBER) { dot = 0 } @@ -276,5 +279,5 @@ const isElementOrthogonalWithView = (normal: Vector3) => { // [0] and [1] are always 0 const dot = viewVector[2] * normal[2] - return Math.abs(dot) <= 1e-10 + return Math.abs(dot) < TINY_NUMBER } diff --git a/packages/driver/test/cypress/fixtures/issue-5682.html b/packages/driver/test/cypress/fixtures/issue-5682.html index c73cb4cc57e3..2ff8b8589c48 100644 --- a/packages/driver/test/cypress/fixtures/issue-5682.html +++ b/packages/driver/test/cypress/fixtures/issue-5682.html @@ -114,91 +114,84 @@

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

CASE 2: Direct parent preserve-3d elements

-

a2-1: Parent Flipped visible + target hidden

+

a2-1-1: Parent Flipped visible + target hidden

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

a2-2: Parents rotated 60deg each visible + target hidden

+

a2-1-2: Parents rotated 60deg each visible + target hidden

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

a2-3: Parent Flipped hidden + target hidden

+

a2-1-3: Parent Flipped hidden + target hidden

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

a2-4: Parent 60deg visible + target hidden 60deg

+

a2-1-4: Parent 60deg visible + target hidden 60deg

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

a2-5: Parent Flipped visible + target visible

+

a2-2-1: Parent Flipped visible + target visible

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

a2-6: Parents Flipped visible + target visible

+

a2-2-2: Parents Flipped visible + target visible

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

a2-7: Parents Flipped 45deg visible + target visible

-
+

a2-2-3: Parents rotated 45deg visible + target visible

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

a2-8: Parent Flipped hidden + identity transform

+

a2-2-4: Parents rotated 30deg visible + target visible 30deg

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

a2-3-1: Parent Flipped hidden + identity transform

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

a2-9: Parent Flipped hidden + no transform

+

a2-3-2: Parent Flipped hidden + no transform

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

a2-10: Grandparent rotated 45deg + parent rotated 45deg hidden + target visible

+

a2-3-3: Grandparent rotated 45deg + parent rotated 45deg hidden + target visible

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

CASE 3: Others

- -

flat after preserve-3d

-
- Grandgrandparent rotateX(180deg) -
- Grandparent rotateX(30deg) -
- Parent rotateX(30deg) -
No transform
-
+
No transform
diff --git a/packages/driver/test/cypress/integration/issues/5682_spec.js b/packages/driver/test/cypress/integration/issues/5682_spec.js index b0fe6f9cc399..ba70a83ed1cf 100644 --- a/packages/driver/test/cypress/integration/issues/5682_spec.js +++ b/packages/driver/test/cypress/integration/issues/5682_spec.js @@ -47,30 +47,25 @@ describe('issue #5682 - backface visibility', () => { }) }) - describe('CASE 2: No transform after preserve-3d', () => { + describe('CASE 2: when direct parents have preserve-3d', () => { 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') + cy.get('#a2-1-1').should('not.be.visible') + cy.get('#a2-1-2').should('not.be.visible') + cy.get('#a2-1-3').should('not.be.visible') + cy.get('#a2-1-4').should('not.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') + cy.get('#a2-2-1').should('be.visible') + cy.get('#a2-2-2').should('be.visible') + cy.get('#a2-2-3').should('not.be.visible') + cy.get('#a2-2-4').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') - }) - }) - - describe('CASE 3: Others', () => { - it('ignores and returns visible when flat appears after preserve-3d', () => { - cy.get('#a3-1').should('be.visible') + cy.get('#a2-3-1').should('be.visible') + cy.get('#a2-3-2').should('not.be.visible') + cy.get('#a2-3-3').should('not.be.visible') }) })