Bug 1032724 - Improve spatial navigation, r=blassey
authorKershaw Chang <kechang@mozilla.com>
Tue, 15 Jul 2014 02:16:00 +0200
changeset 194344 b6ca88f40b7964d62ca2f2d91cc9998c4914c60f
parent 194343 d56e8ef026a035fd5e6c69369d00255710d7ab3c
child 194345 c12579b0b8561454443c5bbdb560c18ffa65ccd8
push id27144
push userkwierso@gmail.com
push dateWed, 16 Jul 2014 21:35:16 +0000
treeherdermozilla-central@4024d8019701 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey
bugs1032724
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1032724 - Improve spatial navigation, r=blassey
toolkit/modules/SpatialNavigation.jsm
toolkit/modules/tests/mochitest/test_spatial_navigation.html
--- a/toolkit/modules/SpatialNavigation.jsm
+++ b/toolkit/modules/SpatialNavigation.jsm
@@ -285,55 +285,38 @@ function _getBestToFocus(nodes, key, cur
         if (nodeMid.y <= (currentlyFocusedMid.y + currentlyFocusedRect.height / 2)) {
           continue;
         }
         break;
     }
 
     // Initialize best to the first viable value:
     if (!best) {
-      best = nodes[i];
-      bestDist = _spatialDistance(best, currentlyFocused);
+      bestDist = _spatialDistanceOfCorner(currentlyFocused, nodes[i], key);
+      if (bestDist >= 0) {
+        best = nodes[i];
+      }
       continue;
     }
 
     // Of the remaining nodes, pick the one closest to the currently focused
     // node.
-    let curDist = _spatialDistance(nodes[i], currentlyFocused);
-    if (curDist > bestDist) {
+    let curDist = _spatialDistanceOfCorner(currentlyFocused, nodes[i], key);
+    if ((curDist > bestDist) || curDist === -1) {
       continue;
     }
+    else if (curDist === bestDist) {
+      let midCurDist = _spatialDistance(currentlyFocused, nodes[i]);
+      let midBestDist = _spatialDistance(currentlyFocused, best);
+      if (midCurDist > midBestDist)
+        continue;
+    }
 
-    bestMid = _getMidpoint(best);
-    switch (key) {
-      case PrefObserver['keyCodeLeft']:
-        if (nodeMid.x > bestMid.x) {
-          best = nodes[i];
-          bestDist = curDist;
-        }
-        break;
-      case PrefObserver['keyCodeRight']:
-        if (nodeMid.x < bestMid.x) {
-          best = nodes[i];
-          bestDist = curDist;
-        }
-        break;
-      case PrefObserver['keyCodeUp']:
-        if (nodeMid.y > bestMid.y) {
-          best = nodes[i];
-          bestDist = curDist;
-        }
-        break;
-      case PrefObserver['keyCodeDown']:
-        if (nodeMid.y < bestMid.y) {
-          best = nodes[i];
-          bestDist = curDist;
-        }
-        break;
-    }
+    best = nodes[i];
+    bestDist = curDist;
   }
   return best;
 }
 
 // Returns the midpoint of a node.
 function _getMidpoint(node) {
   let mid = {};
   let box = node.getBoundingClientRect();
@@ -371,47 +354,144 @@ function _getSearchRect(currentlyFocused
   newRect.top    = currentlyFocusedRect.top;
   newRect.right  = currentlyFocusedRect.right;
   newRect.bottom = currentlyFocusedRect.bottom;
   newRect.width  = currentlyFocusedRect.width;
   newRect.height = currentlyFocusedRect.height;
 
   switch (key) {
     case PrefObserver['keyCodeLeft']:
+      newRect.right = newRect.left;
+      newRect.left = cssPageRect.left;
+      newRect.width = newRect.right - newRect.left;
+
+      newRect.bottom = cssPageRect.bottom;
+      newRect.top = cssPageRect.top;
+      newRect.height = newRect.bottom - newRect.top;
+      break;
+
+    case PrefObserver['keyCodeRight']:
+      newRect.left = newRect.right;
+      newRect.right = cssPageRect.right;
+      newRect.width = newRect.right - newRect.left;
+
+      newRect.bottom = cssPageRect.bottom;
+      newRect.top = cssPageRect.top;
+      newRect.height = newRect.bottom - newRect.top;
+      break;
+
+    case PrefObserver['keyCodeUp']:
+      newRect.bottom = newRect.top;
+      newRect.top = cssPageRect.top;
+      newRect.height = newRect.bottom - newRect.top;
+
+      newRect.right = cssPageRect.right;
       newRect.left = cssPageRect.left;
       newRect.width = newRect.right - newRect.left;
       break;
 
-    case PrefObserver['keyCodeRight']:
-      newRect.right = cssPageRect.right;
-      newRect.width = newRect.right - newRect.left;
-      break;
-
-    case PrefObserver['keyCodeUp']:
-      newRect.top = cssPageRect.top;
-      newRect.height = newRect.bottom - newRect.top;
-      break;
-
     case PrefObserver['keyCodeDown']:
+      newRect.top = newRect.bottom;
       newRect.bottom = cssPageRect.bottom;
       newRect.height = newRect.bottom - newRect.top;
+
+      newRect.right = cssPageRect.right;
+      newRect.left = cssPageRect.left;
+      newRect.width = newRect.right - newRect.left;
       break;
   }
   return newRect;
 }
 
 // Gets the distance between two points a and b.
 function _spatialDistance(a, b) {
   let mida = _getMidpoint(a);
   let midb = _getMidpoint(b);
 
   return Math.round(Math.pow(mida.x - midb.x, 2) +
                     Math.pow(mida.y - midb.y, 2));
 }
 
+// Get the distance between the corner of two nodes
+function _spatialDistanceOfCorner(from, to, key) {
+  let fromRect = from.getBoundingClientRect();
+  let toRect = to.getBoundingClientRect();
+  let fromMid = _getMidpoint(from);
+  let toMid = _getMidpoint(to);
+  let hDistance = 0;
+  let vDistance = 0;
+
+  switch (key) {
+    case PrefObserver['keyCodeLeft']:
+      // Make sure the "to" node is really at the left side of "from" node by
+      //  1. Check the mid point
+      //  2. The right border of "to" node must be less than the "from" node
+      if ((fromMid.x - toMid.x) < 0 || toRect.right >= fromRect.right)
+        return -1;
+      hDistance = Math.abs(fromRect.left - toRect.right);
+      if (toRect.bottom <= fromRect.top) {
+        vDistance = fromRect.top - toRect.bottom;
+      }
+      else if (fromRect.bottom <= toRect.top) {
+        vDistance = toRect.top - fromRect.bottom;
+      }
+      else {
+        vDistance = 0;
+      }
+      break;
+
+    case PrefObserver['keyCodeRight']:
+      if ((toMid.x - fromMid.x) < 0 || toRect.left <= fromRect.left)
+        return -1;
+      hDistance = Math.abs(toRect.left - fromRect.right);
+      if (toRect.bottom <= fromRect.top) {
+        vDistance = fromRect.top - toRect.bottom;
+      }
+      else if (fromRect.bottom <= toRect.top) {
+        vDistance = toRect.top - fromRect.bottom;
+      }
+      else {
+        vDistance = 0;
+      }
+      break;
+
+    case PrefObserver['keyCodeUp']:
+      if ((fromMid.y - toMid.y) < 0 || toRect.bottom >= fromRect.bottom)
+        return -1;
+      vDistance = Math.abs(fromRect.top - toRect.bottom);
+      if (fromRect.right <= toRect.left) {
+        hDistance = toRect.left - fromRect.right;
+      }
+      else if (toRect.right <= fromRect.left) {
+        hDistance = fromRect.left - toRect.right;
+      }
+      else {
+        hDistance = 0;
+      }
+      break;
+
+    case PrefObserver['keyCodeDown']:
+      if ((toMid.y - fromMid.y) < 0 || toRect.top <= fromRect.top)
+        return -1;
+      vDistance = Math.abs(toRect.top - fromRect.bottom);
+      if (fromRect.right <= toRect.left) {
+        hDistance = toRect.left - fromRect.right;
+      }
+      else if (toRect.right <= fromRect.left) {
+        hDistance = fromRect.left - toRect.right;
+      }
+      else {
+        hDistance = 0;
+      }
+      break;
+  }
+  return Math.round(Math.pow(hDistance, 2) +
+                    Math.pow(vDistance, 2));
+}
+
 // Snav preference observer
 var PrefObserver = {
   register: function() {
     this.prefService = Cc["@mozilla.org/preferences-service;1"]
                           .getService(Ci.nsIPrefService);
 
     this._branch = this.prefService.getBranch("snav.");
     this._branch.QueryInterface(Ci.nsIPrefBranch2);
@@ -519,9 +599,9 @@ var PrefObserver = {
         } catch(e) {
           this.keyCodeReturn = Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
         }
         break;
     }
   }
 };
 
