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

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial