From 1aee808bf8c15d7c271e2b51b6e1cf0a60e08878 Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Wed, 8 Apr 2026 15:30:28 +0100 Subject: [PATCH 1/6] [JavaScript] Move 'isDisplayed' atom to be typescript --- javascript/atoms/BUILD.bazel | 1 + javascript/atoms/fragments/BUILD.bazel | 16 + .../html5/shadow_dom_typescript_test.html | 230 ++++++ .../atoms/test/shown_typescript_test.html | 657 ++++++++++++++++++ .../atoms/typescript/is-displayed-global.js | 437 ++++++++++++ javascript/atoms/typescript/is-displayed.js | 433 ++++++++++++ javascript/atoms/typescript/is-displayed.ts | 466 +++++++++++++ .../selenium-webdriver/lib/atoms/BUILD.bazel | 12 + py/BUILD.bazel | 2 +- 9 files changed, 2253 insertions(+), 1 deletion(-) create mode 100644 javascript/atoms/test/html5/shadow_dom_typescript_test.html create mode 100644 javascript/atoms/test/shown_typescript_test.html create mode 100644 javascript/atoms/typescript/is-displayed-global.js create mode 100644 javascript/atoms/typescript/is-displayed.js create mode 100644 javascript/atoms/typescript/is-displayed.ts diff --git a/javascript/atoms/BUILD.bazel b/javascript/atoms/BUILD.bazel index b6b787107fb39..934c89aa9ab6f 100644 --- a/javascript/atoms/BUILD.bazel +++ b/javascript/atoms/BUILD.bazel @@ -11,6 +11,7 @@ filegroup( "**/*.js", "**/*.png", "**/*.svg", + "**/*.ts", ]), visibility = [ "//dotnet/test:__subpackages__", diff --git a/javascript/atoms/fragments/BUILD.bazel b/javascript/atoms/fragments/BUILD.bazel index 2bbe6e6678fc2..15aedb7b01f32 100644 --- a/javascript/atoms/fragments/BUILD.bazel +++ b/javascript/atoms/fragments/BUILD.bazel @@ -1,3 +1,4 @@ +load("//common:defs.bzl", "copy_file") load("//javascript:defs.bzl", "closure_fragment") closure_fragment( @@ -130,6 +131,21 @@ closure_fragment( ], ) +copy_file( + name = "is-displayed-typescript", + src = "//javascript/atoms:typescript/is-displayed.js", + out = "is-displayed-typescript.js", + visibility = [ + "//dotnet/src/webdriver:__pkg__", + "//java/src/org/openqa/selenium/remote:__pkg__", + "//javascript/chrome-driver:__pkg__", + "//javascript/ie-driver:__pkg__", + "//javascript/selenium-webdriver/lib/atoms:__pkg__", + "//py:__pkg__", + "//rb/lib/selenium/webdriver/atoms:__pkg__", + ], +) + closure_fragment( name = "is-editable", function = "bot.dom.isEditable", diff --git a/javascript/atoms/test/html5/shadow_dom_typescript_test.html b/javascript/atoms/test/html5/shadow_dom_typescript_test.html new file mode 100644 index 0000000000000..cd31886bd2cf0 --- /dev/null +++ b/javascript/atoms/test/html5/shadow_dom_typescript_test.html @@ -0,0 +1,230 @@ + + + + + + + + + + + + +
+
+ +

Page for Shadow DOM tests

+ +
Foo
+
+
+
Foo
+ +
+
+ +
Foo
+
+
Foo
+
+
Foo
+
Bar
+
Baz
+
+ + + + diff --git a/javascript/atoms/test/shown_typescript_test.html b/javascript/atoms/test/shown_typescript_test.html new file mode 100644 index 0000000000000..8a2040a8353dc --- /dev/null +++ b/javascript/atoms/test/shown_typescript_test.html @@ -0,0 +1,657 @@ + + + + + + shown_typescript_test.html + + + + + + + + + + + + + + +
+
+
+

Hello world. I like cheese.

+
+
+

Displayed

+ +

Transparent

+ +
+ + + + + + + + + +
+ cheese puffs +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
text
+
+ + + + +
+ +
+ + +
+ This is a zero height/width div and overflow hidden +
+ +
+
+ This is a zero height/width div and overflow hidden +
+
+
+ +
+ +
+ +
+
+ positivePositionChildInNegativePositionParent +
+
+ + +
+ +
+ + + + + + + + + + + +
a
b
c
+ +
+ Some Summary +
+

More text

+
+
+ +
+ A +
B
+
+ +
+
text
+
+ + + + diff --git a/javascript/atoms/typescript/is-displayed-global.js b/javascript/atoms/typescript/is-displayed-global.js new file mode 100644 index 0000000000000..b85a686405dd4 --- /dev/null +++ b/javascript/atoms/typescript/is-displayed-global.js @@ -0,0 +1,437 @@ +window.bot = window.bot || {}; +window.bot.dom = window.bot.dom || {}; +window.bot.dom.typescript = window.bot.dom.typescript || {}; +window.bot.dom.typescript.IS_SHADOW_DOM_ENABLED = typeof ShadowRoot === 'function'; +window.bot.dom.typescript.isShown = (function () { + function toUpperCaseTag(tagName) { + return tagName ? tagName.toUpperCase() : undefined; + } + + function isElement(node, tagName) { + if (!node || !(node instanceof Element)) { + return false; + } + var normalizedTagName = toUpperCaseTag(tagName); + if (node instanceof HTMLFormElement) { + return !normalizedTagName || normalizedTagName === 'FORM'; + } + return ( + typeof node.tagName === 'string' && + (!normalizedTagName || node.tagName.toUpperCase() === normalizedTagName) + ); + } + + function getParentElement(node) { + var current = node.parentNode; + while ( + current && + current.nodeType !== Node.ELEMENT_NODE && + current.nodeType !== Node.DOCUMENT_NODE && + current.nodeType !== Node.DOCUMENT_FRAGMENT_NODE + ) { + current = current.parentNode; + } + return current instanceof Element ? current : null; + } + + function getEffectiveStyle(elem, propertyName) { + var win = elem.ownerDocument.defaultView; + if (!win) { + return null; + } + var computed = win.getComputedStyle(elem); + if (!computed) { + return null; + } + return computed.getPropertyValue(propertyName) || null; + } + + function getOpacity(elem) { + var opacityStyle = getEffectiveStyle(elem, 'opacity'); + var opacity = opacityStyle ? Number(opacityStyle) : 1; + var parent = getParentElement(elem); + return parent ? opacity * getOpacity(parent) : opacity; + } + + function getParentNodeInComposedDom(node) { + var parent = node.parentNode; + if (parent && parent.shadowRoot && node.assignedSlot !== undefined) { + return node.assignedSlot ? node.assignedSlot.parentNode : null; + } + return parent; + } + + function createRect(left, top, width, height) { + return { + left: left, + top: top, + right: left + width, + bottom: top + height, + width: width, + height: height, + }; + } + + function getClientRect(elem) { + var imageMap = maybeFindImageMap(elem); + if (imageMap) { + return imageMap.rect; + } + + if (isElement(elem, 'HTML')) { + var doc = elem.ownerDocument; + return createRect(0, 0, doc.documentElement.clientWidth, doc.documentElement.clientHeight); + } + + try { + var nativeRect = elem.getBoundingClientRect(); + return { + left: nativeRect.left, + top: nativeRect.top, + right: nativeRect.right, + bottom: nativeRect.bottom, + width: nativeRect.right - nativeRect.left, + height: nativeRect.bottom - nativeRect.top, + }; + } catch (_error) { + return createRect(0, 0, 0, 0); + } + } + + function getAreaRelativeRect(area) { + var shape = area.shape.toLowerCase(); + var coords = area.coords.split(',').map(function (value) { + return Number(value.trim()); + }); + + if (shape === 'rect' && coords.length === 4) { + return createRect(coords[0], coords[1], coords[2] - coords[0], coords[3] - coords[1]); + } + + if (shape === 'circle' && coords.length === 3) { + return createRect(coords[0] - coords[2], coords[1] - coords[2], coords[2] * 2, coords[2] * 2); + } + + if (shape === 'poly' && coords.length > 2) { + var minX = coords[0]; + var minY = coords[1]; + var maxX = minX; + var maxY = minY; + + for (var index = 2; index + 1 < coords.length; index += 2) { + minX = Math.min(minX, coords[index]); + maxX = Math.max(maxX, coords[index]); + minY = Math.min(minY, coords[index + 1]); + maxY = Math.max(maxY, coords[index + 1]); + } + + return createRect(minX, minY, maxX - minX, maxY - minY); + } + + return createRect(0, 0, 0, 0); + } + + function findImageUsingMap(mapName, doc) { + var elements = doc.getElementsByTagName('*'); + for (var index = 0; index < elements.length; index += 1) { + var useMap = elements[index].getAttribute('usemap'); + if (useMap === '#' + mapName) { + return elements[index]; + } + } + return null; + } + + function maybeFindImageMap(elem) { + var isMap = isElement(elem, 'MAP'); + if (!isMap && !isElement(elem, 'AREA')) { + return null; + } + + var map = isMap ? elem : isElement(elem.parentNode, 'MAP') ? elem.parentNode : null; + var image = null; + var rect = createRect(0, 0, 0, 0); + + if (map instanceof HTMLMapElement && map.name) { + image = findImageUsingMap(map.name, map.ownerDocument); + if (image) { + rect = getClientRect(image); + if (!isMap && elem instanceof HTMLAreaElement && elem.shape.toLowerCase() !== 'default') { + var relativeRect = getAreaRelativeRect(elem); + var relativeX = Math.min(Math.max(relativeRect.left, 0), rect.width); + var relativeY = Math.min(Math.max(relativeRect.top, 0), rect.height); + var width = Math.min(relativeRect.width, rect.width - relativeX); + var height = Math.min(relativeRect.height, rect.height - relativeY); + rect = createRect(relativeX + rect.left, relativeY + rect.top, width, height); + } + } + } + + return { image: image, rect: rect }; + } + + function getClientRegion(elem) { + return getClientRect(elem); + } + + function getOverflowState(elem) { + var region = getClientRegion(elem); + var ownerDoc = elem.ownerDocument; + var htmlElem = ownerDoc.documentElement; + var bodyElem = ownerDoc.body; + var htmlOverflowStyle = getEffectiveStyle(htmlElem, 'overflow') || 'visible'; + var treatAsFixedPosition = false; + + function canBeOverflowed(container, position) { + if (container === htmlElem) { + return true; + } + + var display = getEffectiveStyle(container, 'display') || ''; + if (display.indexOf('inline') === 0 || display === 'contents') { + return false; + } + + if (position === 'absolute' && getEffectiveStyle(container, 'position') === 'static') { + return false; + } + + return true; + } + + function getOverflowParent(current) { + var position = getEffectiveStyle(current, 'position'); + if (position === 'fixed') { + treatAsFixedPosition = true; + return current === htmlElem ? null : htmlElem; + } + + var parent = getParentElement(current); + while (parent && !canBeOverflowed(parent, position)) { + parent = getParentElement(parent); + } + return parent; + } + + function getOverflowStyles(current) { + var overflowElem = current; + if (htmlOverflowStyle === 'visible') { + if (current === htmlElem && bodyElem) { + overflowElem = bodyElem; + } else if (current === bodyElem) { + return { x: 'visible', y: 'visible' }; + } + } + + var overflow = { + x: getEffectiveStyle(overflowElem, 'overflow-x') || 'visible', + y: getEffectiveStyle(overflowElem, 'overflow-y') || 'visible', + }; + + if (current === htmlElem) { + overflow.x = overflow.x === 'visible' ? 'auto' : overflow.x; + overflow.y = overflow.y === 'visible' ? 'auto' : overflow.y; + } + + return overflow; + } + + function getScroll(current) { + if (current === htmlElem) { + return { + x: ownerDoc.defaultView ? ownerDoc.defaultView.pageXOffset : 0, + y: ownerDoc.defaultView ? ownerDoc.defaultView.pageYOffset : 0, + }; + } + + return { x: current.scrollLeft, y: current.scrollTop }; + } + + for (var container = getOverflowParent(elem); container; container = getOverflowParent(container)) { + var containerOverflow = getOverflowStyles(container); + if (containerOverflow.x === 'visible' && containerOverflow.y === 'visible') { + continue; + } + + var containerRect = getClientRect(container); + if (containerRect.width === 0 || containerRect.height === 0) { + return 'hidden'; + } + + var underflowsX = region.right < containerRect.left; + var underflowsY = region.bottom < containerRect.top; + if ((underflowsX && containerOverflow.x === 'hidden') || (underflowsY && containerOverflow.y === 'hidden')) { + return 'hidden'; + } + + if ((underflowsX && containerOverflow.x !== 'visible') || (underflowsY && containerOverflow.y !== 'visible')) { + var containerScroll = getScroll(container); + var unscrollableX = region.right < containerRect.left - containerScroll.x; + var unscrollableY = region.bottom < containerRect.top - containerScroll.y; + if ((unscrollableX && containerOverflow.x !== 'visible') || (unscrollableY && containerOverflow.y !== 'visible')) { + return 'hidden'; + } + + var containerUnderflowState = getOverflowState(container); + return containerUnderflowState === 'hidden' ? 'hidden' : 'scroll'; + } + + var overflowsX = region.left >= containerRect.left + containerRect.width; + var overflowsY = region.top >= containerRect.top + containerRect.height; + if ((overflowsX && containerOverflow.x === 'hidden') || (overflowsY && containerOverflow.y === 'hidden')) { + return 'hidden'; + } + + if ((overflowsX && containerOverflow.x !== 'visible') || (overflowsY && containerOverflow.y !== 'visible')) { + if (treatAsFixedPosition) { + var docScroll = getScroll(container); + if (region.left >= htmlElem.scrollWidth - docScroll.x || region.right >= htmlElem.scrollHeight - docScroll.y) { + return 'hidden'; + } + } + + var containerOverflowState = getOverflowState(container); + return containerOverflowState === 'hidden' ? 'hidden' : 'scroll'; + } + } + + return 'none'; + } + + function isShownInternal(elem, ignoreOpacity, displayedFn) { + if (!isElement(elem)) { + throw new Error('Argument to isShown must be of type Element'); + } + + if (isElement(elem, 'BODY')) { + return true; + } + + if (isElement(elem, 'OPTION') || isElement(elem, 'OPTGROUP')) { + var select = elem.closest('select'); + return !!select && isShownInternal(select, true, displayedFn); + } + + var imageMap = maybeFindImageMap(elem); + if (imageMap) { + return !!imageMap.image && imageMap.rect.width > 0 && imageMap.rect.height > 0 && + isShownInternal(imageMap.image, ignoreOpacity, displayedFn); + } + + if (isElement(elem, 'INPUT') && elem.type.toLowerCase() === 'hidden') { + return false; + } + + if (isElement(elem, 'NOSCRIPT')) { + return false; + } + + var visibility = getEffectiveStyle(elem, 'visibility'); + if (visibility === 'collapse' || visibility === 'hidden') { + return false; + } + + if (!displayedFn(elem)) { + return false; + } + + if (!ignoreOpacity && getOpacity(elem) === 0) { + return false; + } + + function positiveSize(element) { + var rect = getClientRect(element); + if (rect.height > 0 && rect.width > 0) { + return true; + } + + if (isElement(element, 'PATH') && (rect.height > 0 || rect.width > 0)) { + var strokeWidth = getEffectiveStyle(element, 'stroke-width'); + return !!strokeWidth && parseInt(strokeWidth, 10) > 0; + } + + var elementVisibility = getEffectiveStyle(element, 'visibility'); + if (elementVisibility === 'collapse' || elementVisibility === 'hidden') { + return false; + } + + if (!displayedFn(element)) { + return false; + } + + if (getEffectiveStyle(element, 'overflow') === 'hidden') { + return false; + } + + for (var index = 0; index < element.childNodes.length; index += 1) { + var child = element.childNodes[index]; + if (child.nodeType === Node.TEXT_NODE) { + var text = child.nodeValue || ''; + if (/^[\s]*$/.test(text) && /[\n\r\t]/.test(text)) { + continue; + } + return true; + } + + if (child instanceof Element && positiveSize(child)) { + return true; + } + } + + return false; + } + + if (!positiveSize(elem)) { + return false; + } + + function hiddenByOverflow(element) { + if (getOverflowState(element) !== 'hidden') { + return false; + } + + for (var index = 0; index < element.childNodes.length; index += 1) { + var child = element.childNodes[index]; + if (child instanceof Element && !hiddenByOverflow(child) && positiveSize(child)) { + return false; + } + } + + return true; + } + + return !hiddenByOverflow(elem); + } + + return function (elem, optIgnoreOpacity) { + function displayed(node) { + if (isElement(node)) { + var display = getEffectiveStyle(node, 'display'); + var contentVisibility = getEffectiveStyle(node, 'content-visibility'); + if (display === 'none' || contentVisibility === 'hidden') { + return false; + } + } + + var parent = getParentNodeInComposedDom(node); + if (typeof ShadowRoot === 'function' && parent instanceof ShadowRoot) { + if (parent.host.shadowRoot && parent.host.shadowRoot !== parent) { + return false; + } + parent = parent.host; + } + + if (parent && (parent.nodeType === Node.DOCUMENT_NODE || parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE)) { + return true; + } + + if (parent instanceof HTMLDetailsElement && !parent.open && !isElement(node, 'SUMMARY')) { + return false; + } + + return !!parent && displayed(parent); + } + + return isShownInternal(elem, !!optIgnoreOpacity, displayed); + }; +})(); \ No newline at end of file diff --git a/javascript/atoms/typescript/is-displayed.js b/javascript/atoms/typescript/is-displayed.js new file mode 100644 index 0000000000000..d22e911505dec --- /dev/null +++ b/javascript/atoms/typescript/is-displayed.js @@ -0,0 +1,433 @@ +(function () { + function toUpperCaseTag(tagName) { + return tagName ? tagName.toUpperCase() : undefined; + } + + function isElement(node, tagName) { + if (!node || !(node instanceof Element)) { + return false; + } + var normalizedTagName = toUpperCaseTag(tagName); + if (node instanceof HTMLFormElement) { + return !normalizedTagName || normalizedTagName === 'FORM'; + } + return ( + typeof node.tagName === 'string' && + (!normalizedTagName || node.tagName.toUpperCase() === normalizedTagName) + ); + } + + function getParentElement(node) { + var current = node.parentNode; + while ( + current && + current.nodeType !== Node.ELEMENT_NODE && + current.nodeType !== Node.DOCUMENT_NODE && + current.nodeType !== Node.DOCUMENT_FRAGMENT_NODE + ) { + current = current.parentNode; + } + return current instanceof Element ? current : null; + } + + function getEffectiveStyle(elem, propertyName) { + var win = elem.ownerDocument.defaultView; + if (!win) { + return null; + } + var computed = win.getComputedStyle(elem); + if (!computed) { + return null; + } + return computed.getPropertyValue(propertyName) || null; + } + + function getOpacity(elem) { + var opacityStyle = getEffectiveStyle(elem, 'opacity'); + var opacity = opacityStyle ? Number(opacityStyle) : 1; + var parent = getParentElement(elem); + return parent ? opacity * getOpacity(parent) : opacity; + } + + function getParentNodeInComposedDom(node) { + var parent = node.parentNode; + if (parent && parent.shadowRoot && node.assignedSlot !== undefined) { + return node.assignedSlot ? node.assignedSlot.parentNode : null; + } + return parent; + } + + function createRect(left, top, width, height) { + return { + left: left, + top: top, + right: left + width, + bottom: top + height, + width: width, + height: height, + }; + } + + function getClientRect(elem) { + var imageMap = maybeFindImageMap(elem); + if (imageMap) { + return imageMap.rect; + } + + if (isElement(elem, 'HTML')) { + var doc = elem.ownerDocument; + return createRect(0, 0, doc.documentElement.clientWidth, doc.documentElement.clientHeight); + } + + try { + var nativeRect = elem.getBoundingClientRect(); + return { + left: nativeRect.left, + top: nativeRect.top, + right: nativeRect.right, + bottom: nativeRect.bottom, + width: nativeRect.right - nativeRect.left, + height: nativeRect.bottom - nativeRect.top, + }; + } catch (_error) { + return createRect(0, 0, 0, 0); + } + } + + function getAreaRelativeRect(area) { + var shape = area.shape.toLowerCase(); + var coords = area.coords.split(',').map(function (value) { + return Number(value.trim()); + }); + + if (shape === 'rect' && coords.length === 4) { + return createRect(coords[0], coords[1], coords[2] - coords[0], coords[3] - coords[1]); + } + + if (shape === 'circle' && coords.length === 3) { + return createRect(coords[0] - coords[2], coords[1] - coords[2], coords[2] * 2, coords[2] * 2); + } + + if (shape === 'poly' && coords.length > 2) { + var minX = coords[0]; + var minY = coords[1]; + var maxX = minX; + var maxY = minY; + + for (var index = 2; index + 1 < coords.length; index += 2) { + minX = Math.min(minX, coords[index]); + maxX = Math.max(maxX, coords[index]); + minY = Math.min(minY, coords[index + 1]); + maxY = Math.max(maxY, coords[index + 1]); + } + + return createRect(minX, minY, maxX - minX, maxY - minY); + } + + return createRect(0, 0, 0, 0); + } + + function findImageUsingMap(mapName, doc) { + var elements = doc.getElementsByTagName('*'); + for (var index = 0; index < elements.length; index += 1) { + var useMap = elements[index].getAttribute('usemap'); + if (useMap === '#' + mapName) { + return elements[index]; + } + } + return null; + } + + function maybeFindImageMap(elem) { + var isMap = isElement(elem, 'MAP'); + if (!isMap && !isElement(elem, 'AREA')) { + return null; + } + + var map = isMap ? elem : isElement(elem.parentNode, 'MAP') ? elem.parentNode : null; + var image = null; + var rect = createRect(0, 0, 0, 0); + + if (map instanceof HTMLMapElement && map.name) { + image = findImageUsingMap(map.name, map.ownerDocument); + if (image) { + rect = getClientRect(image); + if (!isMap && elem instanceof HTMLAreaElement && elem.shape.toLowerCase() !== 'default') { + var relativeRect = getAreaRelativeRect(elem); + var relativeX = Math.min(Math.max(relativeRect.left, 0), rect.width); + var relativeY = Math.min(Math.max(relativeRect.top, 0), rect.height); + var width = Math.min(relativeRect.width, rect.width - relativeX); + var height = Math.min(relativeRect.height, rect.height - relativeY); + rect = createRect(relativeX + rect.left, relativeY + rect.top, width, height); + } + } + } + + return { image: image, rect: rect }; + } + + function getClientRegion(elem) { + return getClientRect(elem); + } + + function getOverflowState(elem) { + var region = getClientRegion(elem); + var ownerDoc = elem.ownerDocument; + var htmlElem = ownerDoc.documentElement; + var bodyElem = ownerDoc.body; + var htmlOverflowStyle = getEffectiveStyle(htmlElem, 'overflow') || 'visible'; + var treatAsFixedPosition = false; + + function canBeOverflowed(container, position) { + if (container === htmlElem) { + return true; + } + + var display = getEffectiveStyle(container, 'display') || ''; + if (display.indexOf('inline') === 0 || display === 'contents') { + return false; + } + + if (position === 'absolute' && getEffectiveStyle(container, 'position') === 'static') { + return false; + } + + return true; + } + + function getOverflowParent(current) { + var position = getEffectiveStyle(current, 'position'); + if (position === 'fixed') { + treatAsFixedPosition = true; + return current === htmlElem ? null : htmlElem; + } + + var parent = getParentElement(current); + while (parent && !canBeOverflowed(parent, position)) { + parent = getParentElement(parent); + } + return parent; + } + + function getOverflowStyles(current) { + var overflowElem = current; + if (htmlOverflowStyle === 'visible') { + if (current === htmlElem && bodyElem) { + overflowElem = bodyElem; + } else if (current === bodyElem) { + return { x: 'visible', y: 'visible' }; + } + } + + var overflow = { + x: getEffectiveStyle(overflowElem, 'overflow-x') || 'visible', + y: getEffectiveStyle(overflowElem, 'overflow-y') || 'visible', + }; + + if (current === htmlElem) { + overflow.x = overflow.x === 'visible' ? 'auto' : overflow.x; + overflow.y = overflow.y === 'visible' ? 'auto' : overflow.y; + } + + return overflow; + } + + function getScroll(current) { + if (current === htmlElem) { + return { + x: ownerDoc.defaultView ? ownerDoc.defaultView.pageXOffset : 0, + y: ownerDoc.defaultView ? ownerDoc.defaultView.pageYOffset : 0, + }; + } + + return { x: current.scrollLeft, y: current.scrollTop }; + } + + for (var container = getOverflowParent(elem); container; container = getOverflowParent(container)) { + var containerOverflow = getOverflowStyles(container); + if (containerOverflow.x === 'visible' && containerOverflow.y === 'visible') { + continue; + } + + var containerRect = getClientRect(container); + if (containerRect.width === 0 || containerRect.height === 0) { + return 'hidden'; + } + + var underflowsX = region.right < containerRect.left; + var underflowsY = region.bottom < containerRect.top; + if ((underflowsX && containerOverflow.x === 'hidden') || (underflowsY && containerOverflow.y === 'hidden')) { + return 'hidden'; + } + + if ((underflowsX && containerOverflow.x !== 'visible') || (underflowsY && containerOverflow.y !== 'visible')) { + var containerScroll = getScroll(container); + var unscrollableX = region.right < containerRect.left - containerScroll.x; + var unscrollableY = region.bottom < containerRect.top - containerScroll.y; + if ((unscrollableX && containerOverflow.x !== 'visible') || (unscrollableY && containerOverflow.y !== 'visible')) { + return 'hidden'; + } + + var containerUnderflowState = getOverflowState(container); + return containerUnderflowState === 'hidden' ? 'hidden' : 'scroll'; + } + + var overflowsX = region.left >= containerRect.left + containerRect.width; + var overflowsY = region.top >= containerRect.top + containerRect.height; + if ((overflowsX && containerOverflow.x === 'hidden') || (overflowsY && containerOverflow.y === 'hidden')) { + return 'hidden'; + } + + if ((overflowsX && containerOverflow.x !== 'visible') || (overflowsY && containerOverflow.y !== 'visible')) { + if (treatAsFixedPosition) { + var docScroll = getScroll(container); + if (region.left >= htmlElem.scrollWidth - docScroll.x || region.right >= htmlElem.scrollHeight - docScroll.y) { + return 'hidden'; + } + } + + var containerOverflowState = getOverflowState(container); + return containerOverflowState === 'hidden' ? 'hidden' : 'scroll'; + } + } + + return 'none'; + } + + function isShownInternal(elem, ignoreOpacity, displayedFn) { + if (!isElement(elem)) { + throw new Error('Argument to isShown must be of type Element'); + } + + if (isElement(elem, 'BODY')) { + return true; + } + + if (isElement(elem, 'OPTION') || isElement(elem, 'OPTGROUP')) { + var select = elem.closest('select'); + return !!select && isShownInternal(select, true, displayedFn); + } + + var imageMap = maybeFindImageMap(elem); + if (imageMap) { + return !!imageMap.image && imageMap.rect.width > 0 && imageMap.rect.height > 0 && + isShownInternal(imageMap.image, ignoreOpacity, displayedFn); + } + + if (isElement(elem, 'INPUT') && elem.type.toLowerCase() === 'hidden') { + return false; + } + + if (isElement(elem, 'NOSCRIPT')) { + return false; + } + + var visibility = getEffectiveStyle(elem, 'visibility'); + if (visibility === 'collapse' || visibility === 'hidden') { + return false; + } + + if (!displayedFn(elem)) { + return false; + } + + if (!ignoreOpacity && getOpacity(elem) === 0) { + return false; + } + + function positiveSize(element) { + var rect = getClientRect(element); + if (rect.height > 0 && rect.width > 0) { + return true; + } + + if (isElement(element, 'PATH') && (rect.height > 0 || rect.width > 0)) { + var strokeWidth = getEffectiveStyle(element, 'stroke-width'); + return !!strokeWidth && parseInt(strokeWidth, 10) > 0; + } + + var elementVisibility = getEffectiveStyle(element, 'visibility'); + if (elementVisibility === 'collapse' || elementVisibility === 'hidden') { + return false; + } + + if (!displayedFn(element)) { + return false; + } + + if (getEffectiveStyle(element, 'overflow') === 'hidden') { + return false; + } + + for (var index = 0; index < element.childNodes.length; index += 1) { + var child = element.childNodes[index]; + if (child.nodeType === Node.TEXT_NODE) { + var text = child.nodeValue || ''; + if (/^[\s]*$/.test(text) && /[\n\r\t]/.test(text)) { + continue; + } + return true; + } + + if (child instanceof Element && positiveSize(child)) { + return true; + } + } + + return false; + } + + if (!positiveSize(elem)) { + return false; + } + + function hiddenByOverflow(element) { + if (getOverflowState(element) !== 'hidden') { + return false; + } + + for (var index = 0; index < element.childNodes.length; index += 1) { + var child = element.childNodes[index]; + if (child instanceof Element && !hiddenByOverflow(child) && positiveSize(child)) { + return false; + } + } + + return true; + } + + return !hiddenByOverflow(elem); + } + + return function (elem, optIgnoreOpacity) { + function displayed(node) { + if (isElement(node)) { + var display = getEffectiveStyle(node, 'display'); + var contentVisibility = getEffectiveStyle(node, 'content-visibility'); + if (display === 'none' || contentVisibility === 'hidden') { + return false; + } + } + + var parent = getParentNodeInComposedDom(node); + if (typeof ShadowRoot === 'function' && parent instanceof ShadowRoot) { + if (parent.host.shadowRoot && parent.host.shadowRoot !== parent) { + return false; + } + parent = parent.host; + } + + if (parent && (parent.nodeType === Node.DOCUMENT_NODE || parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE)) { + return true; + } + + if (parent instanceof HTMLDetailsElement && !parent.open && !isElement(node, 'SUMMARY')) { + return false; + } + + return !!parent && displayed(parent); + } + + return isShownInternal(elem, !!optIgnoreOpacity, displayed); + }; +})() \ No newline at end of file diff --git a/javascript/atoms/typescript/is-displayed.ts b/javascript/atoms/typescript/is-displayed.ts new file mode 100644 index 0000000000000..756a487f02a56 --- /dev/null +++ b/javascript/atoms/typescript/is-displayed.ts @@ -0,0 +1,466 @@ +type OverflowState = 'none' | 'hidden' | 'scroll'; + +interface Rect { + left: number; + top: number; + right: number; + bottom: number; + width: number; + height: number; +} + +interface ImageMapResult { + image: Element | null; + rect: Rect; +} + +interface OverflowStyles { + x: string; + y: string; +} + +interface Coordinate { + x: number; + y: number; +} + +var isShown = (function (): (elem: Element, optIgnoreOpacity?: boolean) => boolean { + function toUpperCaseTag(tagName?: string): string | undefined { + return tagName ? tagName.toUpperCase() : undefined; + } + + function isElement(node: unknown, tagName?: string): node is Element { + if (!node || !(node instanceof Element)) { + return false; + } + var normalizedTagName = toUpperCaseTag(tagName); + if (node instanceof HTMLFormElement) { + return !normalizedTagName || normalizedTagName === 'FORM'; + } + return ( + typeof node.tagName === 'string' && + (!normalizedTagName || node.tagName.toUpperCase() === normalizedTagName) + ); + } + + function getParentElement(node: Node): Element | null { + var current = node.parentNode; + while ( + current && + current.nodeType !== Node.ELEMENT_NODE && + current.nodeType !== Node.DOCUMENT_NODE && + current.nodeType !== Node.DOCUMENT_FRAGMENT_NODE + ) { + current = current.parentNode; + } + return current instanceof Element ? current : null; + } + + function getEffectiveStyle(elem: Element, propertyName: string): string | null { + var win = elem.ownerDocument.defaultView; + if (!win) { + return null; + } + var computed = win.getComputedStyle(elem); + if (!computed) { + return null; + } + var value = computed.getPropertyValue(propertyName); + return value || null; + } + + function getOpacity(elem: Element): number { + var opacityStyle = getEffectiveStyle(elem, 'opacity'); + var opacity = opacityStyle ? Number(opacityStyle) : 1; + var parent = getParentElement(elem); + return parent ? opacity * getOpacity(parent) : opacity; + } + + function getParentNodeInComposedDom(node: Node): Node | null { + var parent = node.parentNode; + var slottable = node as Node & { assignedSlot?: HTMLSlotElement | null }; + var parentWithShadow = parent as (Node & { shadowRoot?: ShadowRoot | null }) | null; + + if (parentWithShadow && parentWithShadow.shadowRoot && slottable.assignedSlot !== undefined) { + return slottable.assignedSlot ? slottable.assignedSlot.parentNode : null; + } + + return parent; + } + + function createRect(left: number, top: number, width: number, height: number): Rect { + return { + left: left, + top: top, + right: left + width, + bottom: top + height, + width: width, + height: height, + }; + } + + function getClientRect(elem: Element): Rect { + var imageMap = maybeFindImageMap(elem); + if (imageMap) { + return imageMap.rect; + } + + if (isElement(elem, 'HTML')) { + var doc = elem.ownerDocument; + return createRect(0, 0, doc.documentElement.clientWidth, doc.documentElement.clientHeight); + } + + try { + var nativeRect = elem.getBoundingClientRect(); + return { + left: nativeRect.left, + top: nativeRect.top, + right: nativeRect.right, + bottom: nativeRect.bottom, + width: nativeRect.right - nativeRect.left, + height: nativeRect.bottom - nativeRect.top, + }; + } catch (_error) { + return createRect(0, 0, 0, 0); + } + } + + function getAreaRelativeRect(area: HTMLAreaElement): Rect { + var shape = area.shape.toLowerCase(); + var coords = area.coords.split(',').map(function (value) { + return Number(value.trim()); + }); + + if (shape === 'rect' && coords.length === 4) { + return createRect(coords[0], coords[1], coords[2] - coords[0], coords[3] - coords[1]); + } + + if (shape === 'circle' && coords.length === 3) { + return createRect(coords[0] - coords[2], coords[1] - coords[2], coords[2] * 2, coords[2] * 2); + } + + if (shape === 'poly' && coords.length > 2) { + var minX = coords[0]; + var minY = coords[1]; + var maxX = minX; + var maxY = minY; + + for (var index = 2; index + 1 < coords.length; index += 2) { + minX = Math.min(minX, coords[index]); + maxX = Math.max(maxX, coords[index]); + minY = Math.min(minY, coords[index + 1]); + maxY = Math.max(maxY, coords[index + 1]); + } + + return createRect(minX, minY, maxX - minX, maxY - minY); + } + + return createRect(0, 0, 0, 0); + } + + function findImageUsingMap(mapName: string, doc: Document): Element | null { + var elements = doc.getElementsByTagName('*'); + for (var index = 0; index < elements.length; index += 1) { + var useMap = elements[index].getAttribute('usemap'); + if (useMap === '#' + mapName) { + return elements[index]; + } + } + return null; + } + + function maybeFindImageMap(elem: Element): ImageMapResult | null { + var isMap = isElement(elem, 'MAP'); + if (!isMap && !isElement(elem, 'AREA')) { + return null; + } + + var map = isMap ? elem : isElement(elem.parentNode, 'MAP') ? elem.parentNode : null; + var image: Element | null = null; + var rect = createRect(0, 0, 0, 0); + + if (map instanceof HTMLMapElement && map.name) { + image = findImageUsingMap(map.name, map.ownerDocument); + if (image) { + rect = getClientRect(image); + if (!isMap && elem instanceof HTMLAreaElement && elem.shape.toLowerCase() !== 'default') { + var relativeRect = getAreaRelativeRect(elem); + var relativeX = Math.min(Math.max(relativeRect.left, 0), rect.width); + var relativeY = Math.min(Math.max(relativeRect.top, 0), rect.height); + var width = Math.min(relativeRect.width, rect.width - relativeX); + var height = Math.min(relativeRect.height, rect.height - relativeY); + rect = createRect(relativeX + rect.left, relativeY + rect.top, width, height); + } + } + } + + return { image: image, rect: rect }; + } + + function getClientRegion(elem: Element): Rect { + return getClientRect(elem); + } + + function getOverflowState(elem: Element): OverflowState { + var region = getClientRegion(elem); + var ownerDoc = elem.ownerDocument; + var htmlElem = ownerDoc.documentElement; + var bodyElem = ownerDoc.body; + var htmlOverflowStyle = getEffectiveStyle(htmlElem, 'overflow') || 'visible'; + var treatAsFixedPosition = false; + + function canBeOverflowed(container: Element, position: string | null): boolean { + if (container === htmlElem) { + return true; + } + + var display = getEffectiveStyle(container, 'display') || ''; + if (display.indexOf('inline') === 0 || display === 'contents') { + return false; + } + + if (position === 'absolute' && getEffectiveStyle(container, 'position') === 'static') { + return false; + } + + return true; + } + + function getOverflowParent(current: Element): Element | null { + var position = getEffectiveStyle(current, 'position'); + if (position === 'fixed') { + treatAsFixedPosition = true; + return current === htmlElem ? null : htmlElem; + } + + var parent = getParentElement(current); + while (parent && !canBeOverflowed(parent, position)) { + parent = getParentElement(parent); + } + return parent; + } + + function getOverflowStyles(current: Element): OverflowStyles { + var overflowElem = current; + if (htmlOverflowStyle === 'visible') { + if (current === htmlElem && bodyElem) { + overflowElem = bodyElem; + } else if (current === bodyElem) { + return { x: 'visible', y: 'visible' }; + } + } + + var overflow = { + x: getEffectiveStyle(overflowElem, 'overflow-x') || 'visible', + y: getEffectiveStyle(overflowElem, 'overflow-y') || 'visible', + }; + + if (current === htmlElem) { + overflow.x = overflow.x === 'visible' ? 'auto' : overflow.x; + overflow.y = overflow.y === 'visible' ? 'auto' : overflow.y; + } + + return overflow; + } + + function getScroll(current: Element): Coordinate { + if (current === htmlElem) { + return { + x: ownerDoc.defaultView ? ownerDoc.defaultView.pageXOffset : 0, + y: ownerDoc.defaultView ? ownerDoc.defaultView.pageYOffset : 0, + }; + } + + return { x: current.scrollLeft, y: current.scrollTop }; + } + + for (var container = getOverflowParent(elem); container; container = getOverflowParent(container)) { + var containerOverflow = getOverflowStyles(container); + if (containerOverflow.x === 'visible' && containerOverflow.y === 'visible') { + continue; + } + + var containerRect = getClientRect(container); + if (containerRect.width === 0 || containerRect.height === 0) { + return 'hidden'; + } + + var underflowsX = region.right < containerRect.left; + var underflowsY = region.bottom < containerRect.top; + if ((underflowsX && containerOverflow.x === 'hidden') || (underflowsY && containerOverflow.y === 'hidden')) { + return 'hidden'; + } + + if ((underflowsX && containerOverflow.x !== 'visible') || (underflowsY && containerOverflow.y !== 'visible')) { + var containerScroll = getScroll(container); + var unscrollableX = region.right < containerRect.left - containerScroll.x; + var unscrollableY = region.bottom < containerRect.top - containerScroll.y; + if ((unscrollableX && containerOverflow.x !== 'visible') || (unscrollableY && containerOverflow.y !== 'visible')) { + return 'hidden'; + } + + var containerUnderflowState = getOverflowState(container); + return containerUnderflowState === 'hidden' ? 'hidden' : 'scroll'; + } + + var overflowsX = region.left >= containerRect.left + containerRect.width; + var overflowsY = region.top >= containerRect.top + containerRect.height; + if ((overflowsX && containerOverflow.x === 'hidden') || (overflowsY && containerOverflow.y === 'hidden')) { + return 'hidden'; + } + + if ((overflowsX && containerOverflow.x !== 'visible') || (overflowsY && containerOverflow.y !== 'visible')) { + if (treatAsFixedPosition) { + var docScroll = getScroll(container); + if (region.left >= htmlElem.scrollWidth - docScroll.x || region.right >= htmlElem.scrollHeight - docScroll.y) { + return 'hidden'; + } + } + + var containerOverflowState = getOverflowState(container); + return containerOverflowState === 'hidden' ? 'hidden' : 'scroll'; + } + } + + return 'none'; + } + + function isShownInternal(elem: Element, ignoreOpacity: boolean, displayedFn: (element: Node) => boolean): boolean { + if (!isElement(elem)) { + throw new Error('Argument to isShown must be of type Element'); + } + + if (isElement(elem, 'BODY')) { + return true; + } + + if (isElement(elem, 'OPTION') || isElement(elem, 'OPTGROUP')) { + var select = elem.closest('select'); + return !!select && isShownInternal(select, true, displayedFn); + } + + var imageMap = maybeFindImageMap(elem); + if (imageMap) { + return !!imageMap.image && imageMap.rect.width > 0 && imageMap.rect.height > 0 && + isShownInternal(imageMap.image, ignoreOpacity, displayedFn); + } + + if (isElement(elem, 'INPUT') && (elem as HTMLInputElement).type.toLowerCase() === 'hidden') { + return false; + } + + if (isElement(elem, 'NOSCRIPT')) { + return false; + } + + var visibility = getEffectiveStyle(elem, 'visibility'); + if (visibility === 'collapse' || visibility === 'hidden') { + return false; + } + + if (!displayedFn(elem)) { + return false; + } + + if (!ignoreOpacity && getOpacity(elem) === 0) { + return false; + } + + function positiveSize(element: Element): boolean { + var rect = getClientRect(element); + if (rect.height > 0 && rect.width > 0) { + return true; + } + + if (isElement(element, 'PATH') && (rect.height > 0 || rect.width > 0)) { + var strokeWidth = getEffectiveStyle(element, 'stroke-width'); + return !!strokeWidth && parseInt(strokeWidth, 10) > 0; + } + + var elementVisibility = getEffectiveStyle(element, 'visibility'); + if (elementVisibility === 'collapse' || elementVisibility === 'hidden') { + return false; + } + + if (!displayedFn(element)) { + return false; + } + + if (getEffectiveStyle(element, 'overflow') === 'hidden') { + return false; + } + + for (var index = 0; index < element.childNodes.length; index += 1) { + var child = element.childNodes[index]; + if (child.nodeType === Node.TEXT_NODE) { + var text = child.nodeValue || ''; + if (/^[\s]*$/.test(text) && /[\n\r\t]/.test(text)) { + continue; + } + return true; + } + + if (child instanceof Element && positiveSize(child)) { + return true; + } + } + + return false; + } + + if (!positiveSize(elem)) { + return false; + } + + function hiddenByOverflow(element: Element): boolean { + if (getOverflowState(element) !== 'hidden') { + return false; + } + + for (var index = 0; index < element.childNodes.length; index += 1) { + var child = element.childNodes[index]; + if (child instanceof Element && !hiddenByOverflow(child) && positiveSize(child)) { + return false; + } + } + + return true; + } + + return !hiddenByOverflow(elem); + } + + return function isShownElement(elem: Element, optIgnoreOpacity?: boolean): boolean { + function displayed(node: Node): boolean { + if (isElement(node)) { + var display = getEffectiveStyle(node, 'display'); + var contentVisibility = getEffectiveStyle(node, 'content-visibility'); + if (display === 'none' || contentVisibility === 'hidden') { + return false; + } + } + + var parent = getParentNodeInComposedDom(node); + if (typeof ShadowRoot === 'function' && parent instanceof ShadowRoot) { + if (parent.host.shadowRoot && parent.host.shadowRoot !== parent) { + return false; + } + parent = parent.host; + } + + if (parent && (parent.nodeType === Node.DOCUMENT_NODE || parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE)) { + return true; + } + + if (parent instanceof HTMLDetailsElement && !parent.open && !isElement(node, 'SUMMARY')) { + return false; + } + + return !!parent && displayed(parent); + } + + return isShownInternal(elem, !!optIgnoreOpacity, displayed); + }; +})(); + +export default isShown; \ No newline at end of file diff --git a/javascript/selenium-webdriver/lib/atoms/BUILD.bazel b/javascript/selenium-webdriver/lib/atoms/BUILD.bazel index 88404c5b13011..83f72f3ad4ef8 100644 --- a/javascript/selenium-webdriver/lib/atoms/BUILD.bazel +++ b/javascript/selenium-webdriver/lib/atoms/BUILD.bazel @@ -19,6 +19,18 @@ js_run_binary( visibility = ["//javascript/selenium-webdriver:__pkg__"], ) +js_run_binary( + name = "is_displayed_typescript", + srcs = ["//javascript/atoms/fragments:is-displayed-typescript"], + outs = ["is-displayed-typescript.js"], + args = [ + "$(rootpath //javascript/atoms/fragments:is-displayed-typescript)", + "$(rootpath :is-displayed-typescript.js)", + ], + tool = ":make_atoms_module", + visibility = ["//javascript/selenium-webdriver:__pkg__"], +) + js_run_binary( name = "get_attribute", srcs = ["//javascript/webdriver/atoms:get-attribute.js"], diff --git a/py/BUILD.bazel b/py/BUILD.bazel index 2f68f61b77021..7b97ed9f31eb4 100644 --- a/py/BUILD.bazel +++ b/py/BUILD.bazel @@ -123,7 +123,7 @@ copy_file( copy_file( name = "is-displayed", - src = "//javascript/atoms/fragments:is-displayed.js", + src = "//javascript/atoms/fragments:is-displayed-typescript.js", out = "selenium/webdriver/remote/isDisplayed.js", ) From 32342c31082e7a2c8966da159f1d2a7a406baf4e Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Wed, 8 Apr 2026 16:35:58 +0100 Subject: [PATCH 2/6] Use typescript output --- javascript/atoms/BUILD.bazel | 27 ++ javascript/atoms/fragments/BUILD.bazel | 2 +- javascript/atoms/typescript/is-displayed.js | 433 -------------------- javascript/atoms/typescript/is-displayed.ts | 15 +- 4 files changed, 35 insertions(+), 442 deletions(-) delete mode 100644 javascript/atoms/typescript/is-displayed.js diff --git a/javascript/atoms/BUILD.bazel b/javascript/atoms/BUILD.bazel index 934c89aa9ab6f..183c60fa30200 100644 --- a/javascript/atoms/BUILD.bazel +++ b/javascript/atoms/BUILD.bazel @@ -1,8 +1,35 @@ load("@rules_closure//closure:defs.bzl", "closure_js_library") +load("@aspect_rules_js//js:defs.bzl", "js_run_binary") load("//javascript:defs.bzl", "closure_js_deps", "closure_test_suite") package(default_visibility = ["//visibility:public"]) +js_run_binary( + name = "is-displayed-typescript-compiled", + srcs = ["typescript/is-displayed.ts"], + outs = ["typescript/is-displayed.compiled.js"], + args = [ + "--target", + "ES2017", + "--module", + "none", + "--removeComments", + "--pretty", + "false", + "--outFile", + "$(rootpath :typescript/is-displayed.compiled.js)", + "$(rootpath typescript/is-displayed.ts)", + ], + tool = "@npm_typescript//:tsc", +) + +genrule( + name = "is-displayed-typescript-generated", + srcs = [":is-displayed-typescript-compiled"], + outs = ["typescript/is-displayed.generated.js"], + cmd = "sed '$$ s/;[[:space:]]*$$//' $(location :is-displayed-typescript-compiled) > $@", +) + filegroup( name = "atoms", srcs = glob([ diff --git a/javascript/atoms/fragments/BUILD.bazel b/javascript/atoms/fragments/BUILD.bazel index 15aedb7b01f32..5c23b9db952c0 100644 --- a/javascript/atoms/fragments/BUILD.bazel +++ b/javascript/atoms/fragments/BUILD.bazel @@ -133,7 +133,7 @@ closure_fragment( copy_file( name = "is-displayed-typescript", - src = "//javascript/atoms:typescript/is-displayed.js", + src = "//javascript/atoms:typescript/is-displayed.generated.js", out = "is-displayed-typescript.js", visibility = [ "//dotnet/src/webdriver:__pkg__", diff --git a/javascript/atoms/typescript/is-displayed.js b/javascript/atoms/typescript/is-displayed.js deleted file mode 100644 index d22e911505dec..0000000000000 --- a/javascript/atoms/typescript/is-displayed.js +++ /dev/null @@ -1,433 +0,0 @@ -(function () { - function toUpperCaseTag(tagName) { - return tagName ? tagName.toUpperCase() : undefined; - } - - function isElement(node, tagName) { - if (!node || !(node instanceof Element)) { - return false; - } - var normalizedTagName = toUpperCaseTag(tagName); - if (node instanceof HTMLFormElement) { - return !normalizedTagName || normalizedTagName === 'FORM'; - } - return ( - typeof node.tagName === 'string' && - (!normalizedTagName || node.tagName.toUpperCase() === normalizedTagName) - ); - } - - function getParentElement(node) { - var current = node.parentNode; - while ( - current && - current.nodeType !== Node.ELEMENT_NODE && - current.nodeType !== Node.DOCUMENT_NODE && - current.nodeType !== Node.DOCUMENT_FRAGMENT_NODE - ) { - current = current.parentNode; - } - return current instanceof Element ? current : null; - } - - function getEffectiveStyle(elem, propertyName) { - var win = elem.ownerDocument.defaultView; - if (!win) { - return null; - } - var computed = win.getComputedStyle(elem); - if (!computed) { - return null; - } - return computed.getPropertyValue(propertyName) || null; - } - - function getOpacity(elem) { - var opacityStyle = getEffectiveStyle(elem, 'opacity'); - var opacity = opacityStyle ? Number(opacityStyle) : 1; - var parent = getParentElement(elem); - return parent ? opacity * getOpacity(parent) : opacity; - } - - function getParentNodeInComposedDom(node) { - var parent = node.parentNode; - if (parent && parent.shadowRoot && node.assignedSlot !== undefined) { - return node.assignedSlot ? node.assignedSlot.parentNode : null; - } - return parent; - } - - function createRect(left, top, width, height) { - return { - left: left, - top: top, - right: left + width, - bottom: top + height, - width: width, - height: height, - }; - } - - function getClientRect(elem) { - var imageMap = maybeFindImageMap(elem); - if (imageMap) { - return imageMap.rect; - } - - if (isElement(elem, 'HTML')) { - var doc = elem.ownerDocument; - return createRect(0, 0, doc.documentElement.clientWidth, doc.documentElement.clientHeight); - } - - try { - var nativeRect = elem.getBoundingClientRect(); - return { - left: nativeRect.left, - top: nativeRect.top, - right: nativeRect.right, - bottom: nativeRect.bottom, - width: nativeRect.right - nativeRect.left, - height: nativeRect.bottom - nativeRect.top, - }; - } catch (_error) { - return createRect(0, 0, 0, 0); - } - } - - function getAreaRelativeRect(area) { - var shape = area.shape.toLowerCase(); - var coords = area.coords.split(',').map(function (value) { - return Number(value.trim()); - }); - - if (shape === 'rect' && coords.length === 4) { - return createRect(coords[0], coords[1], coords[2] - coords[0], coords[3] - coords[1]); - } - - if (shape === 'circle' && coords.length === 3) { - return createRect(coords[0] - coords[2], coords[1] - coords[2], coords[2] * 2, coords[2] * 2); - } - - if (shape === 'poly' && coords.length > 2) { - var minX = coords[0]; - var minY = coords[1]; - var maxX = minX; - var maxY = minY; - - for (var index = 2; index + 1 < coords.length; index += 2) { - minX = Math.min(minX, coords[index]); - maxX = Math.max(maxX, coords[index]); - minY = Math.min(minY, coords[index + 1]); - maxY = Math.max(maxY, coords[index + 1]); - } - - return createRect(minX, minY, maxX - minX, maxY - minY); - } - - return createRect(0, 0, 0, 0); - } - - function findImageUsingMap(mapName, doc) { - var elements = doc.getElementsByTagName('*'); - for (var index = 0; index < elements.length; index += 1) { - var useMap = elements[index].getAttribute('usemap'); - if (useMap === '#' + mapName) { - return elements[index]; - } - } - return null; - } - - function maybeFindImageMap(elem) { - var isMap = isElement(elem, 'MAP'); - if (!isMap && !isElement(elem, 'AREA')) { - return null; - } - - var map = isMap ? elem : isElement(elem.parentNode, 'MAP') ? elem.parentNode : null; - var image = null; - var rect = createRect(0, 0, 0, 0); - - if (map instanceof HTMLMapElement && map.name) { - image = findImageUsingMap(map.name, map.ownerDocument); - if (image) { - rect = getClientRect(image); - if (!isMap && elem instanceof HTMLAreaElement && elem.shape.toLowerCase() !== 'default') { - var relativeRect = getAreaRelativeRect(elem); - var relativeX = Math.min(Math.max(relativeRect.left, 0), rect.width); - var relativeY = Math.min(Math.max(relativeRect.top, 0), rect.height); - var width = Math.min(relativeRect.width, rect.width - relativeX); - var height = Math.min(relativeRect.height, rect.height - relativeY); - rect = createRect(relativeX + rect.left, relativeY + rect.top, width, height); - } - } - } - - return { image: image, rect: rect }; - } - - function getClientRegion(elem) { - return getClientRect(elem); - } - - function getOverflowState(elem) { - var region = getClientRegion(elem); - var ownerDoc = elem.ownerDocument; - var htmlElem = ownerDoc.documentElement; - var bodyElem = ownerDoc.body; - var htmlOverflowStyle = getEffectiveStyle(htmlElem, 'overflow') || 'visible'; - var treatAsFixedPosition = false; - - function canBeOverflowed(container, position) { - if (container === htmlElem) { - return true; - } - - var display = getEffectiveStyle(container, 'display') || ''; - if (display.indexOf('inline') === 0 || display === 'contents') { - return false; - } - - if (position === 'absolute' && getEffectiveStyle(container, 'position') === 'static') { - return false; - } - - return true; - } - - function getOverflowParent(current) { - var position = getEffectiveStyle(current, 'position'); - if (position === 'fixed') { - treatAsFixedPosition = true; - return current === htmlElem ? null : htmlElem; - } - - var parent = getParentElement(current); - while (parent && !canBeOverflowed(parent, position)) { - parent = getParentElement(parent); - } - return parent; - } - - function getOverflowStyles(current) { - var overflowElem = current; - if (htmlOverflowStyle === 'visible') { - if (current === htmlElem && bodyElem) { - overflowElem = bodyElem; - } else if (current === bodyElem) { - return { x: 'visible', y: 'visible' }; - } - } - - var overflow = { - x: getEffectiveStyle(overflowElem, 'overflow-x') || 'visible', - y: getEffectiveStyle(overflowElem, 'overflow-y') || 'visible', - }; - - if (current === htmlElem) { - overflow.x = overflow.x === 'visible' ? 'auto' : overflow.x; - overflow.y = overflow.y === 'visible' ? 'auto' : overflow.y; - } - - return overflow; - } - - function getScroll(current) { - if (current === htmlElem) { - return { - x: ownerDoc.defaultView ? ownerDoc.defaultView.pageXOffset : 0, - y: ownerDoc.defaultView ? ownerDoc.defaultView.pageYOffset : 0, - }; - } - - return { x: current.scrollLeft, y: current.scrollTop }; - } - - for (var container = getOverflowParent(elem); container; container = getOverflowParent(container)) { - var containerOverflow = getOverflowStyles(container); - if (containerOverflow.x === 'visible' && containerOverflow.y === 'visible') { - continue; - } - - var containerRect = getClientRect(container); - if (containerRect.width === 0 || containerRect.height === 0) { - return 'hidden'; - } - - var underflowsX = region.right < containerRect.left; - var underflowsY = region.bottom < containerRect.top; - if ((underflowsX && containerOverflow.x === 'hidden') || (underflowsY && containerOverflow.y === 'hidden')) { - return 'hidden'; - } - - if ((underflowsX && containerOverflow.x !== 'visible') || (underflowsY && containerOverflow.y !== 'visible')) { - var containerScroll = getScroll(container); - var unscrollableX = region.right < containerRect.left - containerScroll.x; - var unscrollableY = region.bottom < containerRect.top - containerScroll.y; - if ((unscrollableX && containerOverflow.x !== 'visible') || (unscrollableY && containerOverflow.y !== 'visible')) { - return 'hidden'; - } - - var containerUnderflowState = getOverflowState(container); - return containerUnderflowState === 'hidden' ? 'hidden' : 'scroll'; - } - - var overflowsX = region.left >= containerRect.left + containerRect.width; - var overflowsY = region.top >= containerRect.top + containerRect.height; - if ((overflowsX && containerOverflow.x === 'hidden') || (overflowsY && containerOverflow.y === 'hidden')) { - return 'hidden'; - } - - if ((overflowsX && containerOverflow.x !== 'visible') || (overflowsY && containerOverflow.y !== 'visible')) { - if (treatAsFixedPosition) { - var docScroll = getScroll(container); - if (region.left >= htmlElem.scrollWidth - docScroll.x || region.right >= htmlElem.scrollHeight - docScroll.y) { - return 'hidden'; - } - } - - var containerOverflowState = getOverflowState(container); - return containerOverflowState === 'hidden' ? 'hidden' : 'scroll'; - } - } - - return 'none'; - } - - function isShownInternal(elem, ignoreOpacity, displayedFn) { - if (!isElement(elem)) { - throw new Error('Argument to isShown must be of type Element'); - } - - if (isElement(elem, 'BODY')) { - return true; - } - - if (isElement(elem, 'OPTION') || isElement(elem, 'OPTGROUP')) { - var select = elem.closest('select'); - return !!select && isShownInternal(select, true, displayedFn); - } - - var imageMap = maybeFindImageMap(elem); - if (imageMap) { - return !!imageMap.image && imageMap.rect.width > 0 && imageMap.rect.height > 0 && - isShownInternal(imageMap.image, ignoreOpacity, displayedFn); - } - - if (isElement(elem, 'INPUT') && elem.type.toLowerCase() === 'hidden') { - return false; - } - - if (isElement(elem, 'NOSCRIPT')) { - return false; - } - - var visibility = getEffectiveStyle(elem, 'visibility'); - if (visibility === 'collapse' || visibility === 'hidden') { - return false; - } - - if (!displayedFn(elem)) { - return false; - } - - if (!ignoreOpacity && getOpacity(elem) === 0) { - return false; - } - - function positiveSize(element) { - var rect = getClientRect(element); - if (rect.height > 0 && rect.width > 0) { - return true; - } - - if (isElement(element, 'PATH') && (rect.height > 0 || rect.width > 0)) { - var strokeWidth = getEffectiveStyle(element, 'stroke-width'); - return !!strokeWidth && parseInt(strokeWidth, 10) > 0; - } - - var elementVisibility = getEffectiveStyle(element, 'visibility'); - if (elementVisibility === 'collapse' || elementVisibility === 'hidden') { - return false; - } - - if (!displayedFn(element)) { - return false; - } - - if (getEffectiveStyle(element, 'overflow') === 'hidden') { - return false; - } - - for (var index = 0; index < element.childNodes.length; index += 1) { - var child = element.childNodes[index]; - if (child.nodeType === Node.TEXT_NODE) { - var text = child.nodeValue || ''; - if (/^[\s]*$/.test(text) && /[\n\r\t]/.test(text)) { - continue; - } - return true; - } - - if (child instanceof Element && positiveSize(child)) { - return true; - } - } - - return false; - } - - if (!positiveSize(elem)) { - return false; - } - - function hiddenByOverflow(element) { - if (getOverflowState(element) !== 'hidden') { - return false; - } - - for (var index = 0; index < element.childNodes.length; index += 1) { - var child = element.childNodes[index]; - if (child instanceof Element && !hiddenByOverflow(child) && positiveSize(child)) { - return false; - } - } - - return true; - } - - return !hiddenByOverflow(elem); - } - - return function (elem, optIgnoreOpacity) { - function displayed(node) { - if (isElement(node)) { - var display = getEffectiveStyle(node, 'display'); - var contentVisibility = getEffectiveStyle(node, 'content-visibility'); - if (display === 'none' || contentVisibility === 'hidden') { - return false; - } - } - - var parent = getParentNodeInComposedDom(node); - if (typeof ShadowRoot === 'function' && parent instanceof ShadowRoot) { - if (parent.host.shadowRoot && parent.host.shadowRoot !== parent) { - return false; - } - parent = parent.host; - } - - if (parent && (parent.nodeType === Node.DOCUMENT_NODE || parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE)) { - return true; - } - - if (parent instanceof HTMLDetailsElement && !parent.open && !isElement(node, 'SUMMARY')) { - return false; - } - - return !!parent && displayed(parent); - } - - return isShownInternal(elem, !!optIgnoreOpacity, displayed); - }; -})() \ No newline at end of file diff --git a/javascript/atoms/typescript/is-displayed.ts b/javascript/atoms/typescript/is-displayed.ts index 756a487f02a56..e55e27aca14fc 100644 --- a/javascript/atoms/typescript/is-displayed.ts +++ b/javascript/atoms/typescript/is-displayed.ts @@ -24,7 +24,7 @@ interface Coordinate { y: number; } -var isShown = (function (): (elem: Element, optIgnoreOpacity?: boolean) => boolean { +(function (): (elem: Element, optIgnoreOpacity?: boolean) => boolean { function toUpperCaseTag(tagName?: string): string | undefined { return tagName ? tagName.toUpperCase() : undefined; } @@ -105,13 +105,14 @@ var isShown = (function (): (elem: Element, optIgnoreOpacity?: boolean) => boole return imageMap.rect; } - if (isElement(elem, 'HTML')) { - var doc = elem.ownerDocument; + var elemTagName = typeof (elem as Element).tagName === 'string' ? (elem as Element).tagName : ''; + if (elemTagName.toUpperCase() === 'HTML') { + var doc = (elem as Element).ownerDocument; return createRect(0, 0, doc.documentElement.clientWidth, doc.documentElement.clientHeight); } try { - var nativeRect = elem.getBoundingClientRect(); + var nativeRect = (elem as Element).getBoundingClientRect(); return { left: nativeRect.left, top: nativeRect.top, @@ -335,7 +336,7 @@ var isShown = (function (): (elem: Element, optIgnoreOpacity?: boolean) => boole } if (isElement(elem, 'OPTION') || isElement(elem, 'OPTGROUP')) { - var select = elem.closest('select'); + var select = (elem as Element).closest('select'); return !!select && isShownInternal(select, true, displayedFn); } @@ -461,6 +462,4 @@ var isShown = (function (): (elem: Element, optIgnoreOpacity?: boolean) => boole return isShownInternal(elem, !!optIgnoreOpacity, displayed); }; -})(); - -export default isShown; \ No newline at end of file +})(); \ No newline at end of file From 4bd8a50e3c0d90e4a61e0588f0e3d6770419d062 Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Wed, 8 Apr 2026 16:50:36 +0100 Subject: [PATCH 3/6] move test to not have closure --- .../atoms/test/shown_typescript_test.html | 388 +++++++----------- 1 file changed, 154 insertions(+), 234 deletions(-) diff --git a/javascript/atoms/test/shown_typescript_test.html b/javascript/atoms/test/shown_typescript_test.html index 8a2040a8353dc..d0f11e5a3c412 100644 --- a/javascript/atoms/test/shown_typescript_test.html +++ b/javascript/atoms/test/shown_typescript_test.html @@ -10,45 +10,35 @@ + var isShown = window.bot.dom.typescript.isShown; - - From 970bd0a4c8a656d20cdf34599444518f123ad8b5 Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Thu, 9 Apr 2026 11:58:27 +0100 Subject: [PATCH 4/6] formatting --- javascript/atoms/BUILD.bazel | 2 +- .../atoms/typescript/is-displayed-global.js | 20 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/javascript/atoms/BUILD.bazel b/javascript/atoms/BUILD.bazel index 183c60fa30200..345acfa29efd1 100644 --- a/javascript/atoms/BUILD.bazel +++ b/javascript/atoms/BUILD.bazel @@ -1,5 +1,5 @@ -load("@rules_closure//closure:defs.bzl", "closure_js_library") load("@aspect_rules_js//js:defs.bzl", "js_run_binary") +load("@rules_closure//closure:defs.bzl", "closure_js_library") load("//javascript:defs.bzl", "closure_js_deps", "closure_test_suite") package(default_visibility = ["//visibility:public"]) diff --git a/javascript/atoms/typescript/is-displayed-global.js b/javascript/atoms/typescript/is-displayed-global.js index b85a686405dd4..b3aa1b22fa09f 100644 --- a/javascript/atoms/typescript/is-displayed-global.js +++ b/javascript/atoms/typescript/is-displayed-global.js @@ -1,3 +1,21 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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. + + window.bot = window.bot || {}; window.bot.dom = window.bot.dom || {}; window.bot.dom.typescript = window.bot.dom.typescript || {}; @@ -434,4 +452,4 @@ window.bot.dom.typescript.isShown = (function () { return isShownInternal(elem, !!optIgnoreOpacity, displayed); }; -})(); \ No newline at end of file +})(); From c45daee94440f3912e9c65f4d727723f112716e5 Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Fri, 10 Apr 2026 11:13:14 +0100 Subject: [PATCH 5/6] Handle comments --- javascript/atoms/BUILD.bazel | 16 +++++++++-- javascript/atoms/typescript/is-displayed.ts | 17 +++++++++++ .../typescript/strip-trailing-semicolon.js | 28 +++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 javascript/atoms/typescript/strip-trailing-semicolon.js diff --git a/javascript/atoms/BUILD.bazel b/javascript/atoms/BUILD.bazel index 345acfa29efd1..03aeb0cf8e677 100644 --- a/javascript/atoms/BUILD.bazel +++ b/javascript/atoms/BUILD.bazel @@ -1,9 +1,15 @@ -load("@aspect_rules_js//js:defs.bzl", "js_run_binary") +load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_run_binary") load("@rules_closure//closure:defs.bzl", "closure_js_library") load("//javascript:defs.bzl", "closure_js_deps", "closure_test_suite") package(default_visibility = ["//visibility:public"]) +js_binary( + name = "strip_trailing_semicolon", + data = ["typescript/strip-trailing-semicolon.js"], + entry_point = ":typescript/strip-trailing-semicolon.js", +) + js_run_binary( name = "is-displayed-typescript-compiled", srcs = ["typescript/is-displayed.ts"], @@ -23,11 +29,15 @@ js_run_binary( tool = "@npm_typescript//:tsc", ) -genrule( +js_run_binary( name = "is-displayed-typescript-generated", srcs = [":is-displayed-typescript-compiled"], outs = ["typescript/is-displayed.generated.js"], - cmd = "sed '$$ s/;[[:space:]]*$$//' $(location :is-displayed-typescript-compiled) > $@", + args = [ + "$(rootpath :is-displayed-typescript-compiled)", + "$(rootpath :typescript/is-displayed.generated.js)", + ], + tool = ":strip_trailing_semicolon", ) filegroup( diff --git a/javascript/atoms/typescript/is-displayed.ts b/javascript/atoms/typescript/is-displayed.ts index e55e27aca14fc..422a6742ff90a 100644 --- a/javascript/atoms/typescript/is-displayed.ts +++ b/javascript/atoms/typescript/is-displayed.ts @@ -1,3 +1,20 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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. + type OverflowState = 'none' | 'hidden' | 'scroll'; interface Rect { diff --git a/javascript/atoms/typescript/strip-trailing-semicolon.js b/javascript/atoms/typescript/strip-trailing-semicolon.js new file mode 100644 index 0000000000000..249b06b4ef074 --- /dev/null +++ b/javascript/atoms/typescript/strip-trailing-semicolon.js @@ -0,0 +1,28 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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. + +const fs = require('node:fs'); + +const [inputPath, outputPath] = process.argv.slice(2); + +if (!inputPath || !outputPath) { + throw new Error('Expected input and output file paths'); +} + +const input = fs.readFileSync(inputPath, 'utf8'); +const output = input.replace(/;\s*$/, ''); +fs.writeFileSync(outputPath, output); From 6bcf634352ae10eef5595a7465225a5563992ba4 Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Mon, 20 Apr 2026 10:54:40 +0100 Subject: [PATCH 6/6] [javascript] Generate is-displayed-global.js from TypeScript source via Bazel - Fix invalid Bazel label in js_binary: entry_point must not use the ':' target prefix when the path contains '/'. Changed ':typescript/strip-trailing-semicolon.js' to the file-path form 'typescript/strip-trailing-semicolon.js'. - Add wrap-as-global.js Node script that wraps the compiled TypeScript atom output with the window.bot.dom.typescript.isShown global assignment (plus license header), mirroring the pattern of strip-trailing-semicolon.js. - Add 'wrap_as_global' js_binary and 'is-displayed-global' js_run_binary rules so is-displayed-global.js is generated from the same TypeScript source rather than being a separate hand-maintained copy that could drift. - Remove the hand-maintained javascript/atoms/typescript/is-displayed-global.js; it is now produced by the new Bazel rule. Fixes review comments in PR #17316. --- javascript/atoms/BUILD.bazel | 21 +- .../atoms/typescript/is-displayed-global.js | 455 ------------------ javascript/atoms/typescript/wrap-as-global.js | 66 +++ 3 files changed, 85 insertions(+), 457 deletions(-) delete mode 100644 javascript/atoms/typescript/is-displayed-global.js create mode 100644 javascript/atoms/typescript/wrap-as-global.js diff --git a/javascript/atoms/BUILD.bazel b/javascript/atoms/BUILD.bazel index 03aeb0cf8e677..d780f4b84e44c 100644 --- a/javascript/atoms/BUILD.bazel +++ b/javascript/atoms/BUILD.bazel @@ -7,7 +7,13 @@ package(default_visibility = ["//visibility:public"]) js_binary( name = "strip_trailing_semicolon", data = ["typescript/strip-trailing-semicolon.js"], - entry_point = ":typescript/strip-trailing-semicolon.js", + entry_point = "typescript/strip-trailing-semicolon.js", +) + +js_binary( + name = "wrap_as_global", + data = ["typescript/wrap-as-global.js"], + entry_point = "typescript/wrap-as-global.js", ) js_run_binary( @@ -40,6 +46,17 @@ js_run_binary( tool = ":strip_trailing_semicolon", ) +js_run_binary( + name = "is-displayed-global", + srcs = [":is-displayed-typescript-compiled"], + outs = ["typescript/is-displayed-global.js"], + args = [ + "$(rootpath :is-displayed-typescript-compiled)", + "$(rootpath :typescript/is-displayed-global.js)", + ], + tool = ":wrap_as_global", +) + filegroup( name = "atoms", srcs = glob([ @@ -49,7 +66,7 @@ filegroup( "**/*.png", "**/*.svg", "**/*.ts", - ]), + ]) + [":is-displayed-global"], visibility = [ "//dotnet/test:__subpackages__", "//java/test/org/openqa/selenium/environment:__pkg__", diff --git a/javascript/atoms/typescript/is-displayed-global.js b/javascript/atoms/typescript/is-displayed-global.js deleted file mode 100644 index b3aa1b22fa09f..0000000000000 --- a/javascript/atoms/typescript/is-displayed-global.js +++ /dev/null @@ -1,455 +0,0 @@ -// Licensed to the Software Freedom Conservancy (SFC) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The SFC licenses this file -// to you 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. - - -window.bot = window.bot || {}; -window.bot.dom = window.bot.dom || {}; -window.bot.dom.typescript = window.bot.dom.typescript || {}; -window.bot.dom.typescript.IS_SHADOW_DOM_ENABLED = typeof ShadowRoot === 'function'; -window.bot.dom.typescript.isShown = (function () { - function toUpperCaseTag(tagName) { - return tagName ? tagName.toUpperCase() : undefined; - } - - function isElement(node, tagName) { - if (!node || !(node instanceof Element)) { - return false; - } - var normalizedTagName = toUpperCaseTag(tagName); - if (node instanceof HTMLFormElement) { - return !normalizedTagName || normalizedTagName === 'FORM'; - } - return ( - typeof node.tagName === 'string' && - (!normalizedTagName || node.tagName.toUpperCase() === normalizedTagName) - ); - } - - function getParentElement(node) { - var current = node.parentNode; - while ( - current && - current.nodeType !== Node.ELEMENT_NODE && - current.nodeType !== Node.DOCUMENT_NODE && - current.nodeType !== Node.DOCUMENT_FRAGMENT_NODE - ) { - current = current.parentNode; - } - return current instanceof Element ? current : null; - } - - function getEffectiveStyle(elem, propertyName) { - var win = elem.ownerDocument.defaultView; - if (!win) { - return null; - } - var computed = win.getComputedStyle(elem); - if (!computed) { - return null; - } - return computed.getPropertyValue(propertyName) || null; - } - - function getOpacity(elem) { - var opacityStyle = getEffectiveStyle(elem, 'opacity'); - var opacity = opacityStyle ? Number(opacityStyle) : 1; - var parent = getParentElement(elem); - return parent ? opacity * getOpacity(parent) : opacity; - } - - function getParentNodeInComposedDom(node) { - var parent = node.parentNode; - if (parent && parent.shadowRoot && node.assignedSlot !== undefined) { - return node.assignedSlot ? node.assignedSlot.parentNode : null; - } - return parent; - } - - function createRect(left, top, width, height) { - return { - left: left, - top: top, - right: left + width, - bottom: top + height, - width: width, - height: height, - }; - } - - function getClientRect(elem) { - var imageMap = maybeFindImageMap(elem); - if (imageMap) { - return imageMap.rect; - } - - if (isElement(elem, 'HTML')) { - var doc = elem.ownerDocument; - return createRect(0, 0, doc.documentElement.clientWidth, doc.documentElement.clientHeight); - } - - try { - var nativeRect = elem.getBoundingClientRect(); - return { - left: nativeRect.left, - top: nativeRect.top, - right: nativeRect.right, - bottom: nativeRect.bottom, - width: nativeRect.right - nativeRect.left, - height: nativeRect.bottom - nativeRect.top, - }; - } catch (_error) { - return createRect(0, 0, 0, 0); - } - } - - function getAreaRelativeRect(area) { - var shape = area.shape.toLowerCase(); - var coords = area.coords.split(',').map(function (value) { - return Number(value.trim()); - }); - - if (shape === 'rect' && coords.length === 4) { - return createRect(coords[0], coords[1], coords[2] - coords[0], coords[3] - coords[1]); - } - - if (shape === 'circle' && coords.length === 3) { - return createRect(coords[0] - coords[2], coords[1] - coords[2], coords[2] * 2, coords[2] * 2); - } - - if (shape === 'poly' && coords.length > 2) { - var minX = coords[0]; - var minY = coords[1]; - var maxX = minX; - var maxY = minY; - - for (var index = 2; index + 1 < coords.length; index += 2) { - minX = Math.min(minX, coords[index]); - maxX = Math.max(maxX, coords[index]); - minY = Math.min(minY, coords[index + 1]); - maxY = Math.max(maxY, coords[index + 1]); - } - - return createRect(minX, minY, maxX - minX, maxY - minY); - } - - return createRect(0, 0, 0, 0); - } - - function findImageUsingMap(mapName, doc) { - var elements = doc.getElementsByTagName('*'); - for (var index = 0; index < elements.length; index += 1) { - var useMap = elements[index].getAttribute('usemap'); - if (useMap === '#' + mapName) { - return elements[index]; - } - } - return null; - } - - function maybeFindImageMap(elem) { - var isMap = isElement(elem, 'MAP'); - if (!isMap && !isElement(elem, 'AREA')) { - return null; - } - - var map = isMap ? elem : isElement(elem.parentNode, 'MAP') ? elem.parentNode : null; - var image = null; - var rect = createRect(0, 0, 0, 0); - - if (map instanceof HTMLMapElement && map.name) { - image = findImageUsingMap(map.name, map.ownerDocument); - if (image) { - rect = getClientRect(image); - if (!isMap && elem instanceof HTMLAreaElement && elem.shape.toLowerCase() !== 'default') { - var relativeRect = getAreaRelativeRect(elem); - var relativeX = Math.min(Math.max(relativeRect.left, 0), rect.width); - var relativeY = Math.min(Math.max(relativeRect.top, 0), rect.height); - var width = Math.min(relativeRect.width, rect.width - relativeX); - var height = Math.min(relativeRect.height, rect.height - relativeY); - rect = createRect(relativeX + rect.left, relativeY + rect.top, width, height); - } - } - } - - return { image: image, rect: rect }; - } - - function getClientRegion(elem) { - return getClientRect(elem); - } - - function getOverflowState(elem) { - var region = getClientRegion(elem); - var ownerDoc = elem.ownerDocument; - var htmlElem = ownerDoc.documentElement; - var bodyElem = ownerDoc.body; - var htmlOverflowStyle = getEffectiveStyle(htmlElem, 'overflow') || 'visible'; - var treatAsFixedPosition = false; - - function canBeOverflowed(container, position) { - if (container === htmlElem) { - return true; - } - - var display = getEffectiveStyle(container, 'display') || ''; - if (display.indexOf('inline') === 0 || display === 'contents') { - return false; - } - - if (position === 'absolute' && getEffectiveStyle(container, 'position') === 'static') { - return false; - } - - return true; - } - - function getOverflowParent(current) { - var position = getEffectiveStyle(current, 'position'); - if (position === 'fixed') { - treatAsFixedPosition = true; - return current === htmlElem ? null : htmlElem; - } - - var parent = getParentElement(current); - while (parent && !canBeOverflowed(parent, position)) { - parent = getParentElement(parent); - } - return parent; - } - - function getOverflowStyles(current) { - var overflowElem = current; - if (htmlOverflowStyle === 'visible') { - if (current === htmlElem && bodyElem) { - overflowElem = bodyElem; - } else if (current === bodyElem) { - return { x: 'visible', y: 'visible' }; - } - } - - var overflow = { - x: getEffectiveStyle(overflowElem, 'overflow-x') || 'visible', - y: getEffectiveStyle(overflowElem, 'overflow-y') || 'visible', - }; - - if (current === htmlElem) { - overflow.x = overflow.x === 'visible' ? 'auto' : overflow.x; - overflow.y = overflow.y === 'visible' ? 'auto' : overflow.y; - } - - return overflow; - } - - function getScroll(current) { - if (current === htmlElem) { - return { - x: ownerDoc.defaultView ? ownerDoc.defaultView.pageXOffset : 0, - y: ownerDoc.defaultView ? ownerDoc.defaultView.pageYOffset : 0, - }; - } - - return { x: current.scrollLeft, y: current.scrollTop }; - } - - for (var container = getOverflowParent(elem); container; container = getOverflowParent(container)) { - var containerOverflow = getOverflowStyles(container); - if (containerOverflow.x === 'visible' && containerOverflow.y === 'visible') { - continue; - } - - var containerRect = getClientRect(container); - if (containerRect.width === 0 || containerRect.height === 0) { - return 'hidden'; - } - - var underflowsX = region.right < containerRect.left; - var underflowsY = region.bottom < containerRect.top; - if ((underflowsX && containerOverflow.x === 'hidden') || (underflowsY && containerOverflow.y === 'hidden')) { - return 'hidden'; - } - - if ((underflowsX && containerOverflow.x !== 'visible') || (underflowsY && containerOverflow.y !== 'visible')) { - var containerScroll = getScroll(container); - var unscrollableX = region.right < containerRect.left - containerScroll.x; - var unscrollableY = region.bottom < containerRect.top - containerScroll.y; - if ((unscrollableX && containerOverflow.x !== 'visible') || (unscrollableY && containerOverflow.y !== 'visible')) { - return 'hidden'; - } - - var containerUnderflowState = getOverflowState(container); - return containerUnderflowState === 'hidden' ? 'hidden' : 'scroll'; - } - - var overflowsX = region.left >= containerRect.left + containerRect.width; - var overflowsY = region.top >= containerRect.top + containerRect.height; - if ((overflowsX && containerOverflow.x === 'hidden') || (overflowsY && containerOverflow.y === 'hidden')) { - return 'hidden'; - } - - if ((overflowsX && containerOverflow.x !== 'visible') || (overflowsY && containerOverflow.y !== 'visible')) { - if (treatAsFixedPosition) { - var docScroll = getScroll(container); - if (region.left >= htmlElem.scrollWidth - docScroll.x || region.right >= htmlElem.scrollHeight - docScroll.y) { - return 'hidden'; - } - } - - var containerOverflowState = getOverflowState(container); - return containerOverflowState === 'hidden' ? 'hidden' : 'scroll'; - } - } - - return 'none'; - } - - function isShownInternal(elem, ignoreOpacity, displayedFn) { - if (!isElement(elem)) { - throw new Error('Argument to isShown must be of type Element'); - } - - if (isElement(elem, 'BODY')) { - return true; - } - - if (isElement(elem, 'OPTION') || isElement(elem, 'OPTGROUP')) { - var select = elem.closest('select'); - return !!select && isShownInternal(select, true, displayedFn); - } - - var imageMap = maybeFindImageMap(elem); - if (imageMap) { - return !!imageMap.image && imageMap.rect.width > 0 && imageMap.rect.height > 0 && - isShownInternal(imageMap.image, ignoreOpacity, displayedFn); - } - - if (isElement(elem, 'INPUT') && elem.type.toLowerCase() === 'hidden') { - return false; - } - - if (isElement(elem, 'NOSCRIPT')) { - return false; - } - - var visibility = getEffectiveStyle(elem, 'visibility'); - if (visibility === 'collapse' || visibility === 'hidden') { - return false; - } - - if (!displayedFn(elem)) { - return false; - } - - if (!ignoreOpacity && getOpacity(elem) === 0) { - return false; - } - - function positiveSize(element) { - var rect = getClientRect(element); - if (rect.height > 0 && rect.width > 0) { - return true; - } - - if (isElement(element, 'PATH') && (rect.height > 0 || rect.width > 0)) { - var strokeWidth = getEffectiveStyle(element, 'stroke-width'); - return !!strokeWidth && parseInt(strokeWidth, 10) > 0; - } - - var elementVisibility = getEffectiveStyle(element, 'visibility'); - if (elementVisibility === 'collapse' || elementVisibility === 'hidden') { - return false; - } - - if (!displayedFn(element)) { - return false; - } - - if (getEffectiveStyle(element, 'overflow') === 'hidden') { - return false; - } - - for (var index = 0; index < element.childNodes.length; index += 1) { - var child = element.childNodes[index]; - if (child.nodeType === Node.TEXT_NODE) { - var text = child.nodeValue || ''; - if (/^[\s]*$/.test(text) && /[\n\r\t]/.test(text)) { - continue; - } - return true; - } - - if (child instanceof Element && positiveSize(child)) { - return true; - } - } - - return false; - } - - if (!positiveSize(elem)) { - return false; - } - - function hiddenByOverflow(element) { - if (getOverflowState(element) !== 'hidden') { - return false; - } - - for (var index = 0; index < element.childNodes.length; index += 1) { - var child = element.childNodes[index]; - if (child instanceof Element && !hiddenByOverflow(child) && positiveSize(child)) { - return false; - } - } - - return true; - } - - return !hiddenByOverflow(elem); - } - - return function (elem, optIgnoreOpacity) { - function displayed(node) { - if (isElement(node)) { - var display = getEffectiveStyle(node, 'display'); - var contentVisibility = getEffectiveStyle(node, 'content-visibility'); - if (display === 'none' || contentVisibility === 'hidden') { - return false; - } - } - - var parent = getParentNodeInComposedDom(node); - if (typeof ShadowRoot === 'function' && parent instanceof ShadowRoot) { - if (parent.host.shadowRoot && parent.host.shadowRoot !== parent) { - return false; - } - parent = parent.host; - } - - if (parent && (parent.nodeType === Node.DOCUMENT_NODE || parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE)) { - return true; - } - - if (parent instanceof HTMLDetailsElement && !parent.open && !isElement(node, 'SUMMARY')) { - return false; - } - - return !!parent && displayed(parent); - } - - return isShownInternal(elem, !!optIgnoreOpacity, displayed); - }; -})(); diff --git a/javascript/atoms/typescript/wrap-as-global.js b/javascript/atoms/typescript/wrap-as-global.js new file mode 100644 index 0000000000000..a176c562b1cc9 --- /dev/null +++ b/javascript/atoms/typescript/wrap-as-global.js @@ -0,0 +1,66 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you 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. + +// Wraps the compiled isShown TypeScript output as a browser global so that +// test HTML pages can load it directly via