Bug 1123606 - Fix RTL Logic error affecting Handle Movement in TextAreas, r=margaret

This commit is contained in:
Mark Capella 2015-01-26 02:44:01 -05:00
parent bfc0a8f26b
commit 26375d3aa0
4 changed files with 173 additions and 169 deletions

View File

@ -262,10 +262,12 @@ function testLTR_dragAnchorHandleToSelf() {
var initialSelectionText = sh._getSelectedText(); var initialSelectionText = sh._getSelectedText();
// Drag anchor handle and note results. // 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", sh.observe(null, "TextSelection:Move",
JSON.stringify({ handleType : ANCHOR, JSON.stringify({ handleType : ANCHOR,
x : initialSelection.anchorPt.x, x : initialSelection.anchorPt.x,
y : initialSelection.anchorPt.y y : initialSelection.anchorPt.y - 1
}) })
); );
sh.observe(null, "TextSelection:Position", sh.observe(null, "TextSelection:Position",
@ -287,14 +289,11 @@ function testLTR_dragAnchorHandleToSelf() {
selectionExists(initialSelection, selectionExists(initialSelection,
"LTR Selection initially existed at points"), "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."), "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"), "LTR Selection after anchor drag existed at points"),
todo(false, "testLTR_dragAnchorHandleToSelf: " + selectionEquals(anchorDraggedSelection, initialSelection,
// selectionEquals(anchorDraggedSelection, initialSelection,
"LTR Selection points after anchor drag " + "LTR Selection points after anchor drag " +
"should match initial selection points."), "should match initial selection points."),
@ -348,14 +347,11 @@ function testRTL_dragFocusHandleToSelf() {
selectionExists(initialSelection, selectionExists(initialSelection,
"RTL Selection initially existed at points"), "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."), "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"), "RTL Selection after focus drag existed at points"),
todo(false, "testRTL_dragAnchorHandleToSelf: " + selectionEquals(focusDraggedSelection, initialSelection,
// selectionEquals(focusDraggedSelection, initialSelection,
"RTL Selection points after focus drag " + "RTL Selection points after focus drag " +
"should match initial selection points."), "should match initial selection points."),
@ -383,10 +379,12 @@ function testRTL_dragAnchorHandleToSelf() {
var initialSelectionText = sh._getSelectedText(); var initialSelectionText = sh._getSelectedText();
// Drag anchor handle and note results. // 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", sh.observe(null, "TextSelection:Move",
JSON.stringify({ handleType : ANCHOR, JSON.stringify({ handleType : ANCHOR,
x : initialSelection.anchorPt.x, x : initialSelection.anchorPt.x,
y : initialSelection.anchorPt.y y : initialSelection.anchorPt.y - 1
}) })
); );
sh.observe(null, "TextSelection:Position", sh.observe(null, "TextSelection:Position",
@ -493,10 +491,15 @@ function greaterThan(n1, n2, msg) {
}); });
} }
// 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) { function pointEquals(p1, p2, msg) {
return Messaging.sendRequestForResult({ return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections", type: "Robocop:testInputSelections",
result: p1.equals(p2), result: truncPoint(p1).equals(truncPoint(p2)),
msg: msg + " : " + p1.toString() + " == " + p2.toString() msg: msg + " : " + p1.toString() + " == " + p2.toString()
}); });
} }
@ -504,7 +507,7 @@ function pointEquals(p1, p2, msg) {
function pointNotEquals(p1, p2, msg) { function pointNotEquals(p1, p2, msg) {
return Messaging.sendRequestForResult({ return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections", type: "Robocop:testInputSelections",
result: !p1.equals(p2), result: !truncPoint(p1).equals(truncPoint(p2)),
msg: msg + " : " + p1.toString() + " == " + p2.toString() msg: msg + " : " + p1.toString() + " == " + p2.toString()
}); });
} }
@ -512,7 +515,7 @@ function pointNotEquals(p1, p2, msg) {
function selectionExists(selection, msg) { function selectionExists(selection, msg) {
return Messaging.sendRequestForResult({ return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections", type: "Robocop:testInputSelections",
result: !selection.anchorPt.equals(selection.focusPt), result: !truncPoint(selection.anchorPt).equals(truncPoint(selection.focusPt)),
msg: msg + " : anchor:" + selection.anchorPt.toString() + msg: msg + " : anchor:" + selection.anchorPt.toString() +
" focus:" + selection.focusPt.toString() " focus:" + selection.focusPt.toString()
}); });
@ -521,7 +524,8 @@ function selectionExists(selection, msg) {
function selectionEquals(s1, s2, msg) { function selectionEquals(s1, s2, msg) {
return Messaging.sendRequestForResult({ return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections", 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 msg: msg
}); });
} }

