diff --git a/browser/metro/base/content/bindings/selectionoverlay.xml b/browser/metro/base/content/bindings/selectionoverlay.xml index e29bd522ed8..151d1e75a74 100644 --- a/browser/metro/base/content/bindings/selectionoverlay.xml +++ b/browser/metro/base/content/bindings/selectionoverlay.xml @@ -10,9 +10,9 @@ - diff --git a/browser/metro/base/content/contenthandlers/SelectionHandler.js b/browser/metro/base/content/contenthandlers/SelectionHandler.js index 5a179480151..4ad140cb6ef 100644 --- a/browser/metro/base/content/contenthandlers/SelectionHandler.js +++ b/browser/metro/base/content/contenthandlers/SelectionHandler.js @@ -411,8 +411,15 @@ var SelectionHandler = { } // Updates this._cache content selection position data which we send over - // to SelectionHelperUI. - this._updateUIMarkerRects(selection); + // to SelectionHelperUI. Note updateUIMarkerRects will fail if there isn't + // any selection in the page. This can happen when we start a monocle drag + // but haven't dragged enough to create selection. Just return. + try { + this._updateUIMarkerRects(selection); + } catch (ex) { + Util.dumpLn(ex.message); + return; + } this._cache.updateStart = aUpdateStart; this._cache.updateEnd = aUpdateEnd; diff --git a/browser/metro/base/content/helperui/SelectionHelperUI.js b/browser/metro/base/content/helperui/SelectionHelperUI.js index 8a75f073ecc..0154c69c275 100644 --- a/browser/metro/base/content/helperui/SelectionHelperUI.js +++ b/browser/metro/base/content/helperui/SelectionHelperUI.js @@ -76,6 +76,7 @@ function Marker(aParent, aTag, aElementId, xPos, yPos) { this._yPos = yPos; this._selectionHelperUI = aParent; this._element = document.getElementById(aElementId); + this._elementId = aElementId; // These get picked in input.js and receives drag input this._element.customDragger = new MarkerDragger(this); this.tag = aTag; @@ -83,6 +84,7 @@ function Marker(aParent, aTag, aElementId, xPos, yPos) { Marker.prototype = { _element: null, + _elementId: "", _selectionHelperUI: null, _xPos: 0, _yPos: 0, @@ -171,8 +173,12 @@ Marker.prototype = { moveBy: function moveBy(aDx, aDy, aClientX, aClientY) { this._xPos -= aDx; this._yPos -= aDy; - this._selectionHelperUI.markerDragMove(this); - this._setPosition(); + let direction = (aDx < 0 || aDy < 0 ? "end" : "start"); + // We may swap markers in markerDragMove. If markerDragMove + // returns true keep processing, otherwise get out of here. + if (this._selectionHelperUI.markerDragMove(this, direction)) { + this._setPosition(); + } }, hitTest: function hitTest(aX, aY) { @@ -187,6 +193,23 @@ Marker.prototype = { return true; return false; }, + + swapMonocle: function swapMonocle(aCaret) { + let targetElement = aCaret._element; + let targetElementId = aCaret._elementId; + + aCaret._element = this._element; + aCaret._element.customDragger._marker = aCaret; + aCaret._elementId = this._elementId; + + this._xPos = aCaret._xPos; + this._yPos = aCaret._yPos; + this._element = targetElement; + this._element.customDragger._marker = this; + this._elementId = targetElementId; + this._element.visible = true; + }, + }; /* @@ -203,24 +226,29 @@ var SelectionHelperUI = { _movement: { active: false, x:0, y: 0 }, _activeSelectionRect: null, _selectionHandlerActive: false, + _selectionMarkIds: [], + + /* + * Properties + */ get startMark() { if (this._startMark == null) { - this._startMark = new Marker(this, "start", "selectionhandle-start", 0, 0); + this._startMark = new Marker(this, "start", this._selectionMarkIds.pop(), 0, 0); } return this._startMark; }, get endMark() { if (this._endMark == null) { - this._endMark = new Marker(this, "end", "selectionhandle-end", 0, 0); + this._endMark = new Marker(this, "end", this._selectionMarkIds.pop(), 0, 0); } return this._endMark; }, get caretMark() { if (this._caretMark == null) { - this._caretMark = new Marker(this, "caret", "selectionhandle-caret", 0, 0); + this._caretMark = new Marker(this, "caret", this._selectionMarkIds.pop(), 0, 0); } return this._caretMark; }, @@ -229,6 +257,20 @@ var SelectionHelperUI = { return document.getElementById("selection-overlay"); }, + /* + * isActive (prop) + * + * Determines if an edit session is currently active. + */ + get isActive() { + return (this._msgTarget && + this._selectionHandlerActive); + }, + + /* + * Public apis + */ + /* * openEditSession * @@ -241,10 +283,6 @@ var SelectionHelperUI = { this._init(aContent); this._setupDebugOptions(); - // Set the track bounds for each marker NIY - this.startMark.setTrackBounds(aClientX, aClientY); - this.endMark.setTrackBounds(aClientX, aClientY); - // Send this over to SelectionHandler in content, they'll message us // back with information on the current selection. SelectionStart // takes client coordinates. @@ -266,10 +304,6 @@ var SelectionHelperUI = { this._init(aContent); this._setupDebugOptions(); - // Set the track bounds for each marker NIY - this.startMark.setTrackBounds(aClientX, aClientY); - this.endMark.setTrackBounds(aClientX, aClientY); - // Send this over to SelectionHandler in content, they'll message us // back with information on the current selection. SelectionAttach // takes client coordinates. @@ -289,7 +323,7 @@ var SelectionHelperUI = { * Note the caret marker is pretty limited in functionality. The * only thing is can do is be displayed at the caret position. * Once the user starts a drag, the caret marker is hidden, and - * the start and end markers take over. (TBD) + * the start and end markers take over. * * @param aClientX, aClientY client coordiates of the tap that * initiated the session. @@ -319,16 +353,6 @@ var SelectionHelperUI = { return false; }, - /* - * isActive (prop) - * - * Determines if an edit session is currently active. - */ - get isActive() { - return (this._msgTarget && - this._selectionHandlerActive); - }, - /* * closeEditSession * @@ -359,6 +383,11 @@ var SelectionHelperUI = { // store the target message manager this._msgTarget = aMsgTarget; + // Init our list of available monocle ids + this._selectionMarkIds = ["selectionhandle-mark1", + "selectionhandle-mark2", + "selectionhandle-mark3"]; + // SelectionHandler messages messageManager.addMessageListener("Content:SelectionRange", this); messageManager.addMessageListener("Content:SelectionCopied", this); @@ -419,10 +448,11 @@ var SelectionHelperUI = { if (this._caretMark != null) this._caretMark.shutdown(); - delete this._startMark; - delete this._endMark; - delete this._caretMark; + this._startMark = null; + this._endMark = null; + this._caretMark = null; + this._selectionMarkIds = []; this._msgTarget = null; this._activeSelectionRect = null; this._selectionHandlerActive = false; @@ -435,6 +465,54 @@ var SelectionHelperUI = { * Utilities */ + /* + * _swapCaretMarker + * + * Swap two drag markers - used when transitioning from caret mode + * to selection mode. We take the current caret marker (which is in a + * drag state) and swap it out with one of the selection markers. + */ + _swapCaretMarker: function _swapCaretMarker(aDirection) { + let targetMark = null; + if (aDirection == "start") + targetMark = this.startMark; + else + targetMark = this.endMark; + let caret = this.caretMark; + targetMark.swapMonocle(caret); + let id = caret._elementId; + caret.shutdown(); + this._caretMark = null; + this._selectionMarkIds.push(id); + }, + + /* + * _transitionFromCaretToSelection + * + * Transitions from caret mode to text selection mode. + */ + _transitionFromCaretToSelection: function _transitionFromCaretToSelection(aDirection) { + // Get selection markers initialized if they aren't already + { let mark = this.startMark; mark = this.endMark; } + + // Swap the caret marker out for the start or end marker depending + // on direction. + this._swapCaretMarker(aDirection); + + let targetMark = null; + if (aDirection == "start") + targetMark = this.startMark; + else + targetMark = this.endMark; + // Start the selection monocle drag. SelectionHandler relies on this + // for getting initialized. This will also trigger a message back for + // monocle positioning. Note, markerDragMove is still on the stack in + // this call! + targetMark._setPosition(); + this.markerDragStart(targetMark); + this.markerDragMove(targetMark, aDirection); + }, + /* * _setupDebugOptions * @@ -731,13 +809,17 @@ var SelectionHelperUI = { this._sendAsyncMessage("Browser:SelectionMoveEnd", json); }, - markerDragMove: function markerDragMove(aMarker) { + markerDragMove: function markerDragMove(aMarker, aDirection) { let json = this._getMarkerBaseMessage(); json.change = aMarker.tag; if (aMarker.tag == "caret") { - this._sendAsyncMessage("Browser:CaretMove", json); - return; + // We are going to transition from caret browsing mode to selection mode + // on a drag. So swap the caret monocle for a start or end monocle + // depending on the direction of the drag, and start selecting text. + this._transitionFromCaretToSelection(aDirection); + return false; } this._sendAsyncMessage("Browser:SelectionMove", json); + return true; }, }; diff --git a/browser/metro/base/content/input.js b/browser/metro/base/content/input.js index 0d836c5e5cc..fbaff061b6d 100644 --- a/browser/metro/base/content/input.js +++ b/browser/metro/base/content/input.js @@ -146,10 +146,12 @@ var TouchModule = { // once we get omtc and the apzc. Currently though dblclick is delivered to // content and triggers selection of text, so fire up the SelectionHelperUI // once selection is present. - setTimeout(function () { - SelectionHelperUI.attachEditSession(Browser.selectedTab.browser, - aEvent.clientX, aEvent.clientY); - }, 50); + if (!SelectionHelperUI.isActive) { + setTimeout(function () { + SelectionHelperUI.attachEditSession(Browser.selectedTab.browser, + aEvent.clientX, aEvent.clientY); + }, 50); + } break; } } diff --git a/browser/metro/theme/browser.css b/browser/metro/theme/browser.css index 927a60d311a..78a3ef41a28 100644 --- a/browser/metro/theme/browser.css +++ b/browser/metro/theme/browser.css @@ -934,18 +934,18 @@ setting[type="directory"] > .preferences-alignment { /* Text selection handles */ -#selectionhandle-start, -#selectionhandle-end, -#selectionhandle-caret { +#selectionhandle-mark1, +#selectionhandle-mark2, +#selectionhandle-mark3 { border: 0px solid gray; padding: 0px; margin-top: -30px; margin-left: -18px; } -#selectionhandle-start, -#selectionhandle-end, -#selectionhandle-caret { +#selectionhandle-mark1, +#selectionhandle-mark2, +#selectionhandle-mark3 { list-style-image: url("chrome://browser/skin/images/selection-monocle.png"); }