browser/metro/base/content/helperui/ChromeSelectionHandler.js

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 /*
     6  * Selection handler for chrome text inputs
     7  */
     9 let Ci = Components.interfaces;
    11 var ChromeSelectionHandler = {
    12   _mode: this._SELECTION_MODE,
    14   /*************************************************
    15    * Messaging wrapper
    16    */
    18   sendAsync: function sendAsync(aMsg, aJson) {
    19     SelectionHelperUI.receiveMessage({ 
    20       name: aMsg,
    21       json: aJson
    22     });
    23   },
    25   /*************************************************
    26    * Browser event handlers
    27    */
    29   /*
    30    * General selection start method for both caret and selection mode.
    31    */
    32   _onSelectionAttach: function _onSelectionAttach(aJson) {
    33     // Clear previous ChromeSelectionHandler state.
    34     this._deactivate();
    36     // Initialize ChromeSelectionHandler state.
    37     this._domWinUtils = Util.getWindowUtils(window);
    38     this._contentWindow = window;
    39     this._targetElement = aJson.target;
    40     this._targetIsEditable = Util.isTextInput(this._targetElement) ||
    41         this._targetElement instanceof Ci.nsIDOMXULTextBoxElement;
    42     if (!this._targetIsEditable) {
    43       this._onFail("not an editable?", this._targetElement);
    44       return;
    45     }
    47     let selection = this._getSelection();
    48     if (!selection) {
    49       this._onFail("no selection.");
    50       return;
    51     }
    53     if (!this._getTargetElementValue()) {
    54       this._onFail("Target element does not contain any content to select.");
    55       return;
    56     }
    58     // Add a listener to respond to programmatic selection changes.
    59     selection.QueryInterface(Ci.nsISelectionPrivate).addSelectionListener(this);
    61     if (!selection.isCollapsed) {
    62       this._mode = this._SELECTION_MODE;
    63       this._updateSelectionUI("start", true, true);
    64     } else {
    65       this._mode = this._CARET_MODE;
    66       this._updateSelectionUI("caret", false, false, true);
    67     }
    69     this._targetElement.addEventListener("blur", this, false);
    70   },
    72   /*
    73    * Selection monocle start move event handler
    74    */
    75   _onSelectionMoveStart: function _onSelectionMoveStart(aMsg) {
    76     if (!this.targetIsEditable) {
    77       this._onFail("_onSelectionMoveStart with bad targetElement.");
    78       return;
    79     }
    81     if (this._selectionMoveActive) {
    82       this._onFail("mouse is already down on drag start?");
    83       return;
    84     }
    86     // We bail if things get out of sync here implying we missed a message.
    87     this._selectionMoveActive = true;
    89     if (this._targetIsEditable) {
    90       // If we're coming out of an out-of-bounds scroll, the node the user is
    91       // trying to drag may be hidden (the monocle will be pegged to the edge
    92       // of the edit). Make sure the node the user wants to move is visible
    93       // and has focus.
    94       this._updateInputFocus(aMsg.change);
    95     }
    97     // Update the position of our selection monocles
    98     this._updateSelectionUI("update", true, true);
    99   },
   101   /*
   102    * Selection monocle move event handler
   103    */
   104   _onSelectionMove: function _onSelectionMove(aMsg) {
   105     if (!this.targetIsEditable) {
   106       this._onFail("_onSelectionMove with bad targetElement.");
   107       return;
   108     }
   110     if (!this._selectionMoveActive) {
   111       this._onFail("mouse isn't down for drag move?");
   112       return;
   113     }
   115     this._handleSelectionPoint(aMsg, false);
   116   },
   118   /*
   119    * Selection monocle move finished event handler
   120    */
   121   _onSelectionMoveEnd: function _onSelectionMoveComplete(aMsg) {
   122     if (!this.targetIsEditable) {
   123       this._onFail("_onSelectionMoveEnd with bad targetElement.");
   124       return;
   125     }
   127     if (!this._selectionMoveActive) {
   128       this._onFail("mouse isn't down for drag move?");
   129       return;
   130     }
   132     this._handleSelectionPoint(aMsg, true);
   133     this._selectionMoveActive = false;
   135     // Clear any existing scroll timers
   136     this._clearTimers();
   138     // Update the position of our selection monocles
   139     this._updateSelectionUI("end", true, true);
   140   },
   142   _onSelectionUpdate: function _onSelectionUpdate() {
   143     if (!this._targetHasFocus()) {
   144       this._closeSelection();
   145       return;
   146     }
   147     this._updateSelectionUI("update",
   148                             this._mode == this._SELECTION_MODE,
   149                             this._mode == this._SELECTION_MODE,
   150                             this._mode == this._CARET_MODE);
   151   },
   153   /*
   154    * Switch selection modes. Currently we only support switching
   155    * from "caret" to "selection".
   156    */
   157   _onSwitchMode: function _onSwitchMode(aMode, aMarker, aX, aY) {
   158     if (aMode != "selection") {
   159       this._onFail("unsupported mode switch");
   160       return;
   161     }
   163     // Sanity check to be sure we are initialized
   164     if (!this._targetElement) {
   165       this._onFail("not initialized");
   166       return;
   167     }
   169     // Similar to _onSelectionStart - we need to create initial selection
   170     // but without the initialization bits.
   171     let framePoint = this._clientPointToFramePoint({ xPos: aX, yPos: aY });
   172     if (!this._domWinUtils.selectAtPoint(framePoint.xPos, framePoint.yPos,
   173                                          Ci.nsIDOMWindowUtils.SELECT_CHARACTER)) {
   174       this._onFail("failed to set selection at point");
   175       return;
   176     }
   178     // We bail if things get out of sync here implying we missed a message.
   179     this._selectionMoveActive = true;
   180     this._mode = this._SELECTION_MODE;
   182     // Update the position of the selection marker that is *not*
   183     // being dragged.
   184     this._updateSelectionUI("update", aMarker == "end", aMarker == "start");
   185   },
   187   /*
   188    * Selection close event handler
   189    *
   190    * @param aClearSelection requests that selection be cleared.
   191    */
   192   _onSelectionClose: function _onSelectionClose(aClearSelection) {
   193     if (aClearSelection) {
   194       this._clearSelection();
   195     }
   196     this._closeSelection();
   197   },
   199   /*
   200    * Called if for any reason we fail during the selection
   201    * process. Cancels the selection.
   202    */
   203   _onFail: function _onFail(aDbgMessage) {
   204     if (aDbgMessage && aDbgMessage.length > 0)
   205       Util.dumpLn(aDbgMessage);
   206     this.sendAsync("Content:SelectionFail");
   207     this._clearSelection();
   208     this._closeSelection();
   209   },
   211   /*
   212    * Empty conversion routines to match those in
   213    * browser. Called by SelectionHelperUI when
   214    * sending and receiving coordinates in messages.
   215    */
   217   ptClientToBrowser: function ptClientToBrowser(aX, aY, aIgnoreScroll, aIgnoreScale) {
   218     return { x: aX, y: aY }
   219   },
   221   rectBrowserToClient: function rectBrowserToClient(aRect, aIgnoreScroll, aIgnoreScale) {
   222     return {
   223       left: aRect.left,
   224       right: aRect.right,
   225       top: aRect.top,
   226       bottom: aRect.bottom
   227     }
   228   },
   230   ptBrowserToClient: function ptBrowserToClient(aX, aY, aIgnoreScroll, aIgnoreScale) {
   231     return { x: aX, y: aY }
   232   },
   234   ctobx: function ctobx(aCoord) {
   235     return aCoord;
   236   },
   238   ctoby: function ctoby(aCoord) {
   239     return aCoord;
   240   },
   242   btocx: function btocx(aCoord) {
   243     return aCoord;
   244   },
   246   btocy: function btocy(aCoord) {
   247     return aCoord;
   248   },
   251   /*************************************************
   252    * Selection helpers
   253    */
   255   /*
   256    * _clearSelection
   257    *
   258    * Clear existing selection if it exists and reset our internal state.
   259    */
   260   _clearSelection: function _clearSelection() {
   261     let selection = this._getSelection();
   262     if (selection) {
   263       selection.removeAllRanges();
   264     }
   265   },
   267   /*
   268    * _closeSelection
   269    *
   270    * Shuts ChromeSelectionHandler and SelectionHelperUI down.
   271    */
   272   _closeSelection: function _closeSelection() {
   273     this._deactivate();
   274     this.sendAsync("Content:HandlerShutdown", {});
   275   },
   277   /*
   278    * _deactivate
   279    *
   280    * Resets ChromeSelectionHandler state, previously initialized in
   281    * general selection start-method |_onSelectionAttach()|.
   282    */
   283   _deactivate: function _deactivate() {
   284     // Remove our selection notification listener.
   285     let selection = this._getSelection();
   286     if (selection) {
   287       try {
   288         selection.QueryInterface(Ci.nsISelectionPrivate).removeSelectionListener(this);
   289       } catch(e) {
   290         // Fail safe during multiple _deactivate() calls.
   291       }
   292     }
   294     this._clearTimers();
   295     this._cache = null;
   296     this._contentWindow = null;
   297     if (this._targetElement) {
   298       this._targetElement.removeEventListener("blur", this, true);
   299       this._targetElement = null;
   300     }
   301     this._selectionMoveActive = false;
   302     this._domWinUtils = null;
   303     this._targetIsEditable = false;
   304     this._mode = null;
   305   },
   307   get hasSelection() {
   308     if (!this._targetElement) {
   309       return false;
   310     }
   311     let selection = this._getSelection();
   312     return (selection && !selection.isCollapsed);
   313   },
   315   _targetHasFocus: function() {
   316     if (!this._targetElement || !document.commandDispatcher.focusedElement) {
   317       return false;
   318     }
   319     let bindingParent = this._contentWindow.document.getBindingParent(document.commandDispatcher.focusedElement);
   320     return (bindingParent && this._targetElement == bindingParent);
   321   },
   323   /*************************************************
   324    * Events
   325    */
   327   /*
   328    * Scroll + selection advancement timer when the monocle is
   329    * outside the bounds of an input control.
   330    */
   331   scrollTimerCallback: function scrollTimerCallback() {
   332     let result = ChromeSelectionHandler.updateTextEditSelection();
   333     // Update monocle position and speed if we've dragged off to one side
   334     if (result.trigger) {
   335       ChromeSelectionHandler._updateSelectionUI("update", result.start, result.end);
   336     }
   337   },
   339   handleEvent: function handleEvent(aEvent) {
   340     if (aEvent.type == "blur" && !this._targetHasFocus()) {
   341       this._closeSelection();
   342     }
   343   },
   345   msgHandler: function msgHandler(aMsg, aJson) {
   346     if (this._debugEvents && "Browser:SelectionMove" != aMsg) {
   347       Util.dumpLn("ChromeSelectionHandler:", aMsg);
   348     }
   349     switch(aMsg) {
   350       case "Browser:SelectionDebug":
   351         this._onSelectionDebug(aJson);
   352         break;
   354       case "Browser:SelectionAttach":
   355         this._onSelectionAttach(aJson);
   356       break;
   358       case "Browser:CaretAttach":
   359         this._onSelectionAttach(aJson);
   360         break;
   362       case "Browser:SelectionClose":
   363         this._onSelectionClose(aJson.clearSelection);
   364       break;
   366       case "Browser:SelectionUpdate":
   367         this._onSelectionUpdate();
   368       break;
   370       case "Browser:SelectionMoveStart":
   371         this._onSelectionMoveStart(aJson);
   372         break;
   374       case "Browser:SelectionMove":
   375         this._onSelectionMove(aJson);
   376         break;
   378       case "Browser:SelectionMoveEnd":
   379         this._onSelectionMoveEnd(aJson);
   380         break;
   382       case "Browser:CaretUpdate":
   383         this._onCaretPositionUpdate(aJson.caret.xPos, aJson.caret.yPos);
   384         break;
   386       case "Browser:CaretMove":
   387         this._onCaretMove(aJson.caret.xPos, aJson.caret.yPos);
   388         break;
   390       case "Browser:SelectionSwitchMode":
   391         this._onSwitchMode(aJson.newMode, aJson.change, aJson.xPos, aJson.yPos);
   392         break;
   393     }
   394   },
   396   /*************************************************
   397    * Utilities
   398    */
   400   _getSelection: function _getSelection() {
   401     let targetElementEditor = this._getTargetElementEditor();
   403     return targetElementEditor ? targetElementEditor.selection : null;
   404   },
   406   _getTargetElementValue: function _getTargetElementValue() {
   407     if (this._targetElement instanceof Ci.nsIDOMXULTextBoxElement) {
   408       return this._targetElement.inputField.value;
   409     } else if (Util.isTextInput(this._targetElement)) {
   410       return this._targetElement.value;
   411     }
   412     return null;
   413   },
   415   _getSelectController: function _getSelectController() {
   416     let targetElementEditor = this._getTargetElementEditor();
   418     return targetElementEditor ? targetElementEditor.selectionController : null;
   419   },
   421   _getTargetElementEditor: function() {
   422     if (this._targetElement instanceof Ci.nsIDOMXULTextBoxElement) {
   423       return this._targetElement.QueryInterface(Ci.nsIDOMXULTextBoxElement)
   424           .editor;
   425     } else if (Util.isTextInput(this._targetElement)) {
   426       return this._targetElement.QueryInterface(Ci.nsIDOMNSEditableElement)
   427           .editor;
   428     }
   429     return null;
   430   }
   431 };
   433 ChromeSelectionHandler.__proto__ = new SelectionPrototype();
   434 ChromeSelectionHandler.type = 1; // kChromeSelector

mercurial