View File

@ -518,19 +518,16 @@ function testRTL_moveFocusHandleDown() {
"RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"), "RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
selectionExists(changedSelection, "RTL Changed selection existed at points"), selectionExists(changedSelection, "RTL Changed selection existed at points"),
todo(false, "testRTL_moveFocusHandleDown: " + pointEquals(changedSelection.anchorPt, initialSelection.anchorPt,
// pointEquals(changedSelection.anchorPt, initialSelection.anchorPt, "RTL Changed selection focus handle moving down " +
"RTL Changed selection focus handle moving down " + "should not change anchor handle position."),
"should not change anchor handle position."), greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
todo(false, "testRTL_moveFocusHandleDown: " + "RTL Changed selection focusPt.y " +
// greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y, "should be greater than (below) changed 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,
// greaterThan(changedSelection.focusPt.y, initialSelection.focusPt.y, "RTL Changed selection focusPt.y " +
"RTL Changed selection focusPt.y " + "should be greater than (below) Initial selection focusPt.y"),
"should be greater than (below) Initial selection focusPt.y"),
]); ]);
} }
@ -585,24 +582,20 @@ function testRTL_moveFocusHandleUp() {
"RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"), "RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
selectionExists(changedSelection, "RTL Changed selection existed at points"), selectionExists(changedSelection, "RTL Changed selection existed at points"),
todo(false, "testRTL_moveFocusHandleUp: " + pointEquals(changedSelection.focusPt, initialSelection.anchorPt,
// pointEquals(changedSelection.focusPt, initialSelection.anchorPt, "RTL Reversed Changed selection focus handle moving up " +
"RTL Reversed Changed selection focus handle moving up " + "becomes new anchor handle, " +
"becomes new anchor handle, " + "new focus handle is initial anchor handle."),
"new focus handle is initial anchor handle."), greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
todo(false, "testRTL_moveFocusHandleUp: " + "RTL Reversed Changed selection focusPt.y " +
// greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y, "should be greater than (below) changed 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,
// is(changedSelection.focusPt.y, initialSelection.focusPt.y, "RTL Reversed Changed selection focusPt.y " +
"RTL Reversed Changed selection focusPt.y " + "should be equal to Initial selection focusPt.y"),
"should be equal to Initial selection focusPt.y"), lessThan(changedSelection.anchorPt.y, initialSelection.anchorPt.y,
todo(false, "testRTL_moveFocusHandleUp: " + "RTL Reversed Changed selection anchorPt.y " +
// lessThan(changedSelection.anchorPt.y, initialSelection.anchorPt.y, "should be less than (above) Initial selection anchorPt.y"),
"RTL Reversed Changed selection anchorPt.y " +
"should be less than (above) Initial selection anchorPt.y"),
]); ]);
} }
@ -657,19 +650,16 @@ function testRTL_moveAnchorHandleUp() {
"RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"), "RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
selectionExists(changedSelection, "RTL Changed selection existed at points"), selectionExists(changedSelection, "RTL Changed selection existed at points"),
todo(false, "testRTL_moveAnchorHandleUp: " + pointEquals(changedSelection.focusPt, initialSelection.focusPt,
// pointEquals(changedSelection.focusPt, initialSelection.focusPt, "RTL Changed selection anchor handle moving up " +
"RTL Changed selection anchor handle moving up " + "should not change focus handle position."),
"should not change focus handle position."), greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
todo(false, "testRTL_moveAnchorHandleUp: " + "RTL Changed selection focusPt.y " +
// greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y, "should be greater than (below) changed 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,
// lessThan(changedSelection.anchorPt.y, initialSelection.anchorPt.y, "RTL Changed selection anchorPt.y " +
"RTL Changed selection anchorPt.y " + "should be less than (above) Initial selection anchorPt.y"),
"should be less than (above) Initial selection anchorPt.y"),
]); ]);
} }
@ -724,24 +714,20 @@ function testRTL_moveAnchorHandleDown() {
"RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"), "RTL Initial selection anchorPt.x should be greater than (right of) focusPt.x"),
selectionExists(changedSelection, "RTL Changed selection existed at points"), selectionExists(changedSelection, "RTL Changed selection existed at points"),
todo(false, "testRTL_moveAnchorHandleDown: " + pointEquals(changedSelection.anchorPt, initialSelection.focusPt,
// pointEquals(changedSelection.anchorPt, initialSelection.focusPt, "RTL Reversed Changed selection anchor handle moving down " +
"RTL Reversed Changed selection anchor handle moving down " + "becomes new focus handle, " +
"becomes new focus handle, " + "new anchor handle is initial focus handle."),
"new anchor handle is initial focus handle."), greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y,
todo(false, "testRTL_moveAnchorHandleDown: " + "RTL Reversed Changed selection focusPt.y " +
// greaterThan(changedSelection.focusPt.y, changedSelection.anchorPt.y, "should be greater than (below) changed 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,
// is(changedSelection.anchorPt.y, initialSelection.anchorPt.y, "RTL Reversed Changed selection anchorPt.y " +
"RTL Reversed Changed selection anchorPt.y " + "should be equal to Initial selection anchorPt.y"),
"should be equal to Initial selection anchorPt.y"), greaterThan(changedSelection.focusPt.y, initialSelection.focusPt.y,
todo(false, "testRTL_moveAnchorHandleDown: " + "RTL Reversed Changed selection focusPt.y " +
// greaterThan(changedSelection.focusPt.y, initialSelection.focusPt.y, "should be greater than (below) Initial selection focusPt.y"),
"RTL Reversed Changed selection focusPt.y " +
"should be greater than (below) Initial selection focusPt.y"),
]); ]);
} }
@ -810,10 +796,15 @@ function greaterThan(n1, n2, msg) {
}); });
} }
// 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) { function pointEquals(p1, p2, msg) {
return Messaging.sendRequestForResult({ return Messaging.sendRequestForResult({
type: "Robocop:testTextareaSelections", type: "Robocop:testTextareaSelections",
result: p1.equals(p2), result: truncPoint(p1).equals(truncPoint(p2)),
msg: msg + " : " + p1.toString() + " == " + p2.toString() msg: msg + " : " + p1.toString() + " == " + p2.toString()
}); });
} }
@ -821,7 +812,7 @@ function pointEquals(p1, p2, msg) {
function pointNotEquals(p1, p2, msg) { function pointNotEquals(p1, p2, msg) {
return Messaging.sendRequestForResult({ return Messaging.sendRequestForResult({
type: "Robocop:testTextareaSelections", type: "Robocop:testTextareaSelections",
result: !p1.equals(p2), result: !truncPoint(p1).equals(truncPoint(p2)),
msg: msg + " : " + p1.toString() + " == " + p2.toString() msg: msg + " : " + p1.toString() + " == " + p2.toString()
}); });
} }
@ -829,7 +820,7 @@ function pointNotEquals(p1, p2, msg) {
function selectionExists(selection, msg) { function selectionExists(selection, msg) {
return Messaging.sendRequestForResult({ return Messaging.sendRequestForResult({
type: "Robocop:testTextareaSelections", type: "Robocop:testTextareaSelections",
result: !selection.anchorPt.equals(selection.focusPt), result: !truncPoint(selection.anchorPt).equals(truncPoint(selection.focusPt)),
msg: msg + " : anchor:" + selection.anchorPt.toString() + msg: msg + " : anchor:" + selection.anchorPt.toString() +
" focus:" + selection.focusPt.toString() " focus:" + selection.focusPt.toString()
}); });

View File

@ -40,7 +40,11 @@ var SelectionHandler = {
// stored here are relative to the _contentWindow window. // stored here are relative to the _contentWindow window.
_cache: null, _cache: null,
_activeType: 0, // TYPE_NONE _activeType: 0, // TYPE_NONE
_draggingHandles: false, // True while user drags text selection handles _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 _ignoreCompositionChanges: false, // Persist caret during IME composition updates
_prevHandlePositions: [], // Avoid issuing duplicate "TextSelection:Position" messages _prevHandlePositions: [], // Avoid issuing duplicate "TextSelection:Position" messages
_deferCloseTimer: null, // Used to defer _closeSelection() actions during programmatic changes _deferCloseTimer: null, // Used to defer _closeSelection() actions during programmatic changes
@ -152,11 +156,12 @@ var SelectionHandler = {
} }
break; break;
} }
case "TextSelection:Move": { case "TextSelection:Move": {
let data = JSON.parse(aData); let data = JSON.parse(aData);
if (this._activeType == this.TYPE_SELECTION) { if (this._activeType == this.TYPE_SELECTION) {
this._startDraggingHandles(); 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) { } else if (this._activeType == this.TYPE_CURSOR) {
this._startDraggingHandles(); this._startDraggingHandles();
@ -170,30 +175,16 @@ var SelectionHandler = {
} }
break; break;
} }
case "TextSelection:Position": { case "TextSelection:Position": {
if (this._activeType == this.TYPE_SELECTION) { if (this._activeType == this.TYPE_SELECTION) {
this._startDraggingHandles(); this._startDraggingHandles();
this._ensureSelectionDirection();
// 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._stopDraggingHandles(); this._stopDraggingHandles();
this._updateCacheForSelection();
this._positionHandles(); this._positionHandles();
// Changes to handle position can affect selection context and actionbar display // Changes to handle position can affect selection context and actionbar display
this._updateMenu(); this._updateMenu();
@ -225,6 +216,9 @@ var SelectionHandler = {
_startDraggingHandles: function sh_startDraggingHandles() { _startDraggingHandles: function sh_startDraggingHandles() {
if (!this._draggingHandles) { if (!this._draggingHandles) {
this._draggingHandles = true; this._draggingHandles = true;
let selection = this._getSelection();
this._dragStartAnchorOffset = selection.anchorOffset;
this._dragStartFocusOffset = selection.focusOffset;
Messaging.sendRequest({ type: "TextSelection:DraggingHandle", dragging: true }); Messaging.sendRequest({ type: "TextSelection:DraggingHandle", dragging: true });
} }
}, },
@ -234,6 +228,8 @@ var SelectionHandler = {
_stopDraggingHandles: function sh_stopDraggingHandles() { _stopDraggingHandles: function sh_stopDraggingHandles() {
if (this._draggingHandles) { if (this._draggingHandles) {
this._draggingHandles = false; this._draggingHandles = false;
this._dragStartAnchorOffset = null;
this._dragStartFocusOffset = null;
Messaging.sendRequest({ type: "TextSelection:DraggingHandle", dragging: false }); Messaging.sendRequest({ type: "TextSelection:DraggingHandle", dragging: false });
} }
}, },
@ -862,35 +858,21 @@ var SelectionHandler = {
}, },
/* /*
* Helper function for moving the selection inside an editable element. * Moves the selection as the user drags a handle.
* * @param handleType: Specifies either the anchor or the focus handle.
* @param aAnchorX the stationary handle's x-coordinate in client coordinates * @param handlePt: selection point in client coordinates.
* @param aX the moved handle's x-coordinate in client coordinates
* @param aCaretPos the current position of the caret
*/ */
_moveSelectionInEditable: function sh_moveSelectionInEditable(aAnchorX, aX, aCaretPos) { _moveSelection: function sh_moveSelection(handleType, handlePt) {
let anchorOffset = aX < aAnchorX ? this._targetElement.selectionEnd let isAnchorHandle = (handleType == this.HANDLE_TYPE_ANCHOR);
: this._targetElement.selectionStart;
let newOffset = aCaretPos.offset;
let [start, end] = anchorOffset <= newOffset ?
[anchorOffset, newOffset] :
[newOffset, anchorOffset];
this._targetElement.setSelectionRange(start, end);
},
/* // Determine new caret position from handlePt, exit if user
* Moves the selection as the user drags a selection handle. // moved it offscreen.
*
* @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.
let viewOffset = this._getViewOffset(); 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) { if (!caretPos) {
// User moves handle offscreen while positioning
return; return;
} }
@ -900,36 +882,36 @@ var SelectionHandler = {
return; return;
} }
// Update the cache as the handle is dragged (keep the cache in client coordinates). // Update the Selection for editable elements. Selection Change
if (aIsStartHandle) { // logic is the same, regardless of RTL/LTR. Selection direction is
this._cache.anchorPt.x = aX; // maintained always forward (startOffset <= endOffset).
this._cache.anchorPt.y = aY; if (targetIsEditable) {
} else { let start = this._dragStartAnchorOffset;
this._cache.focusPt.x = aX; let end = this._dragStartFocusOffset;
this._cache.focusPt.y = aY; 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(); let selection = this._getSelection();
if (isAnchorHandle) {
// The handles work the same on both LTR and RTL pages, but the anchor/focus nodes let focusNode = selection.focusNode;
// are reversed, so we need to reverse the logic to extend the selection. let focusOffset = selection.focusOffset;
if ((aIsStartHandle && !this._isRTL) || (!aIsStartHandle && this._isRTL)) { selection.collapse(caretPos.offsetNode, caretPos.offset);
if (targetIsEditable) { selection.extend(focusNode, focusOffset);
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);
}
} else { } else {
if (targetIsEditable) { selection.extend(caretPos.offsetNode, caretPos.offset);
let anchorX = this._isRTL ? this._cache.focusPt.x : this._cache.anchorPt.x;
this._moveSelectionInEditable(anchorX, aX, caretPos);
} else {
selection.extend(caretPos.offsetNode, caretPos.offset);
}
} }
}, },
@ -1149,29 +1131,56 @@ var SelectionHandler = {
return offset; return offset;
}, },
// Returns true if the selection has been reversed. Takes optional aIsStartHandle /*
// param to decide whether the selection has been reversed. * The direction of the Selection is ensured for editables while the user drags
_updateCacheForSelection: function sh_updateCacheForSelection(aIsStartHandle) { * 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(); let rects = this._getSelection().getRangeAt(0).getClientRects();
if (!rects[0]) { if (rects.length == 0) {
// nsISelection object exists, but there's nothing actually selected // nsISelection object exists, but there's nothing actually selected
throw "Failed to update cache for invalid selection"; throw "Failed to update cache for invalid selection";
} }
let start = { x: this._isRTL ? rects[0].right : rects[0].left, y: rects[0].bottom }; let anchorIdx = 0;
let end = { x: this._isRTL ? rects[rects.length - 1].left : rects[rects.length - 1].right, y: rects[rects.length - 1].bottom }; let focusIdx = rects.length - 1;
if (this._isRTL) {
let selectionReversed = false; // Right-to-Left (ie: Hebrew) anchorPt is on right, focusPt is on left.
if (this._cache.anchorPt) { this._cache.anchorPt = new Point(rects[anchorIdx].right, rects[anchorIdx].bottom);
// 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) this._cache.focusPt = new Point(rects[focusIdx].left, rects[focusIdx].bottom);
selectionReversed = (aIsStartHandle && (end.y > this._cache.focusPt.y || (end.y == this._cache.focusPt.y && end.x > this._cache.focusPt.x))) || } else {
(!aIsStartHandle && (start.y < this._cache.anchorPt.y || (start.y == this._cache.anchorPt.y && start.x < this._cache.anchorPt.x))); // 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) { _getHandlePositions: function sh_getHandlePositions(scroll) {

View File

@ -298,8 +298,8 @@ XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
return ContentAreaUtils; return ContentAreaUtils;
}); });
XPCOMUtils.defineLazyModuleGetter(this, "Rect", XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
"resource://gre/modules/Geometry.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Point", "resource://gre/modules/Geometry.jsm");
function resolveGeckoURI(aURI) { function resolveGeckoURI(aURI) {
if (!aURI) if (!aURI)