-PrefObserver.register();
+PrefObserver.register();
\ No newline at end of file
--- a/toolkit/modules/tests/mochitest/test_spatial_navigation.html
+++ b/toolkit/modules/tests/mochitest/test_spatial_navigation.html
@@ -34,45 +34,43 @@ https://bugzilla.mozilla.org/show_bug.cg
     console.log(left);
     console.log(right);
 
     center.focus();
     is(center.id, document.activeElement.id, "How did we call focus on center and it did" +
                                              " not become the active element?");
 
     synthesizeKey("VK_UP", { });
-    is(top.id, document.activeElement.id,
+    is(document.activeElement.id, top.id,
        "Spatial navigation up key is not handled correctly.");
 
     center.focus();
     synthesizeKey("VK_DOWN", { });
-    is(bottom.id, document.activeElement.id,
+    is(document.activeElement.id, bottom.id,
        "Spatial navigation down key is not handled correctly.");
 
     center.focus();
     synthesizeKey("VK_RIGHT", { });
-    is(right.id, document.activeElement.id,
+    is(document.activeElement.id, right.id,
        "Spatial navigation right key is not handled correctly.");
 
     center.focus();
     synthesizeKey("VK_LEFT", { });
-    is(left.id, document.activeElement.id,
+    is(document.activeElement.id, left.id,
        "Spatial navigation left key is not handled correctly.");
 
     SimpleTest.finish();
   }
 
   </script>
 </head>
 <body onload="Test();">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=698437">Mozilla Bug 698437</a>
 <p id="display"></p>
 <div id="content">
-  <p> This is a <a id="top" href="#">really</a> long sentence </p>
-  <p> <a id="left" href="#">This</a> is a
-      <a id="center" href="#">really</a> long
-      <a id="right" href="#">sentence</a> </p>
-  <p> This is a <a id="bottom" href="#">really</a> long sentence </p>
+  <button id="lefttop">1</button><button id="top">2</button><button id="righttop">3</button><br>
+  <button id="left">4</button><button id="center">5</button><button id="right">6</button><br>
+  <button id="leftbottom">7</button><button id="bottom">8</button><button id="rightbottom">9</button><br>
 </div>
 <pre id="test">
 </pre>
 </body>
 </html>