diff --git a/browser/metro/base/content/helperui/ChromeSelectionHandler.js b/browser/metro/base/content/helperui/ChromeSelectionHandler.js index af458b07013..2c2ba95a405 100644 --- a/browser/metro/base/content/helperui/ChromeSelectionHandler.js +++ b/browser/metro/base/content/helperui/ChromeSelectionHandler.js @@ -8,11 +8,8 @@ let Ci = Components.interfaces; -const kCaretMode = 1; -const kSelectionMode = 2; - var ChromeSelectionHandler = { - _mode: kSelectionMode, + _mode: this._SELECTION_MODE, /************************************************* * Messaging wrapper @@ -33,6 +30,10 @@ var ChromeSelectionHandler = { * General selection start method for both caret and selection mode. */ _onSelectionAttach: function _onSelectionAttach(aJson) { + // Clear previous ChromeSelectionHandler state. + this._deactivate(); + + // Initialize ChromeSelectionHandler state. this._domWinUtils = Util.getWindowUtils(window); this._contentWindow = window; this._targetElement = aJson.target; @@ -54,11 +55,14 @@ var ChromeSelectionHandler = { return; } + // Add a listener to respond to programmatic selection changes. + selection.QueryInterface(Ci.nsISelectionPrivate).addSelectionListener(this); + if (!selection.isCollapsed) { - this._mode = kSelectionMode; + this._mode = this._SELECTION_MODE; this._updateSelectionUI("start", true, true); } else { - this._mode = kCaretMode; + this._mode = this._CARET_MODE; this._updateSelectionUI("caret", false, false, true); } @@ -156,9 +160,9 @@ var ChromeSelectionHandler = { return; } this._updateSelectionUI("update", - this._mode == kSelectionMode, - this._mode == kSelectionMode, - this._mode == kCaretMode); + this._mode == this._SELECTION_MODE, + this._mode == this._SELECTION_MODE, + this._mode == this._CARET_MODE); }, /* @@ -188,7 +192,7 @@ var ChromeSelectionHandler = { // We bail if things get out of sync here implying we missed a message. this._selectionMoveActive = true; - this._mode = kSelectionMode; + this._mode = this._SELECTION_MODE; // Update the position of the selection marker that is *not* // being dragged. @@ -266,7 +270,7 @@ var ChromeSelectionHandler = { /* * _clearSelection * - * Clear existing selection if it exists and reset our internla state. + * Clear existing selection if it exists and reset our internal state. */ _clearSelection: function _clearSelection() { let selection = this._getSelection(); @@ -278,9 +282,30 @@ var ChromeSelectionHandler = { /* * _closeSelection * - * Shuts SelectionHandler down. + * Shuts ChromeSelectionHandler and SelectionHelperUI down. */ _closeSelection: function _closeSelection() { + this._deactivate(); + this.sendAsync("Content:HandlerShutdown", {}); + }, + + /* + * _deactivate + * + * Resets ChromeSelectionHandler state, previously initialized in + * general selection start-method |_onSelectionAttach()|. + */ + _deactivate: function _deactivate() { + // Remove our selection notification listener. + let selection = this._getSelection(); + if (selection) { + try { + selection.QueryInterface(Ci.nsISelectionPrivate).removeSelectionListener(this); + } catch(e) { + // Fail safe during multiple _deactivate() calls. + } + } + this._clearTimers(); this._cache = null; this._contentWindow = null; @@ -291,7 +316,7 @@ var ChromeSelectionHandler = { this._selectionMoveActive = false; this._domWinUtils = null; this._targetIsEditable = false; - this.sendAsync("Content:HandlerShutdown", {}); + this._mode = null; }, get hasSelection() { diff --git a/browser/metro/base/content/library/SelectionPrototype.js b/browser/metro/base/content/library/SelectionPrototype.js index 9ba512c95b9..0407be612a0 100644 --- a/browser/metro/base/content/library/SelectionPrototype.js +++ b/browser/metro/base/content/library/SelectionPrototype.js @@ -53,6 +53,9 @@ dump("### SelectionPrototype.js loaded\n"); var SelectionPrototype = function() { } SelectionPrototype.prototype = { + _CARET_MODE: 1, // Single monocle mode (Collapsed caret). + _SELECTION_MODE: 2, // Double monocle mode (Selection w/Text). + _debugEvents: false, _cache: {}, _targetElement: null, @@ -169,6 +172,28 @@ SelectionPrototype.prototype = { this._debugEvents = aMsg.dumpEvents; }, + /* + * Selection Changed notification listener. This allows us to respond to selection changes + * introduced programmatically by Gecko events, target-page support code, etc. + */ + notifySelectionChanged: function notifySelectionChanged(aDocument, aSelection, aReason) { + // Ignore user generated selectionChange notifications during monocle/marker movement. + if ((typeof SelectionHelperUI != "undefined") && SelectionHelperUI.hasActiveDrag) { + return; + } + + // Ignore selectionChange notifications, unless reason is mouseup, or unknown. + if (!(aReason & Ci.nsISelectionListener.MOUSEUP_REASON) && + (aReason != Ci.nsISelectionListener.NO_REASON)) { + return; + } + + // If in selection mode, and we have a selection, update it. + if (this._mode == this._SELECTION_MODE && !aSelection.isCollapsed) { + this._updateSelectionUI("update", true, true); + } + }, + /************************************************* * Selection api */ diff --git a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js index 647f8e7833f..c9322eb0cf2 100644 --- a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js +++ b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js @@ -232,7 +232,7 @@ gTests.push({ gTests.push({ desc: "Bug 957646 - Selection monocles sometimes don't display when tapping" + - " text ion the nav bar.", + " text in the nav bar.", run: function() { yield showNavBar(); @@ -250,6 +250,43 @@ gTests.push({ } }); +gTests.push({ + desc: "Bug 972574 - Monocles not matching selection after double tap" + + " in URL text field.", + run: function() { + yield showNavBar(); + + let MARGIN_OF_ERROR = 15; + let EST_URLTEXT_WIDTH = 125; + + let edit = document.getElementById("urlbar-edit"); + edit.value = "http://www.wikipedia.org/"; + + // Determine a tap point centered on URL. + let editRectangle = edit.getBoundingClientRect(); + let midX = editRectangle.left + Math.ceil(EST_URLTEXT_WIDTH / 2); + let midY = editRectangle.top + Math.ceil(editRectangle.height / 2); + + // Tap inside the input for fluffing to take effect. + sendTap(window, midX, midY); + + // Double-tap inside the input to selectALL. + sendDoubleTap(window, midX, midY); + + // Check for start/end monocles positioned within accepted margins. + checkMonoclePositionRange("start", + Math.ceil(editRectangle.left - MARGIN_OF_ERROR), + Math.ceil(editRectangle.left + MARGIN_OF_ERROR), + Math.ceil(editRectangle.top + editRectangle.height - MARGIN_OF_ERROR), + Math.ceil(editRectangle.top + editRectangle.height + MARGIN_OF_ERROR)); + checkMonoclePositionRange("end", + Math.ceil(editRectangle.left + EST_URLTEXT_WIDTH - MARGIN_OF_ERROR), + Math.ceil(editRectangle.left + EST_URLTEXT_WIDTH + MARGIN_OF_ERROR), + Math.ceil(editRectangle.top + editRectangle.height - MARGIN_OF_ERROR), + Math.ceil(editRectangle.top + editRectangle.height + MARGIN_OF_ERROR)); + } +}); + gTests.push({ desc: "Bug 972428 - grippers not appearing under the URL field when adding " + "text.",