Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed backface visibility. #5591

Merged
merged 5 commits into from
Nov 8, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions packages/driver/src/dom/visibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -112,6 +116,42 @@ const elHasVisibilityHidden = ($el) => {
return $el.css('visibility') === 'hidden'
}

// 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'

if (backfaceInvisible) {
sainthkh marked this conversation as resolved.
Show resolved Hide resolved
const transform = style.getPropertyValue('transform')

if (transform.startsWith('matrix3d')) {
const m3d = transform.substring(8).match(/-?[0-9]+(?:\.[0-9]+)?/g)
sainthkh marked this conversation as resolved.
Show resolved Hide resolved
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
}
}

return false
}

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'
}
Expand Down Expand Up @@ -272,6 +312,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)
}
Expand Down Expand Up @@ -388,6 +432,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)
Expand Down
45 changes: 43 additions & 2 deletions packages/driver/test/cypress/integration/dom/visibility_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('<div>No transform</div>')

expect(el).to.be.visible
})

it('is visible when an element is rotated < 90 degrees', () => {
const el = add('<div style="backface-visibility: hidden; transform: rotateX(45deg)">rotateX(45deg)</div>')

expect(el).to.be.visible

const el2 = add('<div style="backface-visibility: hidden; transform: rotateY(-45deg)">rotateY(-45deg)</div>')

expect(el2).to.be.visible
})

it('is invisible when an element is rotated > 90 degrees', () => {
const el = add('<div style="backface-visibility: hidden; transform: rotateX(135deg)">rotateX(135deg)</div>')

expect(el).to.be.hidden

const el2 = add('<div style="backface-visibility: hidden; transform: rotateY(-135deg)">rotateY(-135deg)</div>')

expect(el2).to.be.hidden
})

it('is invisible when an element is rotated in 90 degrees', () => {
const el = add('<div style="backface-visibility: hidden; transform: rotateX(90deg)">rotateX(90deg)</div>')

expect(el).to.be.hidden

const el2 = add('<div style="backface-visibility: hidden; transform: rotateY(-90deg)">rotateY(-90deg)</div>')

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
})
})
Expand Down