@luminati-io/webdriverio8
Version:
Next-gen browser and mobile automation test framework for Node.js
222 lines • 17.2 kB
JavaScript
/*
* Copyright (C) 2017 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* check if element is visible
* @param {HTMLElement} elem element to check
* @return {Boolean} true if element is within viewport
*/
export default function isElementDisplayed(element) {
function nodeIsElement(node) {
if (!node) {
return false;
}
switch (node.nodeType) {
case Node.ELEMENT_NODE:
case Node.DOCUMENT_NODE:
case Node.DOCUMENT_FRAGMENT_NODE:
return true;
default:
return false;
}
}
function parentElementForElement(element) {
if (!element) {
return null;
}
return enclosingNodeOrSelfMatchingPredicate(element.parentNode, nodeIsElement);
}
function enclosingNodeOrSelfMatchingPredicate(targetNode, predicate) {
for (let node = targetNode; node && node !== targetNode.ownerDocument; node = node.parentNode) {
if (predicate(node)) {
return node;
}
}
return null;
}
function enclosingElementOrSelfMatchingPredicate(targetElement, predicate) {
for (let element = targetElement; element && element !== targetElement.ownerDocument; element = parentElementForElement(element)) {
if (predicate(element)) {
return element;
}
}
return null;
}
function cascadedStylePropertyForElement(element, property) {
if (!element || !property) {
return null;
}
// if document-fragment, skip it and use element.host instead. This happens
// when the element is inside a shadow root.
// window.getComputedStyle errors on document-fragment.
if ('ShadowRoot' in window && element instanceof window.ShadowRoot) {
element = element.host;
}
const computedStyle = window.getComputedStyle(element);
const computedStyleProperty = computedStyle.getPropertyValue(property);
if (computedStyleProperty && computedStyleProperty !== 'inherit') {
return computedStyleProperty;
}
// Ideally getPropertyValue would return the 'used' or 'actual' value, but
// it doesn't for legacy reasons. So we need to do our own poor man's cascade.
// Fall back to the first non-'inherit' value found in an ancestor.
// In any case, getPropertyValue will not return 'initial'.
// FIXME: will this incorrectly inherit non-inheritable CSS properties?
// I think all important non-inheritable properties (width, height, etc.)
// for our purposes here are specially resolved, so this may not be an issue.
// Specification is here: https://drafts.csswg.org/cssom/#resolved-values
const parentElement = parentElementForElement(element);
return cascadedStylePropertyForElement(parentElement, property);
}
function elementHasBoundingBox(element) {
const boundingBox = element.getBoundingClientRect();
return boundingBox.width > 0 && boundingBox.height > 0;
}
function elementSubtreeHasNonZeroDimensions(element) {
if (elementHasBoundingBox(element)) {
return true;
}
// Paths can have a zero width or height. Treat them as shown if the stroke width is positive.
const boundingBox = element.getBoundingClientRect();
if (element.tagName.toUpperCase() === 'PATH' && boundingBox.width + boundingBox.height > 0) {
const strokeWidth = cascadedStylePropertyForElement(element, 'stroke-width');
return !!strokeWidth && (parseInt(strokeWidth, 10) > 0);
}
const cascadedOverflow = cascadedStylePropertyForElement(element, 'overflow');
if (cascadedOverflow === 'hidden') {
return false;
}
// If the container's overflow is not hidden and it has zero size, consider the
// container to have non-zero dimensions if a child node has non-zero dimensions.
return [].some.call(element.childNodes, function (childNode) {
if (childNode.nodeType === Node.TEXT_NODE) {
return true;
}
if (nodeIsElement(childNode)) {
return elementSubtreeHasNonZeroDimensions(childNode);
}
return false;
});
}
function elementOverflowsContainer(element) {
const cascadedOverflow = cascadedStylePropertyForElement(element, 'overflow');
if (cascadedOverflow !== 'hidden') {
return false;
}
// FIXME: this needs to take into account the scroll position of the element,
// the display modes of it and its ancestors, and the container it overflows.
// See Selenium's bot.dom.getOverflowState atom for an exhaustive list of edge cases.
return true;
}
function isElementSubtreeHiddenByOverflow(element) {
if (!element) {
return false;
}
if (!elementOverflowsContainer(element)) {
return false;
}
if (!element.childNodes.length) {
return false;
}
// This element's subtree is hidden by overflow if all child subtrees are as well.
return [].every.call(element.childNodes, function (childNode) {
// Returns true if the child node is overflowed or otherwise hidden.
// Base case: not an element, has zero size, scrolled out, or doesn't overflow container.
// Visibility of text nodes is controlled by parent
if (childNode.nodeType === Node.TEXT_NODE) {
return false;
}
if (!nodeIsElement(childNode)) {
return true;
}
if (!elementSubtreeHasNonZeroDimensions(childNode)) {
return true;
}
// Recurse.
return isElementSubtreeHiddenByOverflow(childNode);
});
}
// walk up the tree testing for a shadow root
function isElementInsideShadowRoot(element) {
if (!element) {
return false;
}
if (element.parentNode && element.parentNode.host) {
return true;
}
return isElementInsideShadowRoot(element.parentNode);
}
// This is a partial reimplementation of Selenium's "element is displayed" algorithm.
// When the W3C specification's algorithm stabilizes, we should implement that.
// If this command is misdirected to the wrong document (and is NOT inside a shadow root), treat it as not shown.
if (!isElementInsideShadowRoot(element) && !document.body.contains(element)) {
return false;
}
// Special cases for specific tag names.
switch (element.tagName.toUpperCase()) {
case 'BODY':
return true;
case 'SCRIPT':
case 'NOSCRIPT':
return false;
case 'OPTGROUP':
case 'OPTION': {
// Option/optgroup are considered shown if the containing <select> is shown.
const enclosingSelectElement = enclosingNodeOrSelfMatchingPredicate(element, function (e) {
return e.tagName.toUpperCase() === 'SELECT';
});
return isElementDisplayed(enclosingSelectElement);
}
case 'INPUT':
// <input type="hidden"> is considered not shown.
if (element.type === 'hidden') {
return false;
}
break;
// case 'MAP':
// FIXME: Selenium has special handling for <map> elements. We don't do anything now.
default:
break;
}
if (cascadedStylePropertyForElement(element, 'visibility') !== 'visible') {
return false;
}
const hasAncestorWithZeroOpacity = !!enclosingElementOrSelfMatchingPredicate(element, function (e) {
return Number(cascadedStylePropertyForElement(e, 'opacity')) === 0;
});
const hasAncestorWithDisplayNone = !!enclosingElementOrSelfMatchingPredicate(element, function (e) {
return cascadedStylePropertyForElement(e, 'display') === 'none';
});
if (hasAncestorWithZeroOpacity || hasAncestorWithDisplayNone) {
return false;
}
if (!elementSubtreeHasNonZeroDimensions(element)) {
return false;
}
if (isElementSubtreeHiddenByOverflow(element) && !elementHasBoundingBox(element)) {
return false;
}
return true;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaXNFbGVtZW50RGlzcGxheWVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NjcmlwdHMvaXNFbGVtZW50RGlzcGxheWVkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXVCRztBQUVIOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsT0FBTyxVQUFVLGtCQUFrQixDQUFFLE9BQWdCO0lBQ3hELFNBQVMsYUFBYSxDQUFDLElBQWM7UUFDakMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ1IsT0FBTyxLQUFLLENBQUE7UUFDaEIsQ0FBQztRQUVELFFBQVEsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ3hCLEtBQUssSUFBSSxDQUFDLFlBQVksQ0FBQztZQUN2QixLQUFLLElBQUksQ0FBQyxhQUFhLENBQUM7WUFDeEIsS0FBSyxJQUFJLENBQUMsc0JBQXNCO2dCQUM1QixPQUFPLElBQUksQ0FBQTtZQUVmO2dCQUNJLE9BQU8sS0FBSyxDQUFBO1FBQ2hCLENBQUM7SUFDTCxDQUFDO0lBRUQsU0FBUyx1QkFBdUIsQ0FBQyxPQUFpQjtRQUM5QyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDWCxPQUFPLElBQUksQ0FBQTtRQUNmLENBQUM7UUFFRCxPQUFPLG9DQUFvQyxDQUFDLE9BQU8sQ0FBQyxVQUF3QixFQUFFLGFBQWEsQ0FBQyxDQUFBO0lBQ2hHLENBQUM7SUFFRCxTQUFTLG9DQUFvQyxDQUFDLFVBQW9DLEVBQUUsU0FBbUI7UUFDbkcsS0FDSSxJQUFJLElBQUksR0FBZSxVQUFVLEVBQ2pDLElBQUksSUFBSSxJQUFJLEtBQU0sVUFBbUIsQ0FBQyxhQUFhLEVBQ25ELElBQUksR0FBSSxJQUFvQixDQUFDLFVBQXdCLEVBQ3ZELENBQUM7WUFDQyxJQUFJLFNBQVMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO2dCQUNsQixPQUFPLElBQUksQ0FBQTtZQUNmLENBQUM7UUFDTCxD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