diff --git a/lighthouse-core/driver/gatherers/interstitial.js b/lighthouse-core/driver/gatherers/interstitial.js index 03ef833dc4d7..d928db8e42f9 100644 --- a/lighthouse-core/driver/gatherers/interstitial.js +++ b/lighthouse-core/driver/gatherers/interstitial.js @@ -24,6 +24,43 @@ const Gather = require('./gather'); function getInterstitial() { const viewportSize = window.outerWidth * window.outerHeight; + const hasNonTransparentBackground = computedStyle => { + // Simple early exit for a solid background color. + const bgColor = computedStyle.backgroundColor; + const hasSolidBackground = (/#\d{6}/.test(bgColor) || + /rgb\(/.test(bgColor) || + /hsl\(/.test(bgColor) || + /^\w+$/.test(bgColor)); + + if (hasSolidBackground) { + return true; + } + + // Next try rgba. + const hasRGBA = /^rgba\([^\)]+\)/.exec(bgColor); + if (hasRGBA !== null && hasRGBA.length >= 2) { + const alpha = parseFloat(hasRGBA[1].split(',').slice(-1)); + if (alpha > 0) { + return true; + } + } + + // Then HSLA. + const hasHSLA = /^hsla\([^\)]+\)/.exec(bgColor); + if (hasHSLA !== null && hasHSLA.length >= 2) { + const alpha = parseFloat(hasHSLA[1].split(',').slice(-1)); + if (alpha > 0) { + return true; + } + } + + // Finally try and see if there's a background image. + const hasBackgroundImage = computedStyle.backgroundImage !== 'none'; + const backgroundRepeats = /repeat/.test(computedStyle.backgroundRepeat); + + return (hasBackgroundImage && backgroundRepeats); + }; + // Walk the tree of elements... const candidates = [...document.querySelectorAll('*')] .filter(e => { @@ -47,7 +84,9 @@ function getInterstitial() { const isCoveringViewport = ((size / viewportSize) > 0.5); // Check it's visible. - const isVisible = computedStyle.opacity > 0 && computedStyle.display !== 'none'; + const isVisible = computedStyle.opacity > 0 && + computedStyle.display !== 'none' && + hasNonTransparentBackground(computedStyle); // Check it's clickable. const isClickable = computedStyle.pointerEvents !== 'none'; @@ -63,6 +102,10 @@ function getInterstitial() { class Interstitial extends Gather { + static get test() { + return getInterstitial; + } + afterPass(options) { const driver = options.driver; diff --git a/lighthouse-core/test/driver/gatherers/interstitial.js b/lighthouse-core/test/driver/gatherers/interstitial.js index 33fa91b1aede..dad2ec08c61a 100644 --- a/lighthouse-core/test/driver/gatherers/interstitial.js +++ b/lighthouse-core/test/driver/gatherers/interstitial.js @@ -53,4 +53,111 @@ describe('Interstitial gatherer', () => { assert.equal(interstitialGatherer.artifact, -1); }); }); + + it('successfully locates interstitial-like elements', () => { + return new Promise((resolve, _) => { + const context = { + window: { + getComputedStyle(element) { + const styles = { + backgroundColor: 'red', + position: 'fixed', + opacity: 1, + pointerEvents: 'auto', + display: 'block' + }; + + switch (element.name) { + case 1: + // Fails test. + styles.backgroundColor = 'rgba(0, 0, 0, 0)'; + break; + + case 2: + // Fails test. + styles.backgroundColor = 'hsla(0, 0, 0, 0)'; + break; + + case 3: + // Passes test. + styles.backgroundColor = 'hsla(0, 0, 0, 0)'; + styles.backgroundImage = 'url(example.png)'; + styles.backgroundRepeat = 'repeat-x'; + break; + + case 4: + // Fails test. + styles.position = 'relative'; + break; + + case 5: + // Fails test. + styles.opacity = 0; + break; + + case 6: + // Fails test. + styles.pointerEvents = 'none'; + break; + + case 7: + // Fails test. + styles.display = 'none'; + break; + + default: + // Passes test. + break; + } + + return styles; + }, + outerWidth: 500, + outerHeight: 500 + }, + document: { + querySelectorAll() { + const getBoundingClientRect = function() { + // Fails test. + if (this.name === 8) { + return { + width: 100, height: 100 + }; + } + + // Passes test. + return { + width: 500, height: 500 + }; + }; + + return [0, 1, 2, 3, 4, 5, 6, 7, 8].map(name => { + return { + name, + getBoundingClientRect: getBoundingClientRect.bind({name}) + }; + }); + } + } + }; + + global.window = context.window; + global.document = context.document; + global.__returnResults = results => { + assert.ok(Array.isArray(results)); + assert.equal(results.length, 2); + assert.equal(results[0].name, 0); + assert.equal(results[1].name, 3); + resolve(); + + global.__returnResults = null; + }; + + const test = InterstitialGatherer.test.bind(context); + test(); + + global.window = null; + global.document = null; + }); + }); });