Joey Arhar ab323122d8
feat(chromium): use Element.checkVisibility in isElementVisible (#16592)
Element.checkVisibility is a new browser API that was shipped in
chromium 105:
https://bugs.chromium.org/p/chromium/issues/detail?id=1309533

Using checkVisibility accounts for the content-visibility:hidden in the
user-agent ShadowRoot of the details element, which means we can remove
the usage of the AutoExpandDetailsElementFlag (I am trying to remove the
flag in chromium).

This behavior is covered by the existing "isVisible and isHidden should
work with details" test in locator-convenience.spec.ts.
2022-09-22 13:48:58 -07:00

92 lines
3.5 KiB
TypeScript

/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function isInsideScope(scope: Node, element: Element | undefined): boolean {
while (element) {
if (scope.contains(element))
return true;
element = enclosingShadowHost(element);
}
return false;
}
export function parentElementOrShadowHost(element: Element): Element | undefined {
if (element.parentElement)
return element.parentElement;
if (!element.parentNode)
return;
if (element.parentNode.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */ && (element.parentNode as ShadowRoot).host)
return (element.parentNode as ShadowRoot).host;
}
export function enclosingShadowRootOrDocument(element: Element): Document | ShadowRoot | undefined {
let node: Node = element;
while (node.parentNode)
node = node.parentNode;
if (node.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */ || node.nodeType === 9 /* Node.DOCUMENT_NODE */)
return node as Document | ShadowRoot;
}
function enclosingShadowHost(element: Element): Element | undefined {
while (element.parentElement)
element = element.parentElement;
return parentElementOrShadowHost(element);
}
export function closestCrossShadow(element: Element | undefined, css: string): Element | undefined {
while (element) {
const closest = element.closest(css);
if (closest)
return closest;
element = enclosingShadowHost(element);
}
}
export function isElementVisible(element: Element): boolean {
// Note: this logic should be similar to waitForDisplayedAtStablePosition() to avoid surprises.
if (!element.ownerDocument || !element.ownerDocument.defaultView)
return true;
const style = element.ownerDocument.defaultView.getComputedStyle(element);
if (style.display === 'contents') {
// display:contents is not rendered itself, but its child nodes are.
for (let child = element.firstChild; child; child = child.nextSibling) {
if (child.nodeType === 1 /* Node.ELEMENT_NODE */ && isElementVisible(child as Element))
return true;
if (child.nodeType === 3 /* Node.TEXT_NODE */ && isVisibleTextNode(child as Text))
return true;
}
return false;
}
// Element.checkVisibility checks for content-visibility and also looks at
// styles up the flat tree including user-agent ShadowRoots, such as the
// details element for example.
// @ts-ignore Typescript doesn't know that checkVisibility exists yet.
if (Element.prototype.checkVisibility && !element.checkVisibility({ checkOpacity: false, checkVisibilityCSS: false }))
return false;
if (!style || style.visibility === 'hidden')
return false;
const rect = element.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
function isVisibleTextNode(node: Text) {
// https://stackoverflow.com/questions/1461059/is-there-an-equivalent-to-getboundingclientrect-for-text-nodes
const range = document.createRange();
range.selectNode(node);
const rect = range.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}