merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 26 Jan 2015 14:24:08 +0100
changeset 225643 95c76c3b01726526eb9ff8152c8d3f8c94a86006
parent 225642 4368f39456904bcfe89e0829046489d87b1511b6 (current diff)
parent 225615 92c38ff324f6354bafca16b95143ff96a8fdc54f (diff)
child 225644 54be9bcdacd970435c87881d1b906f6e9416bd40
child 225691 3a2d8da694ac7c17d76512d1fd5f52ed51d8bdca
child 225837 0bec7418755382d3c182e1ae3349be24bb2de2ad
push id10990
push usercbook@mozilla.com
push dateMon, 26 Jan 2015 14:06:38 +0000
treeherderfx-team@54be9bcdacd9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone38.0a1
merge fx-team to mozilla-central a=merge
--- a/browser/components/uitour/UITour-lib.js
+++ b/browser/components/uitour/UITour-lib.js
@@ -265,8 +265,13 @@ if (typeof Mozilla == 'undefined') {
 
 	Mozilla.UITour.openSearchPanel = function(callback) {
 		_sendEvent('openSearchPanel', {
 			callbackID: _waitForCallback(callback)
 		});
 	};
 
 })();
+
+// Make this library Require-able.
+if (typeof module !== 'undefined' && module.exports) {
+  module.exports = Mozilla.UITour;
+}
--- a/browser/devtools/projecteditor/lib/plugins/new/new.js
+++ b/browser/devtools/projecteditor/lib/plugins/new/new.js
@@ -46,17 +46,17 @@ var NewFile = Class({
       let template = "untitled{1}." + extension;
       let name = this.suggestName(parent, template);
 
       tree.promptNew(name, parent, sibling).then(name => {
 
         // XXX: sanitize bad file names.
 
         // If the name is already taken, just add/increment a number.
-        if (this.hasChild(parent, name)) {
+        if (parent.hasChild(name)) {
           let matches = name.match(/([^\d.]*)(\d*)([^.]*)(.*)/);
           template = matches[1] + "{1}" + matches[3] + matches[4];
           name = this.suggestName(parent, template, parseInt(matches[2]) || 2);
         }
 
         return parent.createChild(name);
       }).then(resource => {
         tree.selectResource(resource);
@@ -66,24 +66,15 @@ var NewFile = Class({
   },
 
   suggestName: function(parent, template, start=1) {
     let i = start;
     let name;
     do {
       name = template.replace("\{1\}", i === 1 ? "" : i);
       i++;
-    } while (this.hasChild(parent, name));
+    } while (parent.hasChild(name));
 
     return name;
-  },
-
-  hasChild: function(resource, name) {
-    for (let child of resource.children) {
-      if (child.basename === name) {
-        return true;
-      }
-    }
-    return false;
   }
-})
+});
 exports.NewFile = NewFile;
 registerPlugin(NewFile);
--- a/browser/devtools/projecteditor/lib/plugins/rename/rename.js
+++ b/browser/devtools/projecteditor/lib/plugins/rename/rename.js
@@ -28,17 +28,17 @@ var RenamePlugin = Class({
       let resource = tree.getSelectedResource();
       let parent = resource.parent;
       let oldName = resource.basename;
 
       tree.promptEdit(oldName, resource).then(name => {
         if (name === oldName) {
           return resource;
         }
-        if (resource.hasChild(parent, name)) {
+        if (parent.hasChild(name)) {
           let matches = name.match(/([^\d.]*)(\d*)([^.]*)(.*)/);
           let template = matches[1] + "{1}" + matches[3] + matches[4];
           name = this.suggestName(resource, template, parseInt(matches[2]) || 2);
         }
         return parent.rename(oldName,name);
       }).then(resource => {
         this.host.project.refresh();
         tree.selectResource(resource);
@@ -51,16 +51,16 @@ var RenamePlugin = Class({
 
   suggestName: function(resource, template, start=1) {
     let i = start;
     let name;
     let parent = resource.parent;
     do {
       name = template.replace("\{1\}", i === 1 ? "" : i);
       i++;
-    } while (resource.hasChild(parent, name));
+    } while (parent.hasChild(name));
 
     return name;
   }
 });
 
 exports.RenamePlugin = RenamePlugin;
 registerPlugin(RenamePlugin);
--- a/browser/devtools/projecteditor/lib/stores/resource.js
+++ b/browser/devtools/projecteditor/lib/stores/resource.js
@@ -122,23 +122,22 @@ var Resource = Class({
     resource.parent = this;
     this.children.add(resource);
     this.store.notifyAdd(resource);
     emit(this, "children-changed", this);
     return resource;
   },
 
   /**
-   * Checks a resource has child with specific name.
+   * Checks if current object has child with specific name.
    *
-   * @param Resource resource
    * @param string name
    */
-  hasChild: function(resource, name) {
-    for (let child of resource.children) {
+  hasChild: function(name) {
+    for (let child of this.children) {
       if (child.basename === name) {
         return true;
       }
     }
     return false;
   },
 
   /**
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -1925,24 +1925,23 @@ nsDocumentViewer::SetPreviousViewer(nsIC
 }
 
 NS_IMETHODIMP
 nsDocumentViewer::SetBounds(const nsIntRect& aBounds)
 {
   NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
 
   mBounds = aBounds;
-  if (mWindow) {
-    if (!mAttachedToParent) {
-      // Don't have the widget repaint. Layout will generate repaint requests
-      // during reflow.
-      mWindow->Resize(aBounds.x, aBounds.y,
-                      aBounds.width, aBounds.height,
-                      false);
-    }
+
+  if (mWindow && !mAttachedToParent) {
+    // Resize the widget, but don't trigger repaint. Layout will generate
+    // repaint requests during reflow.
+    mWindow->Resize(aBounds.x, aBounds.y,
+                    aBounds.width, aBounds.height,
+                    false);
   } else if (mPresContext && mViewManager) {
     int32_t p2a = mPresContext->AppUnitsPerDevPixel();
     mViewManager->SetWindowDimensions(NSIntPixelsToAppUnits(mBounds.width, p2a),
                                       NSIntPixelsToAppUnits(mBounds.height, p2a));
   }
 
   // If there's a previous viewer, it's the one that's actually showing,
   // so be sure to resize it as well so it paints over the right area.
--- a/mobile/android/base/tests/roboextender/testInputSelections.html
+++ b/mobile/android/base/tests/roboextender/testInputSelections.html
@@ -257,20 +257,22 @@ function testLTR_dragAnchorHandleToSelf(
 
   // Note initial Selection handle points.
   var initialSelection =
     { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
       focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
   var initialSelectionText = sh._getSelectedText();
 
   // Drag anchor handle and note results.
+  // Note, due to edge case with border boundaries, we actually
+  // move inside a pixel, to maintain Selection position.
   sh.observe(null, "TextSelection:Move",
     JSON.stringify({ handleType : ANCHOR,
       x : initialSelection.anchorPt.x,
-      y : initialSelection.anchorPt.y
+      y : initialSelection.anchorPt.y - 1
     })
   );
   sh.observe(null, "TextSelection:Position",
     JSON.stringify({ handleType : ANCHOR })
   );
   var anchorDraggedSelection =
     { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
       focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
@@ -282,24 +284,21 @@ function testLTR_dragAnchorHandleToSelf(
   return Promise.all([
     ok(true, "testLTR_dragAnchorHandleToSelf - Test Starts."),
 
     is(initialSelectionText, LTR_INPUT_TEXT_VALUE,
       "LTR Selection text initially should match expected value."),
     selectionExists(initialSelection,
       "LTR Selection initially existed at points"),
 
-    todo(false, "testLTR_dragAnchorHandleToSelf: " +
-    // is(anchorDragSelectionText, LTR_INPUT_TEXT_VALUE,
+    is(anchorDragSelectionText, LTR_INPUT_TEXT_VALUE,
       "LTR Selection text after anchor drag should match expected value."),
-    todo(false, "testLTR_dragAnchorHandleToSelf: " +
-    // selectionExists(anchorDraggedSelection,
+    selectionExists(anchorDraggedSelection,
       "LTR Selection after anchor drag existed at points"),
-    todo(false, "testLTR_dragAnchorHandleToSelf: " +
-    // selectionEquals(anchorDraggedSelection, initialSelection,
+    selectionEquals(anchorDraggedSelection, initialSelection,
       "LTR Selection points after anchor drag " +
       "should match initial selection points."),
 
     ok(true, "testLTR_dragAnchorHandleToSelf - Test Finishes."),
   ]);
 }
 
 /* =================================================================================
@@ -343,24 +342,21 @@ function testRTL_dragFocusHandleToSelf()
   return Promise.all([
     ok(true, "testRTL_dragFocusHandleToSelf - Test Starts."),
 
     is(initialSelectionText, RTL_INPUT_TEXT_VALUE,
       "RTL Selection text initially should match expected value."),
     selectionExists(initialSelection,
       "RTL Selection initially existed at points"),
 
-    todo(false, "testRTL_dragAnchorHandleToSelf: " +
-    // is(focusDragSelectionText, RTL_INPUT_TEXT_VALUE,
+    is(focusDragSelectionText, RTL_INPUT_TEXT_VALUE,
       "RTL Selection text after focus drag should match expected value."),
-    todo(false, "testRTL_dragAnchorHandleToSelf: " +
-    // selectionExists(focusDraggedSelection,
+    selectionExists(focusDraggedSelection,
       "RTL Selection after focus drag existed at points"),
-    todo(false, "testRTL_dragAnchorHandleToSelf: " +
-    // selectionEquals(focusDraggedSelection, initialSelection,
+    selectionEquals(focusDraggedSelection, initialSelection,
       "RTL Selection points after focus drag " +
       "should match initial selection points."),
 
     ok(true, "testRTL_dragFocusHandleToSelf - Test Finishes."),
   ]);
 }
 
 /* =================================================================================
@@ -378,20 +374,22 @@ function testRTL_dragAnchorHandleToSelf(
 
   // Note initial Selection handle points.
   var initialSelection =
     { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
       focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
   var initialSelectionText = sh._getSelectedText();
 
   // Drag anchor handle and note results.
+  // Note, due to edge case with border boundaries, we actually
+  // move inside a pixel, to maintain Selection position.
   sh.observe(null, "TextSelection:Move",
     JSON.stringify({ handleType : ANCHOR,
       x : initialSelection.anchorPt.x,
-      y : initialSelection.anchorPt.y
+      y : initialSelection.anchorPt.y - 1
     })
   );
   sh.observe(null, "TextSelection:Position",
     JSON.stringify({ handleType : ANCHOR })
   );
   var anchorDraggedSelection =
     { anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
       focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
@@ -488,45 +486,51 @@ function lessThan(n1, n2, msg) {
 function greaterThan(n1, n2, msg) {
   return Messaging.sendRequestForResult({
     type: "Robocop:testInputSelections",
     result: n1 > n2,
     msg: msg + " : " + n1 + " > " + n2
   });
 }
 
+// Use fuzzy logic to compare screen coords.
+function truncPoint(point) {
+  return new Point(Math.trunc(point.x), Math.trunc(point.y));
+}
+
 function pointEquals(p1, p2, msg) {
   return Messaging.sendRequestForResult({
     type: "Robocop:testInputSelections",
-    result: p1.equals(p2),
+    result: truncPoint(p1).equals(truncPoint(p2)),
     msg: msg + " : " + p1.toString() + " == " + p2.toString()
   });
 }
 
 function pointNotEquals(p1, p2, msg) {
   return Messaging.sendRequestForResult({
     type: "Robocop:testInputSelections",
-    result: !p1.equals(p2),
+    result: !truncPoint(p1).equals(truncPoint(p2)),
     msg: msg + " : " + p1.toString() + " == " + p2.toString()
   });
 }
 
 function selectionExists(selection, msg) {
   return Messaging.sendRequestForResult({
     type: "Robocop:testInputSelections",
-    result: !selection.anchorPt.equals(selection.focusPt),
+    result: !truncPoint(selection.anchorPt).equals(truncPoint(selection.focusPt)),
     msg: msg + " : anchor:" + selection.anchorPt.toString() +
       " focus:" + selection.focusPt.toString()
   });
 }
 
 function selectionEquals(s1, s2, msg) {
   return Messaging.sendRequestForResult({
     type: "Robocop:testInputSelections",
-    result: s1.anchorPt.equals(s2.anchorPt) && s1.focusPt.equals(s2.focusPt),
+    result: truncPoint(s1.anchorPt).equals(truncPoint(s2.anchorPt)) &&
+            truncPoint(s1.focusPt).equals(truncPoint(s2.focusPt)),
     msg: msg
   });
 }
 
 /* =================================================================================
  *
  * Page definition for all tests.
  *
--- a/mobile/android/base/tests/roboextender/testTextareaSelections.html
+++ b/mobile/android/base/tests/roboextender/testTextareaSelections.html
@@ -513,29 +513,26 @@ function testRTL_moveFocusHandleDown() {
 
     selectionExists(initialSelection, "RTL Initial selection existed at points"),
     is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
       "RTL Initial selection anchorPt.y should match focusPt.y"),
     greaterThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
       "RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
 
     selectionExists(changedSelection, "RTL Changed selection existed at points"),
-    todo(false, "testRTL_moveFocusHandleDown: " +
-    // pointEquals(changedSelection.anchorPt, initialSelection.anchorPt,
-       "RTL Changed selection focus handle moving down " +
-       "should not change anchor handle position."),
-    todo(false, "testRTL_moveFocusHandleDown: " +
-    // greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
-       "RTL Changed selection focusPt.y " +
-       "should be greater than (below) changed anchorPt.y"),
+    pointEquals(changedSelection.anchorPt, initialSelection.anchorPt,
+      "RTL Changed selection focus handle moving down " +
+      "should not change anchor handle position."),
+    greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
+      "RTL Changed selection focusPt.y " +
+      "should be greater than (below) changed anchorPt.y"),
 
-    todo(false, "testRTL_moveFocusHandleDown: " +
-    // greaterThan(changedSelection.focusPt.y, initialSelection.focusPt.y,
-       "RTL Changed selection focusPt.y " +
-       "should be greater than (below) Initial selection focusPt.y"),
+    greaterThan(changedSelection.focusPt.y, initialSelection.focusPt.y,
+      "RTL Changed selection focusPt.y " +
+      "should be greater than (below) Initial selection focusPt.y"),
   ]);
 }
 
 /* =================================================================================
  *
  * RTL Textarea test will create a single-line selection in the middle of the element
  * and ensure that handle reversals are detected as expected.
  *
@@ -580,34 +577,30 @@ function testRTL_moveFocusHandleUp() {
 
     selectionExists(initialSelection, "RTL Initial selection existed at points"),
     is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
       "RTL Initial selection anchorPt.y should match focusPt.y"),
     greaterThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
       "RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
 
     selectionExists(changedSelection, "RTL Changed selection existed at points"),
-    todo(false, "testRTL_moveFocusHandleUp: " +
-    // pointEquals(changedSelection.focusPt, initialSelection.anchorPt,
-       "RTL Reversed Changed selection focus handle moving up " +
-       "becomes new anchor handle, " +
-       "new focus handle is initial anchor handle."),
-    todo(false, "testRTL_moveFocusHandleUp: " +
-    // greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
-       "RTL Reversed Changed selection focusPt.y " +
-       "should be greater than (below) changed anchorPt.y"),
+    pointEquals(changedSelection.focusPt, initialSelection.anchorPt,
+      "RTL Reversed Changed selection focus handle moving up " +
+      "becomes new anchor handle, " +
+      "new focus handle is initial anchor handle."),
+    greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
+      "RTL Reversed Changed selection focusPt.y " +
+      "should be greater than (below) changed anchorPt.y"),
 
-    todo(false, "testRTL_moveFocusHandleUp: " +
-    // is(changedSelection.focusPt.y, initialSelection.focusPt.y,
-       "RTL Reversed Changed selection focusPt.y " +
-       "should be equal to Initial selection focusPt.y"),
-    todo(false, "testRTL_moveFocusHandleUp: " +
-    // lessThan(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
-       "RTL Reversed Changed selection anchorPt.y " +
-       "should be less than (above) Initial selection anchorPt.y"),
+    is(changedSelection.focusPt.y, initialSelection.focusPt.y,
+      "RTL Reversed Changed selection focusPt.y " +
+      "should be equal to Initial selection focusPt.y"),
+    lessThan(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
+      "RTL Reversed Changed selection anchorPt.y " +
+      "should be less than (above) Initial selection anchorPt.y"),
   ]);
 }
 
 /* =================================================================================
  *
  * RTL Textarea test will create a single-line selection in the middle of the element
  * and ensure that handle reversals are detected as expected.
  *
@@ -652,29 +645,26 @@ function testRTL_moveAnchorHandleUp() {
 
     selectionExists(initialSelection, "RTL Initial selection existed at points"),
     is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
       "RTL Initial selection anchorPt.y should match focusPt.y"),
     greaterThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
       "RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
 
     selectionExists(changedSelection, "RTL Changed selection existed at points"),
-    todo(false, "testRTL_moveAnchorHandleUp: " +
-    // pointEquals(changedSelection.focusPt, initialSelection.focusPt,
-       "RTL Changed selection anchor handle moving up " +
-       "should not change focus handle position."),
-    todo(false, "testRTL_moveAnchorHandleUp: " +
-    // greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
-       "RTL Changed selection focusPt.y " +
-       "should be greater than (below) changed anchorPt.y"),
+    pointEquals(changedSelection.focusPt, initialSelection.focusPt,
+      "RTL Changed selection anchor handle moving up " +
+      "should not change focus handle position."),
+    greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
+      "RTL Changed selection focusPt.y " +
+      "should be greater than (below) changed anchorPt.y"),
 
-    todo(false, "testRTL_moveAnchorHandleUp: " +
-    // lessThan(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
-       "RTL Changed selection anchorPt.y " +
-       "should be less than (above) Initial selection anchorPt.y"),
+    lessThan(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
+      "RTL Changed selection anchorPt.y " +
+      "should be less than (above) Initial selection anchorPt.y"),
   ]);
 }
 
 /* =================================================================================
  *
  * RTL Textarea test will create a single-line selection in the middle of the element
  * and ensure that handle reversals are detected as expected.
  *
@@ -719,34 +709,30 @@ function testRTL_moveAnchorHandleDown() 
 
     selectionExists(initialSelection, "RTL Initial selection existed at points"),
     is(initialSelection.anchorPt.y, initialSelection.focusPt.y,
       "RTL Initial selection anchorPt.y should match focusPt.y"),
     greaterThan(initialSelection.anchorPt.x, initialSelection.focusPt.x,
       "RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
 
     selectionExists(changedSelection, "RTL Changed selection existed at points"),
-    todo(false, "testRTL_moveAnchorHandleDown: " +
-    // pointEquals(changedSelection.anchorPt, initialSelection.focusPt,
-       "RTL Reversed Changed selection anchor handle moving down " +
-       "becomes new focus handle, " +
-       "new anchor handle is initial focus handle."),
-    todo(false, "testRTL_moveAnchorHandleDown: " +
-    // greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
-       "RTL Reversed Changed selection focusPt.y " +
-       "should be greater than (below) changed anchorPt.y"),
+    pointEquals(changedSelection.anchorPt, initialSelection.focusPt,
+      "RTL Reversed Changed selection anchor handle moving down " +
+      "becomes new focus handle, " +
+      "new anchor handle is initial focus handle."),
+    greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
+      "RTL Reversed Changed selection focusPt.y " +
+      "should be greater than (below) changed anchorPt.y"),
 
-    todo(false, "testRTL_moveAnchorHandleDown: " +
-    // is(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
-       "RTL Reversed Changed selection anchorPt.y " +
-       "should be equal to Initial selection anchorPt.y"),
-    todo(false, "testRTL_moveAnchorHandleDown: " +
-    // greaterThan(changedSelection.focusPt.y, initialSelection.focusPt.y,
-       "RTL Reversed Changed selection focusPt.y " +
-       "should be greater than (below) Initial selection focusPt.y"),
+    is(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
+      "RTL Reversed Changed selection anchorPt.y " +
+      "should be equal to Initial selection anchorPt.y"),
+    greaterThan(changedSelection.focusPt.y, initialSelection.focusPt.y,
+      "RTL Reversed Changed selection focusPt.y " +
+      "should be greater than (below) Initial selection focusPt.y"),
   ]);
 }
 
 /* =================================================================================
  *
  * After finish of all selection tests, wrap up and go home.
  *
  */
@@ -805,36 +791,41 @@ function lessThan(n1, n2, msg) {
 function greaterThan(n1, n2, msg) {
   return Messaging.sendRequestForResult({
     type: "Robocop:testTextareaSelections",
     result: n1 > n2,
     msg: msg + " : " + n1 + " > " + n2
   });
 }
 
+// Use fuzzy logic to compare screen coords.
+function truncPoint(point) {
+  return new Point(Math.trunc(point.x), Math.trunc(point.y));
+}
+
 function pointEquals(p1, p2, msg) {
   return Messaging.sendRequestForResult({
     type: "Robocop:testTextareaSelections",
-    result: p1.equals(p2),
+    result: truncPoint(p1).equals(truncPoint(p2)),
     msg: msg + " : " + p1.toString() + " == " + p2.toString()
   });
 }
 
 function pointNotEquals(p1, p2, msg) {
   return Messaging.sendRequestForResult({
     type: "Robocop:testTextareaSelections",
-    result: !p1.equals(p2),
+    result: !truncPoint(p1).equals(truncPoint(p2)),
     msg: msg + " : " + p1.toString() + " == " + p2.toString()
   });
 }
 
 function selectionExists(selection, msg) {
   return Messaging.sendRequestForResult({
     type: "Robocop:testTextareaSelections",
-    result: !selection.anchorPt.equals(selection.focusPt),
+    result: !truncPoint(selection.anchorPt).equals(truncPoint(selection.focusPt)),
     msg: msg + " : anchor:" + selection.anchorPt.toString() +
       " focus:" + selection.focusPt.toString()
   });
 }
 
 /* =================================================================================
  *
  * Page definition for all tests.
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -35,17 +35,21 @@ var SelectionHandler = {
 
   SELECT_ALL: 0,
   SELECT_AT_POINT: 1,
 
   // Keeps track of data about the dimensions of the selection. Coordinates
   // stored here are relative to the _contentWindow window.
   _cache: null,
   _activeType: 0, // TYPE_NONE
+
   _draggingHandles: false, // True while user drags text selection handles
+  _dragStartAnchorOffset: null, // Editables need initial pos during HandleMove events
+  _dragStartFocusOffset: null, // Editables need initial pos during HandleMove events
+
   _ignoreCompositionChanges: false, // Persist caret during IME composition updates
   _prevHandlePositions: [], // Avoid issuing duplicate "TextSelection:Position" messages
   _deferCloseTimer: null, // Used to defer _closeSelection() actions during programmatic changes
 
   // TargetElement changes (text <--> no text) trigger actionbar UI update
   _prevTargetElementHasText: null,
 
   // The window that holds the selection (can be a sub-frame)
@@ -147,58 +151,45 @@ var SelectionHandler = {
         break;
       case "after-viewport-change": {
         if (this._activeType == this.TYPE_SELECTION) {
           // Update the cache after the viewport changes (e.g. panning, zooming).
           this._updateCacheForSelection();
         }
         break;
       }
+
       case "TextSelection:Move": {
         let data = JSON.parse(aData);
         if (this._activeType == this.TYPE_SELECTION) {
           this._startDraggingHandles();
-          this._moveSelection(data.handleType == this.HANDLE_TYPE_ANCHOR, data.x, data.y);
+          this._moveSelection(data.handleType, new Point(data.x, data.y));
 
         } else if (this._activeType == this.TYPE_CURSOR) {
           this._startDraggingHandles();
 
           // Ignore IMM composition notifications when caret movement starts
           this._ignoreCompositionChanges = true;
           this._moveCaret(data.x, data.y);
 
           // Move the handle directly under the caret
           this._positionHandles();
         }
         break;
       }
+
       case "TextSelection:Position": {
         if (this._activeType == this.TYPE_SELECTION) {
           this._startDraggingHandles();
+          this._ensureSelectionDirection();
+          this._stopDraggingHandles();
 
-          // Check to see if the handles should be reversed.
-          let isStartHandle = JSON.parse(aData).handleType == this.HANDLE_TYPE_ANCHOR;
-          try {
-            let selectionReversed = this._updateCacheForSelection(isStartHandle);
-            if (selectionReversed) {
-              // Reverse the anchor and focus to correspond to the new start and end handles.
-              let selection = this._getSelection();
-              let anchorNode = selection.anchorNode;
-              let anchorOffset = selection.anchorOffset;
-              selection.collapse(selection.focusNode, selection.focusOffset);
-              selection.extend(anchorNode, anchorOffset);
-            }
-          } catch (e) {
-            // User finished handle positioning with one end off the screen
-            this._closeSelection();
-            break;
-          }
+          this._updateCacheForSelection();
+          this._positionHandles();
 
-          this._stopDraggingHandles();
-          this._positionHandles();
           // Changes to handle position can affect selection context and actionbar display
           this._updateMenu();
 
         } else if (this._activeType == this.TYPE_CURSOR) {
           // Act on IMM composition notifications after caret movement ends
           this._ignoreCompositionChanges = false;
           this._stopDraggingHandles();
           this._positionHandles();
@@ -220,25 +211,30 @@ var SelectionHandler = {
     }
   },
 
   // Ignore selectionChange notifications during handle dragging, disable dynamic
   // IME text compositions (autoSuggest, autoCorrect, etc)
   _startDraggingHandles: function sh_startDraggingHandles() {
     if (!this._draggingHandles) {
       this._draggingHandles = true;
+      let selection = this._getSelection();
+      this._dragStartAnchorOffset = selection.anchorOffset;
+      this._dragStartFocusOffset = selection.focusOffset;
       Messaging.sendRequest({ type: "TextSelection:DraggingHandle", dragging: true });
     }
   },
 
   // Act on selectionChange notifications when not dragging handles, allow dynamic
   // IME text compositions (autoSuggest, autoCorrect, etc)
   _stopDraggingHandles: function sh_stopDraggingHandles() {
     if (this._draggingHandles) {
       this._draggingHandles = false;
+      this._dragStartAnchorOffset = null;
+      this._dragStartFocusOffset = null;
       Messaging.sendRequest({ type: "TextSelection:DraggingHandle", dragging: false });
     }
   },
 
   handleEvent: function sh_handleEvent(aEvent) {
     // Ignore all but selectionListener notifications during deferred _closeSelection().
     if (this._deferCloseTimer) {
       return;
@@ -857,84 +853,70 @@ var SelectionHandler = {
             (aElement instanceof HTMLTextAreaElement)) && !aElement.readOnly);
   },
 
   _isNonTextInputElement: function(aElement) {
     return (aElement instanceof HTMLInputElement && !aElement.mozIsTextField(false));
   },
 
   /*
-   * Helper function for moving the selection inside an editable element.
-   *
-   * @param aAnchorX the stationary handle's x-coordinate in client coordinates
-   * @param aX the moved handle's x-coordinate in client coordinates
-   * @param aCaretPos the current position of the caret
+   * Moves the selection as the user drags a handle.
+   * @param handleType: Specifies either the anchor or the focus handle.
+   * @param handlePt: selection point in client coordinates.
    */
-  _moveSelectionInEditable: function sh_moveSelectionInEditable(aAnchorX, aX, aCaretPos) {
-    let anchorOffset = aX < aAnchorX ? this._targetElement.selectionEnd
-                                     : this._targetElement.selectionStart;
-    let newOffset = aCaretPos.offset;
-    let [start, end] = anchorOffset <= newOffset ?
-                       [anchorOffset, newOffset] :
-                       [newOffset, anchorOffset];
-    this._targetElement.setSelectionRange(start, end);
-  },
+  _moveSelection: function sh_moveSelection(handleType, handlePt) {
+    let isAnchorHandle = (handleType == this.HANDLE_TYPE_ANCHOR);
 
-  /*
-   * Moves the selection as the user drags a selection handle.
-   *
-   * @param aIsStartHandle whether the user is moving the start handle (as opposed to the end handle)
-   * @param aX, aY selection point in client coordinates
-   */
-  _moveSelection: function sh_moveSelection(aIsStartHandle, aX, aY) {
-    // XXX We should be smarter about the coordinates we pass to caretPositionFromPoint, especially
-    // in editable targets. We should factor out the logic that's currently in _moveCaret.
+    // Determine new caret position from handlePt, exit if user
+    // moved it offscreen.
     let viewOffset = this._getViewOffset();
-    let caretPos = this._contentWindow.document.caretPositionFromPoint(aX - viewOffset.x, aY - viewOffset.y);
+    let ptX = handlePt.x - viewOffset.x;
+    let ptY = handlePt.y - viewOffset.y;
+    let cwd = this._contentWindow.document;
+    let caretPos = cwd.caretPositionFromPoint(ptX, ptY);
     if (!caretPos) {
-      // User moves handle offscreen while positioning
       return;
     }
 
     // Constrain text selection within editable elements.
     let targetIsEditable = this._targetElement instanceof Ci.nsIDOMNSEditableElement;
     if (targetIsEditable && (caretPos.offsetNode != this._targetElement)) {
       return;
     }
 
-    // Update the cache as the handle is dragged (keep the cache in client coordinates).
-    if (aIsStartHandle) {
-      this._cache.anchorPt.x = aX;
-      this._cache.anchorPt.y = aY;
-    } else {
-      this._cache.focusPt.x = aX;
-      this._cache.focusPt.y = aY;
+    // Update the Selection for editable elements. Selection Change
+    // logic is the same, regardless of RTL/LTR. Selection direction is
+    // maintained always forward (startOffset <= endOffset).
+    if (targetIsEditable) {
+      let start = this._dragStartAnchorOffset;
+      let end = this._dragStartFocusOffset;
+      if (isAnchorHandle) {
+        start = caretPos.offset;
+      } else {
+        end = caretPos.offset;
+      }
+      if (start > end) {
+        [start, end] = [end, start];
+      }
+      this._targetElement.setSelectionRange(start, end);
+      return;
     }
 
+    // Update the Selection for non-editable elements. Selection Change
+    // logic is the same, regardless of RTL/LTR. Selection direction internally
+    // can finish reversed by user drag. ie: Forward is (a,o ---> f,o),
+    // and reversed is (a,o <--- f,o).
     let selection = this._getSelection();
-
-    // The handles work the same on both LTR and RTL pages, but the anchor/focus nodes
-    // are reversed, so we need to reverse the logic to extend the selection.
-    if ((aIsStartHandle && !this._isRTL) || (!aIsStartHandle && this._isRTL)) {
-      if (targetIsEditable) {
-        let anchorX = this._isRTL ? this._cache.anchorPt.x : this._cache.focusPt.x;
-        this._moveSelectionInEditable(anchorX, aX, caretPos);
-      } else {
-        let focusNode = selection.focusNode;
-        let focusOffset = selection.focusOffset;
-        selection.collapse(caretPos.offsetNode, caretPos.offset);
-        selection.extend(focusNode, focusOffset);
-      }
+    if (isAnchorHandle) {
+      let focusNode = selection.focusNode;
+      let focusOffset = selection.focusOffset;
+      selection.collapse(caretPos.offsetNode, caretPos.offset);
+      selection.extend(focusNode, focusOffset);
     } else {
-      if (targetIsEditable) {
-        let anchorX = this._isRTL ? this._cache.focusPt.x : this._cache.anchorPt.x;
-        this._moveSelectionInEditable(anchorX, aX, caretPos);
-      } else {
-        selection.extend(caretPos.offsetNode, caretPos.offset);
-      }
+      selection.extend(caretPos.offsetNode, caretPos.offset);
     }
   },
 
   _moveCaret: function sh_moveCaret(aX, aY) {
     // Get rect of text inside element
     let range = document.createRange();
     range.selectNodeContents(this._targetElement.QueryInterface(Ci.nsIDOMNSEditableElement).editor.rootElement);
     let textBounds = range.getBoundingClientRect();
@@ -1144,39 +1126,66 @@ var SelectionHandler = {
       offset.y += rect.top;
 
       win = win.parent;
     }
 
     return offset;
   },
 
-  // Returns true if the selection has been reversed. Takes optional aIsStartHandle
-  // param to decide whether the selection has been reversed.
-  _updateCacheForSelection: function sh_updateCacheForSelection(aIsStartHandle) {
+  /*
+   * The direction of the Selection is ensured for editables while the user drags
+   * the handles (per "TextSelection:Move" event). For non-editables, we just let
+   * the user change direction, but fix it up at the end of handle movement (final
+   * "TextSelection:Position" event).
+   */
+  _ensureSelectionDirection: function() {
+    // Never needed at this time.
+    if (this._targetElement instanceof Ci.nsIDOMNSEditableElement) {
+      return;
+    }
+
+    // Nothing needed if not reversed.
+    let qcEventResult = this._domWinUtils.sendQueryContentEvent(
+      this._domWinUtils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
+    if (!qcEventResult.reversed) {
+      return;
+    }
+
+    // Reverse the Selection.
+    let selection = this._getSelection();
+    let newFocusNode = selection.anchorNode;
+    let newFocusOffset = selection.anchorOffset;
+
+    selection.collapse(selection.focusNode, selection.focusOffset);
+    selection.extend(newFocusNode, newFocusOffset);
+  },
+
+  /*
+   * Updates the TYPE_SELECTION cache, with the handle anchor/focus point values
+   * of the current selection. Passed to Java for UI positioning only.
+   */
+  _updateCacheForSelection: function() {
     let rects = this._getSelection().getRangeAt(0).getClientRects();
-    if (!rects[0]) {
+    if (rects.length == 0) {
       // nsISelection object exists, but there's nothing actually selected
       throw "Failed to update cache for invalid selection";
     }
 
-    let start = { x: this._isRTL ? rects[0].right : rects[0].left, y: rects[0].bottom };
-    let end = { x: this._isRTL ? rects[rects.length - 1].left : rects[rects.length - 1].right, y: rects[rects.length - 1].bottom };
-
-    let selectionReversed = false;
-    if (this._cache.anchorPt) {
-      // If the end moved past the old end, but we're dragging the start handle, then that handle should become the end handle (and vice versa)
-      selectionReversed = (aIsStartHandle && (end.y > this._cache.focusPt.y || (end.y == this._cache.focusPt.y && end.x > this._cache.focusPt.x))) ||
-                          (!aIsStartHandle && (start.y < this._cache.anchorPt.y || (start.y == this._cache.anchorPt.y && start.x < this._cache.anchorPt.x)));
+    let anchorIdx = 0;
+    let focusIdx = rects.length - 1;
+    if (this._isRTL) {
+      // Right-to-Left (ie: Hebrew) anchorPt is on right, focusPt is on left.
+      this._cache.anchorPt = new Point(rects[anchorIdx].right, rects[anchorIdx].bottom);
+      this._cache.focusPt = new Point(rects[focusIdx].left, rects[focusIdx].bottom);
+    } else {
+      // Left-to-Right (ie: English) anchorPt is on left, focusPt is on right.
+      this._cache.anchorPt = new Point(rects[anchorIdx].left, rects[anchorIdx].bottom);
+      this._cache.focusPt = new Point(rects[focusIdx].right, rects[focusIdx].bottom);
     }
-
-    this._cache.anchorPt = start;
-    this._cache.focusPt = end;
-
-    return selectionReversed;
   },
 
   _getHandlePositions: function sh_getHandlePositions(scroll) {
     // the checkHidden function tests to see if the given point is hidden inside an
     // iframe/subdocument. this is so that if we select some text inside an iframe and
     // scroll the iframe so the selection is out of view, we hide the handles rather
     // than having them float on top of the main page content.
     let checkHidden = function(x, y) {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -293,18 +293,18 @@ function convertFromTwipsToPx(aSize) {
 }
 
 XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
   let ContentAreaUtils = {};
   Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils);
   return ContentAreaUtils;
 });
 
-XPCOMUtils.defineLazyModuleGetter(this, "Rect",
-                                  "resource://gre/modules/Geometry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Point", "resource://gre/modules/Geometry.jsm");
 
 function resolveGeckoURI(aURI) {
   if (!aURI)
     throw "Can't resolve an empty uri";
 
   if (aURI.startsWith("chrome://")) {
     let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]);
     return registry.convertChromeURL(Services.io.newURI(aURI, null, null)).spec;