Commit 683abde9 authored by Andrew Nicols's avatar Andrew Nicols Committed by Chromium LUCI CQ

[chromedriver] Detect detached elements when determining location

When an element is no longer attached to the page's DOM a
StaleElementException should be thrown per the JsonWire specification.

R=johnchen@chromium.org, shengfa@google.com

Bug: chromedriver:3675

Change-Id: I96e300388737ab62cebe07ac851254059ccaef13
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2596290Reviewed-by: default avatarShengfa Lin <shengfa@google.com>
Reviewed-by: default avatarJohn Chen <johnchen@chromium.org>
Commit-Queue: Shengfa Lin <shengfa@google.com>
Auto-Submit: Andrew Nicols <andrewrn@gmail.com>
Cr-Commit-Position: refs/heads/master@{#838759}
parent 090e13aa
...@@ -2,6 +2,52 @@ ...@@ -2,6 +2,52 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
/**
* Constructs new error to be thrown with given code and message.
*
* @param {string} message Message reported to user.
* @param {StatusCode} code StatusCode for error.
* @return {!Error} Error object that can be thrown.
*/
function newError(message, code) {
const error = new Error(message);
error.code = code;
return error;
}
/**
* Get the root node for the given element, jumping up through any ShadowRoots
* if they are found.
*
* @param {Node} node The node to find the root of
* @return {Node} The root node
*/
function getNodeRootThroughAnyShadows(node) {
// Fetch the root node for the current node.
let root = node.getRootNode()
// Keep jumping to the root node for the attachment host of any ShadowRoot.
while (root.host) {
root = root.host.getRootNode()
}
return root;
}
/**
* Check whether the specified node is attached to the DOM, either directly or
* via any attached ShadowRoot.
*
* @param {Node} node The node to test
* @return {boolean} Whether the node is attached to the DOM.
*/
function isNodeReachable(node) {
const nodeRoot = getNodeRootThroughAnyShadows(node);
// Check whether the root is the Document or Proxy node.
return (nodeRoot == document.documentElement.parentNode);
}
function getFirstNonZeroWidthHeightRect(rects) { function getFirstNonZeroWidthHeightRect(rects) {
for (const rect of rects) { for (const rect of rects) {
if (rect.height > 0 && rect.width > 0) { if (rect.height > 0 && rect.width > 0) {
...@@ -71,7 +117,8 @@ function getInViewPoint(element) { ...@@ -71,7 +117,8 @@ function getInViewPoint(element) {
function inView(element) { function inView(element) {
var elementPoint = getInViewPoint(element); var elementPoint = getInViewPoint(element);
if (elementPoint[0] <= 0 || elementPoint[1] <= 0 || if (!elementPoint ||
elementPoint[0] <= 0 || elementPoint[1] <= 0 ||
elementPoint[0] >= window.innerWidth || elementPoint[0] >= window.innerWidth ||
elementPoint[1] >= window.innerHeight || elementPoint[1] >= window.innerHeight ||
!document.elementsFromPoint(elementPoint[0], elementPoint[1]) !document.elementsFromPoint(elementPoint[0], elementPoint[1])
...@@ -87,6 +134,11 @@ function getElementLocation(element, center) { ...@@ -87,6 +134,11 @@ function getElementLocation(element, center) {
if (element.nodeType != 1) if (element.nodeType != 1)
throw new Error(element + ' is not an element'); throw new Error(element + ' is not an element');
if (!isNodeReachable(element)) {
// errorCode 10: StaleElementException
throw newError('element is not attached to the page document', 10);
}
if (!inView(element)) { if (!inView(element)) {
element.scrollIntoView({behavior: "instant", element.scrollIntoView({behavior: "instant",
block: "end", block: "end",
...@@ -95,10 +147,8 @@ function getElementLocation(element, center) { ...@@ -95,10 +147,8 @@ function getElementLocation(element, center) {
var clientRects = element.getClientRects(); var clientRects = element.getClientRects();
if (clientRects.length === 0) { if (clientRects.length === 0) {
var e = new Error(element + ' has no size and location');
// errorCode 60: ElementNotInteractableException // errorCode 60: ElementNotInteractableException
e.code = 60; throw newError(element + ' has no size and location', 60);
throw e;
} }
var elementPoint = getInViewPoint(element); var elementPoint = getInViewPoint(element);
......
...@@ -83,6 +83,17 @@ function testElementChildCenterTopLeftLocation() { ...@@ -83,6 +83,17 @@ function testElementChildCenterTopLeftLocation() {
assertEquals(Math.floor(y), Math.floor(location.y)); assertEquals(Math.floor(y), Math.floor(location.y));
} }
function testElementDetached() {
var newChild = document.createElement('div');
try {
getElementLocation(newChild, false);
assert(false);
} catch (e) {
assertEquals('element is not attached to the page document', e.message);
assertEquals(10, e.code);
}
}
</script> </script>
<body> <body>
<h2>position: absolute;</h2> <h2>position: absolute;</h2>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment