From 390bf83882ea991c9cf32e1ec0b85045579e2c9d Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Tue, 16 Jul 2019 16:10:54 -0600 Subject: [PATCH 1/4] feat(is-visible): add support for clip-path techniques --- lib/commons/dom/is-visible.js | 32 ++++++++++++++++++++++------- test/commons/dom/is-visible.js | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/lib/commons/dom/is-visible.js b/lib/commons/dom/is-visible.js index ee15196e41..80627cf42d 100644 --- a/lib/commons/dom/is-visible.js +++ b/lib/commons/dom/is-visible.js @@ -1,7 +1,9 @@ /* global dom */ +const clipRegex = /rect\s*\(([0-9]+)px,?\s*([0-9]+)px,?\s*([0-9]+)px,?\s*([0-9]+)px\s*\)/; +const clipPathRegex = /(\w+)\((\d+)/; /** - * Determines if an element is hidden with the clip rect technique + * Determines if an element is hidden with a clip or clip-path technique * @method isClipped * @memberof axe.commons.dom * @private @@ -11,11 +13,25 @@ function isClipped(clip) { 'use strict'; - var matches = clip.match( - /rect\s*\(([0-9]+)px,?\s*([0-9]+)px,?\s*([0-9]+)px,?\s*([0-9]+)px\s*\)/ - ); - if (matches && matches.length === 5) { - return matches[3] - matches[1] <= 0 && matches[2] - matches[4] <= 0; + const matchesClip = clip.match(clipRegex); + const matchesClipPath = clip.match(clipPathRegex); + if (matchesClip && matchesClip.length === 5) { + return ( + matchesClip[3] - matchesClip[1] <= 0 && + matchesClip[2] - matchesClip[4] <= 0 + ); + } + if (matchesClipPath) { + const type = matchesClipPath[1]; + const value = parseInt(matchesClipPath[2], 10); + + switch (type) { + case 'inset': + return value >= 50; + case 'circle': + return value === 0; + default: + } } return false; @@ -60,7 +76,9 @@ dom.isVisible = function(el, screenReader, recursed) { if ( style.getPropertyValue('display') === 'none' || ['STYLE', 'SCRIPT', 'NOSCRIPT', 'TEMPLATE'].includes(nodeName) || - (!screenReader && isClipped(style.getPropertyValue('clip'))) || + (!screenReader && + (isClipped(style.getPropertyValue('clip')) || + isClipped(style.getPropertyValue('clip-path')))) || (!recursed && // visibility is only accurate on the first element (style.getPropertyValue('visibility') === 'hidden' || diff --git a/test/commons/dom/is-visible.js b/test/commons/dom/is-visible.js index 8f29b1ae55..a060068d4f 100644 --- a/test/commons/dom/is-visible.js +++ b/test/commons/dom/is-visible.js @@ -188,6 +188,25 @@ describe('dom.isVisible', function() { el = document.getElementById('target'); assert.isFalse(axe.commons.dom.isVisible(el)); }); + + it('should detect clip-path hidden text technique', function() { + fixture.innerHTML = + '
Hi
'; + + var el = document.getElementById('target'); + assert.isFalse(axe.commons.dom.isVisible(el)); + }); + + it('should detect clip-path hidden text technique on parent', function() { + fixture.innerHTML = + '
' + + '
Hi
' + + '
'; + + var el = document.getElementById('target'); + assert.isFalse(axe.commons.dom.isVisible(el)); + }); + (shadowSupported ? it : xit)( 'should correctly handle visible slotted elements', function() { @@ -411,5 +430,23 @@ describe('dom.isVisible', function() { el = document.getElementById('target'); assert.isTrue(axe.commons.dom.isVisible(el, true)); }); + + it('should detect clip-path hidden text technique', function() { + fixture.innerHTML = + '
Hi
'; + + var el = document.getElementById('target'); + assert.isTrue(axe.commons.dom.isVisible(el, true)); + }); + + it('should detect clip-path hidden text technique on parent', function() { + fixture.innerHTML = + '
' + + '
Hi
' + + '
'; + + var el = document.getElementById('target'); + assert.isTrue(axe.commons.dom.isVisible(el, true)); + }); }); }); From faebc0ab78868161f601d49a3c0564f6997c4b7a Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Tue, 16 Jul 2019 16:17:40 -0600 Subject: [PATCH 2/4] ignore phantom --- test/commons/dom/is-visible.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/commons/dom/is-visible.js b/test/commons/dom/is-visible.js index a060068d4f..007e0bc0e9 100644 --- a/test/commons/dom/is-visible.js +++ b/test/commons/dom/is-visible.js @@ -194,7 +194,11 @@ describe('dom.isVisible', function() { '
Hi
'; var el = document.getElementById('target'); - assert.isFalse(axe.commons.dom.isVisible(el)); + if (window.PHANTOMJS) { + assert.ok('PhantomJS is a liar'); + } else { + assert.isFalse(axe.commons.dom.isVisible(el)); + } }); it('should detect clip-path hidden text technique on parent', function() { @@ -204,7 +208,12 @@ describe('dom.isVisible', function() { ''; var el = document.getElementById('target'); - assert.isFalse(axe.commons.dom.isVisible(el)); + + if (window.PHANTOMJS) { + assert.ok('PhantomJS is a liar'); + } else { + assert.isFalse(axe.commons.dom.isVisible(el)); + } }); (shadowSupported ? it : xit)( From 9235d3425ed2c6f5e777d2a4dd2b84add6c7b5a5 Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Wed, 17 Jul 2019 09:30:32 -0600 Subject: [PATCH 3/4] skip test for IE11 --- test/commons/dom/is-visible.js | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/test/commons/dom/is-visible.js b/test/commons/dom/is-visible.js index 007e0bc0e9..346743872c 100644 --- a/test/commons/dom/is-visible.js +++ b/test/commons/dom/is-visible.js @@ -3,6 +3,7 @@ describe('dom.isVisible', function() { var fixture = document.getElementById('fixture'); var fixtureSetup = axe.testUtils.fixtureSetup; + var isIE11 = axe.testUtils.isIE11; var shadowSupported = axe.testUtils.shadowSupport.v1; var fakeNode = { nodeType: Node.ELEMENT_NODE, @@ -189,32 +190,31 @@ describe('dom.isVisible', function() { assert.isFalse(axe.commons.dom.isVisible(el)); }); - it('should detect clip-path hidden text technique', function() { - fixture.innerHTML = - '
Hi
'; + // IE11 either only supports clip paths defined by url() or not at all, + // MDN and caniuse.com give different results... + (isIE11 || window.PHANTOMJS ? it.skip : it)( + 'should detect clip-path hidden text technique', + function() { + fixture.innerHTML = + '
Hi
'; - var el = document.getElementById('target'); - if (window.PHANTOMJS) { - assert.ok('PhantomJS is a liar'); - } else { + var el = document.getElementById('target'); assert.isFalse(axe.commons.dom.isVisible(el)); } - }); - - it('should detect clip-path hidden text technique on parent', function() { - fixture.innerHTML = - '
' + - '
Hi
' + - '
'; + ); - var el = document.getElementById('target'); + (isIE11 || window.PHANTOMJS ? it.skip : it)( + 'should detect clip-path hidden text technique on parent', + function() { + fixture.innerHTML = + '
' + + '
Hi
' + + '
'; - if (window.PHANTOMJS) { - assert.ok('PhantomJS is a liar'); - } else { + var el = document.getElementById('target'); assert.isFalse(axe.commons.dom.isVisible(el)); } - }); + ); (shadowSupported ? it : xit)( 'should correctly handle visible slotted elements', From 8c5765af30ff4f8c2dc2e9b46339f5091e021361 Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Wed, 24 Jul 2019 14:20:28 -0600 Subject: [PATCH 4/4] pass style to isCliped function --- lib/commons/dom/is-visible.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/commons/dom/is-visible.js b/lib/commons/dom/is-visible.js index 80627cf42d..bd23b50eac 100644 --- a/lib/commons/dom/is-visible.js +++ b/lib/commons/dom/is-visible.js @@ -7,14 +7,16 @@ const clipPathRegex = /(\w+)\((\d+)/; * @method isClipped * @memberof axe.commons.dom * @private - * @param {String} clip Computed property value of clip + * @param {CSSStyleDeclaration} style Computed style * @return {Boolean} */ -function isClipped(clip) { +function isClipped(style) { 'use strict'; - const matchesClip = clip.match(clipRegex); - const matchesClipPath = clip.match(clipPathRegex); + const matchesClip = style.getPropertyValue('clip').match(clipRegex); + const matchesClipPath = style + .getPropertyValue('clip-path') + .match(clipPathRegex); if (matchesClip && matchesClip.length === 5) { return ( matchesClip[3] - matchesClip[1] <= 0 && @@ -76,9 +78,7 @@ dom.isVisible = function(el, screenReader, recursed) { if ( style.getPropertyValue('display') === 'none' || ['STYLE', 'SCRIPT', 'NOSCRIPT', 'TEMPLATE'].includes(nodeName) || - (!screenReader && - (isClipped(style.getPropertyValue('clip')) || - isClipped(style.getPropertyValue('clip-path')))) || + (!screenReader && isClipped(style)) || (!recursed && // visibility is only accurate on the first element (style.getPropertyValue('visibility') === 'hidden' ||