diff --git a/browser/metro/base/content/Util.js b/browser/metro/base/content/Util.js index 50cba6d707a4..4cd4039f54d1 100644 --- a/browser/metro/base/content/Util.js +++ b/browser/metro/base/content/Util.js @@ -179,6 +179,12 @@ let Util = { * Rect and nsIDOMRect utilities */ + getCleanRect: function getCleanRect() { + return { + left: 0, top: 0, right: 0, bottom: 0 + }; + }, + pointWithinRect: function pointWithinRect(aX, aY, aRect) { return (aRect.left < aX && aRect.top < aY && aRect.right > aX && aRect.bottom > aY); diff --git a/browser/metro/base/content/contenthandlers/SelectionHandler.js b/browser/metro/base/content/contenthandlers/SelectionHandler.js index 4ad140cb6ef4..6b9615b3d11a 100644 --- a/browser/metro/base/content/contenthandlers/SelectionHandler.js +++ b/browser/metro/base/content/contenthandlers/SelectionHandler.js @@ -313,10 +313,15 @@ var SelectionHandler = { }, /* - * Selection clear event handler + * Selection clear message handler + * + * @param aClearFocus requests that the focus also be cleared. */ - _onSelectionClear: function _onSelectionClear() { + _onSelectionClear: function _onSelectionClear(aClearFocus) { this._clearSelection(); + if (aClearFocus && this._targetElement) { + this._targetElement.blur(); + } }, /* @@ -424,6 +429,7 @@ var SelectionHandler = { this._cache.updateStart = aUpdateStart; this._cache.updateEnd = aUpdateEnd; this._cache.updateCaret = aUpdateCaret || false; + this._cache.targetIsEditable = this._targetIsEditable; // Get monocles positioned correctly sendAsyncMessage("Content:SelectionRange", this._cache); @@ -1049,7 +1055,7 @@ var SelectionHandler = { break; case "Browser:SelectionClear": - this._onSelectionClear(); + this._onSelectionClear(json.clearFocus); break; case "Browser:SelectionDebug": diff --git a/browser/metro/base/content/helperui/SelectionHelperUI.js b/browser/metro/base/content/helperui/SelectionHelperUI.js index 0154c69c2752..121bee3ede6d 100644 --- a/browser/metro/base/content/helperui/SelectionHelperUI.js +++ b/browser/metro/base/content/helperui/SelectionHelperUI.js @@ -227,6 +227,7 @@ var SelectionHelperUI = { _activeSelectionRect: null, _selectionHandlerActive: false, _selectionMarkIds: [], + _targetIsEditable: false, /* * Properties @@ -366,12 +367,16 @@ var SelectionHelperUI = { /* * closeEditSessionAndClear - * + * * Closes an active edit session and shuts down. Clears any selection region * associated with the edit session. + * + * @param aClearFocus bool indicating if the selection handler should also + * clear the focus element. optional, the default is false. */ - closeEditSessionAndClear: function closeEditSessionAndClear() { - this._sendAsyncMessage("Browser:SelectionClear"); + closeEditSessionAndClear: function closeEditSessionAndClear(aClearFocus) { + let clearFocus = aClearFocus || false; + this._sendAsyncMessage("Browser:SelectionClear", { clearFocus: clearFocus }); this.closeEditSession(); }, @@ -388,6 +393,10 @@ var SelectionHelperUI = { "selectionhandle-mark2", "selectionhandle-mark3"]; + // Init selection rect info + this._activeSelectionRect = Util.getCleanRect(); + this._targetElementRect = Util.getCleanRect(); + // SelectionHandler messages messageManager.addMessageListener("Content:SelectionRange", this); messageManager.addMessageListener("Content:SelectionCopied", this); @@ -587,34 +596,53 @@ var SelectionHelperUI = { /* * _onTap * - * Handles taps that move the current caret around text text - * edits. Also will clear active selection if it is present. - * Future: changing slection modes by tapping on a monocle. + * Handles taps that move the current caret around in text edits, + * clear active selection and focus when neccessary, or change + * modes. + * Future: changing selection modes by tapping on a monocle. */ _onTap: function _onTap(aEvent) { + // Check for a tap on a monocle if (this.startMark.hitTest(aEvent.clientX, aEvent.clientY) || this.endMark.hitTest(aEvent.clientX, aEvent.clientY)) { + aEvent.stopPropagation(); + aEvent.preventDefault(); return; } - // If the tap is within the target element and the caret monocle is + // Is the tap point within the bound of the target element? This + // is useful if we are dealing with some sort of input control. + // Not so much if the target is a page or div. + let pointInTargetElement = + Util.pointWithinRect(aEvent.clientX, aEvent.clientY, + this._targetElementRect); + + // If the tap is within an editable element and the caret monocle is // active, update the caret. - if (this.caretMark.visible && - Util.pointWithinRect(aEvent.clientX, aEvent.clientY, - this._targetElementRect)) { + if (this.caretMark.visible && pointInTargetElement) { // Move the caret this._setCaretPositionAtPoint(aEvent.clientX, aEvent.clientY); return; } + // if the target is editable and the user clicks off the target + // clear selection and remove focus from the input. if (this.caretMark.visible) { - // shutdown selection - this.closeEditSessionAndClear(); + // shutdown and clear selection and remove focus + this.closeEditSessionAndClear(true); return; } - // If we have active selection w/monocles, don't let random taps - // kill selection. + // If the target is editable, we have active selection, and + // the user taps off the input, clear selection and shutdown. + if (this.startMark.visible && !pointInTargetElement && + this._targetIsEditable) { + this.closeEditSessionAndClear(true); + return; + } + + // If we have active selection in anything else don't let the event get + // to content. Prevents random taps from killing active selection. aEvent.stopPropagation(); aEvent.preventDefault(); }, @@ -660,6 +688,7 @@ var SelectionHelperUI = { } this._activeSelectionRect = json.selection; this._targetElementRect = json.element; + this._targetIsEditable = json.targetIsEditable; }, _onSelectionFail: function _onSelectionFail() {