Bug 1317386 - Rewrite in-view centre point calcaulation; r?automatedtester draft
authorAndreas Tolfsen <ato@mozilla.com>
Mon, 14 Nov 2016 21:10:08 +0000
changeset 441073 c879557542fcd0f010fa1c3fc76eece3f33328f0
parent 441072 0eead2f40ec0ff4f80a4b33defb68eb64357c9dc
child 441074 8295d047bb0f52027d782e37575c98669f751e33
push id36351
push userbmo:ato@mozilla.com
push dateFri, 18 Nov 2016 10:30:51 +0000
reviewersautomatedtester
bugs1317386
milestone53.0a1
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
testing/marionette/element.js
--- 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;
 };