Bug 1317386 - Rewrite in-view centre point calcaulation; r?automatedtester
The old calcaulation to determine an element's in-view centre point was
wrong as pointed out in https://github.com/w3c/webdriver/issues/425,
and this is an implementation of the proposed algorithm which passes
real-world tests.
This also addresses https://github.com/w3c/webdriver/pull/441 which
checks if the `DOMRect` sequence returned from `getClientRects` is empty,
as it may be if the element's `display` style property is `none`.
MozReview-Commit-ID: 4uitUrviW2a
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -931,71 +931,88 @@ element.isInteractable = function(el) {
*
* @param {DOMElement} el
* Element determine if is pointer-interactable.
*
* @return {boolean}
* True if interactable, false otherwise.
*/
element.isPointerInteractable = function(el) {
- let tree = element.getInteractableElementTree(el);
+ let tree = element.getInteractableElementTree(el, el.ownerDocument);
return tree.length > 0;
};
/**
+ * Calculate the in-view centre point of the area of the given DOM client
+ * rectangle that is inside the viewport.
+ *
+ * @param {DOMRect} rect
+ * Element off a DOMRect sequence produced by calling |getClientRects|
+ * on a |DOMElement|.
+ * @param {nsIDOMWindow} win
+ * Current browsing context.
+ *
+ * @return {Map.<string, number>}
+ * X and Y coordinates that denotes the in-view centre point of |rect|.
+ */
+element.getInViewCentrePoint = function(rect, win) {
+ const {max, min} = Math;
+
+ let x = {
+ left: max(0, min(rect.x, rect.x + rect.width)),
+ right: min(win.innerWidth, max(rect.x, rect.x + rect.width)),
+ };
+ let y = {
+ top: max(0, min(rect.y, rect.y + rect.height)),
+ bottom: min(win.innerHeight, max(rect.y, rect.y + rect.height)),
+ };
+
+ return {
+ x: (x.left + x.right) / 2,
+ y: (y.top + y.bottom) / 2,
+ };
+};
+
+/**
* Produces a pointer-interactable elements tree from a given element.
*
* The tree is defined by the paint order found at the centre point of
* the element's rectangle that is inside the viewport, excluding the size
* of any rendered scrollbars.
*
* @param {DOMElement} el
* Element to determine if is pointer-interactable.
+ * @param {DOMDocument} doc
+ * Current browsing context's active document.
*
* @return {Array.<DOMElement>}
* Sequence of non-opaque elements in paint order.
*/
-element.getInteractableElementTree = function(el) {
- let doc = el.ownerDocument;
+element.getInteractableElementTree = function(el, doc) {
let win = doc.defaultView;
- // step 1
- // TODO
+ // pointer-interactable elements tree, step 1
+ if (element.isDisconnected(el, win)) {
+ return [];
+ }
// steps 2-3
- let box = el.getBoundingClientRect();
- let visible = {
- width: Math.max(box.x, box.x + box.width) - win.innerWidth,
- height: Math.max(box.y, box.y + box.height) - win.innerHeight,
- };
+ let rects = el.getClientRects();
+ if (rects.length == 0) {
+ return [];
+ }
- // steps 4-5
- let offset = {
- vertical: visible.width / 2.0,
- horizontal: visible.height / 2.0,
- };
+ // step 4
+ let centre = element.getInViewCentrePoint(rects[0], win);
- // step 6
- let centre = {
- x: box.x + offset.horizontal,
- y: box.y + offset.vertical,
- };
-
- // step 7
+ // step 5
let tree = doc.elementsFromPoint(centre.x, centre.y);
- // filter out non-interactable elements
- let rv = [];
- for (let el of tree) {
- if (win.getComputedStyle(el).opacity === "1") {
- rv.push(el);
- }
- }
-
- return rv;
+ // only visible elements are considered interactable
+ return tree.filter(el => win.getComputedStyle(el).opacity === "1");
};
// TODO(ato): Not implemented.
// In fact, it's not defined in the spec.
element.isKeyboardInteractable = function(el) {
return true;
};