1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/metro/base/content/contenthandlers/SelectionHandler.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,616 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +let Ci = Components.interfaces; 1.9 +let Cc = Components.classes; 1.10 + 1.11 +dump("### SelectionHandler.js loaded\n"); 1.12 + 1.13 +var SelectionHandler = { 1.14 + init: function init() { 1.15 + this.type = kContentSelector; 1.16 + this.snap = true; 1.17 + this.lastYPos = this.lastXPos = null; 1.18 + addMessageListener("Browser:SelectionStart", this); 1.19 + addMessageListener("Browser:SelectionAttach", this); 1.20 + addMessageListener("Browser:SelectionEnd", this); 1.21 + addMessageListener("Browser:SelectionMoveStart", this); 1.22 + addMessageListener("Browser:SelectionMove", this); 1.23 + addMessageListener("Browser:SelectionMoveEnd", this); 1.24 + addMessageListener("Browser:SelectionUpdate", this); 1.25 + addMessageListener("Browser:SelectionClose", this); 1.26 + addMessageListener("Browser:SelectionCopy", this); 1.27 + addMessageListener("Browser:SelectionDebug", this); 1.28 + addMessageListener("Browser:CaretAttach", this); 1.29 + addMessageListener("Browser:CaretMove", this); 1.30 + addMessageListener("Browser:CaretUpdate", this); 1.31 + addMessageListener("Browser:SelectionSwitchMode", this); 1.32 + addMessageListener("Browser:RepositionInfoRequest", this); 1.33 + addMessageListener("Browser:SelectionHandlerPing", this); 1.34 + addMessageListener("Browser:ResetLastPos", this); 1.35 + }, 1.36 + 1.37 + shutdown: function shutdown() { 1.38 + removeMessageListener("Browser:SelectionStart", this); 1.39 + removeMessageListener("Browser:SelectionAttach", this); 1.40 + removeMessageListener("Browser:SelectionEnd", this); 1.41 + removeMessageListener("Browser:SelectionMoveStart", this); 1.42 + removeMessageListener("Browser:SelectionMove", this); 1.43 + removeMessageListener("Browser:SelectionMoveEnd", this); 1.44 + removeMessageListener("Browser:SelectionUpdate", this); 1.45 + removeMessageListener("Browser:SelectionClose", this); 1.46 + removeMessageListener("Browser:SelectionCopy", this); 1.47 + removeMessageListener("Browser:SelectionDebug", this); 1.48 + removeMessageListener("Browser:CaretAttach", this); 1.49 + removeMessageListener("Browser:CaretMove", this); 1.50 + removeMessageListener("Browser:CaretUpdate", this); 1.51 + removeMessageListener("Browser:SelectionSwitchMode", this); 1.52 + removeMessageListener("Browser:RepositionInfoRequest", this); 1.53 + removeMessageListener("Browser:SelectionHandlerPing", this); 1.54 + removeMessageListener("Browser:ResetLastPos", this); 1.55 + }, 1.56 + 1.57 + sendAsync: function sendAsync(aMsg, aJson) { 1.58 + sendAsyncMessage(aMsg, aJson); 1.59 + }, 1.60 + 1.61 + /************************************************* 1.62 + * Browser event handlers 1.63 + */ 1.64 + 1.65 + /* 1.66 + * Selection start event handler 1.67 + */ 1.68 + _onSelectionStart: function _onSelectionStart(aJson) { 1.69 + // Init content window information 1.70 + if (!this._initTargetInfo(aJson.xPos, aJson.yPos)) { 1.71 + this._onFail("failed to get target information"); 1.72 + return; 1.73 + } 1.74 + 1.75 + // for context menu select command, which doesn't trigger 1.76 + // form input focus changes. 1.77 + if (aJson.setFocus && this._targetIsEditable) { 1.78 + this._targetElement.focus(); 1.79 + } 1.80 + 1.81 + // Clear any existing selection from the document 1.82 + let selection = this._contentWindow.getSelection(); 1.83 + selection.removeAllRanges(); 1.84 + 1.85 + // Set our initial selection, aX and aY should be in client coordinates. 1.86 + let framePoint = this._clientPointToFramePoint({ xPos: aJson.xPos, yPos: aJson.yPos }); 1.87 + if (!this._domWinUtils.selectAtPoint(framePoint.xPos, framePoint.yPos, 1.88 + Ci.nsIDOMWindowUtils.SELECT_WORDNOSPACE)) { 1.89 + this._onFail("failed to set selection at point"); 1.90 + return; 1.91 + } 1.92 + 1.93 + // Update the position of our selection monocles 1.94 + this._updateSelectionUI("start", true, true); 1.95 + }, 1.96 + 1.97 + _onSelectionAttach: function _onSelectionAttach(aX, aY) { 1.98 + // Init content window information 1.99 + if (!this._initTargetInfo(aX, aY)) { 1.100 + this._onFail("failed to get frame offset"); 1.101 + return; 1.102 + } 1.103 + 1.104 + // Update the position of our selection monocles 1.105 + this._updateSelectionUI("start", true, true); 1.106 + }, 1.107 + 1.108 + /* 1.109 + * Switch selection modes. Currently we only support switching 1.110 + * from "caret" to "selection". 1.111 + */ 1.112 + _onSwitchMode: function _onSwitchMode(aMode, aMarker, aX, aY) { 1.113 + if (aMode != "selection") { 1.114 + this._onFail("unsupported mode switch"); 1.115 + return; 1.116 + } 1.117 + 1.118 + // Sanity check to be sure we are initialized 1.119 + if (!this._targetElement) { 1.120 + this._onFail("not initialized"); 1.121 + return; 1.122 + } 1.123 + 1.124 + // Only use selectAtPoint for editable content and avoid that for inputs, 1.125 + // as we can expand caret to selection manually more precisely. We can use 1.126 + // selectAtPoint for inputs too though, but only once bug 881938 is fully 1.127 + // resolved. 1.128 + if(Util.isEditableContent(this._targetElement)) { 1.129 + // Similar to _onSelectionStart - we need to create initial selection 1.130 + // but without the initialization bits. 1.131 + let framePoint = this._clientPointToFramePoint({ xPos: aX, yPos: aY }); 1.132 + if (!this._domWinUtils.selectAtPoint(framePoint.xPos, framePoint.yPos, 1.133 + Ci.nsIDOMWindowUtils.SELECT_CHARACTER)) { 1.134 + this._onFail("failed to set selection at point"); 1.135 + return; 1.136 + } 1.137 + } else if (this._targetElement.selectionStart == 0 || aMarker == "end") { 1.138 + // Expand caret forward or backward depending on direction 1.139 + this._targetElement.selectionEnd++; 1.140 + } else { 1.141 + this._targetElement.selectionStart--; 1.142 + } 1.143 + 1.144 + // We bail if things get out of sync here implying we missed a message. 1.145 + this._selectionMoveActive = true; 1.146 + 1.147 + // Update the position of the selection marker that is *not* 1.148 + // being dragged. 1.149 + this._updateSelectionUI("update", aMarker == "end", aMarker == "start"); 1.150 + }, 1.151 + 1.152 + /* 1.153 + * Selection monocle start move event handler 1.154 + */ 1.155 + _onSelectionMoveStart: function _onSelectionMoveStart(aMsg) { 1.156 + if (!this._contentWindow) { 1.157 + this._onFail("_onSelectionMoveStart was called without proper view set up"); 1.158 + return; 1.159 + } 1.160 + 1.161 + if (this._selectionMoveActive) { 1.162 + this._onFail("mouse is already down on drag start?"); 1.163 + return; 1.164 + } 1.165 + 1.166 + // We bail if things get out of sync here implying we missed a message. 1.167 + this._selectionMoveActive = true; 1.168 + 1.169 + if (this._targetIsEditable) { 1.170 + // If we're coming out of an out-of-bounds scroll, the node the user is 1.171 + // trying to drag may be hidden (the monocle will be pegged to the edge 1.172 + // of the edit). Make sure the node the user wants to move is visible 1.173 + // and has focus. 1.174 + this._updateInputFocus(aMsg.change); 1.175 + } 1.176 + 1.177 + // Update the position of our selection monocles 1.178 + this._updateSelectionUI("update", true, true); 1.179 + }, 1.180 + 1.181 + /* 1.182 + * Selection monocle move event handler 1.183 + */ 1.184 + _onSelectionMove: function _onSelectionMove(aMsg) { 1.185 + if (!this._contentWindow) { 1.186 + this._onFail("_onSelectionMove was called without proper view set up"); 1.187 + return; 1.188 + } 1.189 + 1.190 + if (!this._selectionMoveActive) { 1.191 + this._onFail("mouse isn't down for drag move?"); 1.192 + return; 1.193 + } 1.194 + 1.195 + this._handleSelectionPoint(aMsg, false); 1.196 + }, 1.197 + 1.198 + /* 1.199 + * Selection monocle move finished event handler 1.200 + */ 1.201 + _onSelectionMoveEnd: function _onSelectionMoveComplete(aMsg) { 1.202 + if (!this._contentWindow) { 1.203 + this._onFail("_onSelectionMove was called without proper view set up"); 1.204 + return; 1.205 + } 1.206 + 1.207 + if (!this._selectionMoveActive) { 1.208 + this._onFail("mouse isn't down for drag move?"); 1.209 + return; 1.210 + } 1.211 + 1.212 + this._handleSelectionPoint(aMsg, true); 1.213 + this._selectionMoveActive = false; 1.214 + 1.215 + // _handleSelectionPoint may set a scroll timer, so this must 1.216 + // be reset after the last call. 1.217 + this._clearTimers(); 1.218 + 1.219 + // Update the position of our selection monocles 1.220 + this._updateSelectionUI("end", true, true); 1.221 + }, 1.222 + 1.223 + /* 1.224 + * _onCaretAttach - called by SelectionHelperUI when the user taps in a 1.225 + * form input. Initializes SelectionHandler, updates the location of the 1.226 + * caret, and messages back with current monocle position information. 1.227 + * 1.228 + * @param aX, aY tap location in client coordinates. 1.229 + */ 1.230 + _onCaretAttach: function _onCaretAttach(aX, aY) { 1.231 + // Init content window information 1.232 + if (!this._initTargetInfo(aX, aY)) { 1.233 + this._onFail("failed to get target information"); 1.234 + return; 1.235 + } 1.236 + 1.237 + // This should never happen, but we check to make sure 1.238 + if (!this._targetIsEditable) { 1.239 + this._onFail("Coordiates didn't find a text input element."); 1.240 + return; 1.241 + } 1.242 + 1.243 + // Locate and sanity check the caret position 1.244 + let selection = this._getSelection(); 1.245 + if (!selection || !selection.isCollapsed) { 1.246 + this._onFail("No selection or selection is not collapsed."); 1.247 + return; 1.248 + } 1.249 + 1.250 + // Update the position of our selection monocles 1.251 + this._updateSelectionUI("caret", false, false, true); 1.252 + }, 1.253 + 1.254 + /* 1.255 + * Selection copy event handler 1.256 + * 1.257 + * Check to see if the incoming click was on our selection rect. 1.258 + * if it was, copy to the clipboard. Incoming coordinates are 1.259 + * content values. 1.260 + */ 1.261 + _onSelectionCopy: function _onSelectionCopy(aMsg) { 1.262 + let tap = { 1.263 + xPos: aMsg.xPos, 1.264 + yPos: aMsg.yPos, 1.265 + }; 1.266 + 1.267 + let tapInSelection = (tap.xPos > this._cache.selection.left && 1.268 + tap.xPos < this._cache.selection.right) && 1.269 + (tap.yPos > this._cache.selection.top && 1.270 + tap.yPos < this._cache.selection.bottom); 1.271 + // Util.dumpLn(tapInSelection, 1.272 + // tap.xPos, tap.yPos, "|", this._cache.selection.left, 1.273 + // this._cache.selection.right, this._cache.selection.top, 1.274 + // this._cache.selection.bottom); 1.275 + let success = false; 1.276 + let selectedText = this._getSelectedText(); 1.277 + if (tapInSelection && selectedText.length) { 1.278 + let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"] 1.279 + .getService(Ci.nsIClipboardHelper); 1.280 + clipboard.copyString(selectedText, this._contentWindow.document); 1.281 + success = true; 1.282 + } 1.283 + sendSyncMessage("Content:SelectionCopied", { succeeded: success }); 1.284 + }, 1.285 + 1.286 + /* 1.287 + * Selection close event handler 1.288 + * 1.289 + * @param aClearSelection requests that selection be cleared. 1.290 + */ 1.291 + _onSelectionClose: function _onSelectionClose(aClearSelection) { 1.292 + if (aClearSelection) { 1.293 + this._clearSelection(); 1.294 + } 1.295 + this.closeSelection(); 1.296 + }, 1.297 + 1.298 + /* 1.299 + * Called any time SelectionHelperUI would like us to 1.300 + * recalculate the selection bounds. 1.301 + */ 1.302 + _onSelectionUpdate: function _onSelectionUpdate(aMsg) { 1.303 + if (!this._contentWindow) { 1.304 + this._onFail("_onSelectionUpdate was called without proper view set up"); 1.305 + return; 1.306 + } 1.307 + 1.308 + if (aMsg && aMsg.isInitiatedByAPZC) { 1.309 + let {offset: offset} = Content.getCurrentWindowAndOffset( 1.310 + this._targetCoordinates.x, this._targetCoordinates.y); 1.311 + this._contentOffset = offset; 1.312 + } 1.313 + 1.314 + // Update the position of our selection monocles 1.315 + this._updateSelectionUI("update", true, true); 1.316 + }, 1.317 + 1.318 + /* 1.319 + * Called if for any reason we fail during the selection 1.320 + * process. Cancels the selection. 1.321 + */ 1.322 + _onFail: function _onFail(aDbgMessage) { 1.323 + if (aDbgMessage && aDbgMessage.length > 0) 1.324 + Util.dumpLn(aDbgMessage); 1.325 + this.sendAsync("Content:SelectionFail"); 1.326 + this._clearSelection(); 1.327 + this.closeSelection(); 1.328 + }, 1.329 + 1.330 + /* 1.331 + * _repositionInfoRequest - fired at us by ContentAreaObserver when the 1.332 + * soft keyboard is being displayed. CAO wants to make a decision about 1.333 + * whether the browser deck needs repositioning. 1.334 + */ 1.335 + _repositionInfoRequest: function _repositionInfoRequest(aJsonMsg) { 1.336 + let result = this._calcNewContentPosition(aJsonMsg.viewHeight); 1.337 + 1.338 + // no repositioning needed 1.339 + if (result == 0) { 1.340 + this.sendAsync("Content:RepositionInfoResponse", { reposition: false }); 1.341 + return; 1.342 + } 1.343 + 1.344 + this.sendAsync("Content:RepositionInfoResponse", { 1.345 + reposition: true, 1.346 + raiseContent: result, 1.347 + }); 1.348 + }, 1.349 + 1.350 + _onPing: function _onPing(aId) { 1.351 + this.sendAsync("Content:SelectionHandlerPong", { id: aId }); 1.352 + }, 1.353 + 1.354 + onClickCoords: function (xPos, yPos) { 1.355 + this.lastXPos = xPos; 1.356 + this.lastYPos = yPos; 1.357 + }, 1.358 + 1.359 + /************************************************* 1.360 + * Selection helpers 1.361 + */ 1.362 + 1.363 + /* 1.364 + * _clearSelection 1.365 + * 1.366 + * Clear existing selection if it exists and reset our internla state. 1.367 + */ 1.368 + _clearSelection: function _clearSelection() { 1.369 + this._clearTimers(); 1.370 + if (this._contentWindow) { 1.371 + let selection = this._getSelection(); 1.372 + if (selection) 1.373 + selection.removeAllRanges(); 1.374 + } else { 1.375 + let selection = content.getSelection(); 1.376 + if (selection) 1.377 + selection.removeAllRanges(); 1.378 + } 1.379 + }, 1.380 + 1.381 + /* 1.382 + * closeSelection 1.383 + * 1.384 + * Shuts SelectionHandler down. 1.385 + */ 1.386 + closeSelection: function closeSelection() { 1.387 + this._clearTimers(); 1.388 + this._cache = null; 1.389 + this._contentWindow = null; 1.390 + this._targetElement = null; 1.391 + this._selectionMoveActive = false; 1.392 + this._contentOffset = null; 1.393 + this._domWinUtils = null; 1.394 + this._targetIsEditable = false; 1.395 + this._targetCoordinates = null; 1.396 + sendSyncMessage("Content:HandlerShutdown", {}); 1.397 + }, 1.398 + 1.399 + /* 1.400 + * Find content within frames - cache the target nsIDOMWindow, 1.401 + * client coordinate offset, target element, and dom utils interface. 1.402 + */ 1.403 + _initTargetInfo: function _initTargetInfo(aX, aY) { 1.404 + // getCurrentWindowAndOffset takes client coordinates 1.405 + let { element: element, 1.406 + contentWindow: contentWindow, 1.407 + offset: offset, 1.408 + utils: utils } = 1.409 + Content.getCurrentWindowAndOffset(aX, aY); 1.410 + if (!contentWindow) { 1.411 + return false; 1.412 + } 1.413 + this._targetElement = element; 1.414 + this._contentWindow = contentWindow; 1.415 + this._contentOffset = offset; 1.416 + this._domWinUtils = utils; 1.417 + this._targetIsEditable = Util.isEditable(this._targetElement); 1.418 + this._targetCoordinates = { 1.419 + x: aX, 1.420 + y: aY 1.421 + }; 1.422 + 1.423 + return true; 1.424 + }, 1.425 + 1.426 + /* 1.427 + * _calcNewContentPosition - calculates the distance the browser should be 1.428 + * raised to move the focused form input out of the way of the soft 1.429 + * keyboard. 1.430 + * 1.431 + * @param aNewViewHeight the new content view height 1.432 + * @return 0 if no positioning is required or a positive val equal to the 1.433 + * distance content should be raised to center the target element. 1.434 + */ 1.435 + _calcNewContentPosition: function _calcNewContentPosition(aNewViewHeight) { 1.436 + // We have no target element but the keyboard is up 1.437 + // so lets not cover content that is below the keyboard 1.438 + if (!this._cache || !this._cache.element) { 1.439 + if (this.lastYPos != null && this.lastYPos > aNewViewHeight) { 1.440 + return Services.metro.keyboardHeight; 1.441 + } 1.442 + return 0; 1.443 + } 1.444 + 1.445 + let position = Util.centerElementInView(aNewViewHeight, this._cache.element); 1.446 + if (position !== undefined) { 1.447 + return position; 1.448 + } 1.449 + 1.450 + // Special case: we are dealing with an input that is taller than the 1.451 + // desired height of content. We need to center on the caret location. 1.452 + let rect = 1.453 + this._domWinUtils.sendQueryContentEvent( 1.454 + this._domWinUtils.QUERY_CARET_RECT, 1.455 + this._targetElement.selectionEnd, 1.456 + 0, 0, 0, 1.457 + this._domWinUtils.QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK); 1.458 + if (!rect || !rect.succeeded) { 1.459 + Util.dumpLn("no caret was present, unexpected."); 1.460 + return 0; 1.461 + } 1.462 + 1.463 + // Note sendQueryContentEvent with QUERY_CARET_RECT is really buggy. If it 1.464 + // can't find the exact location of the caret position it will "guess". 1.465 + // Sometimes this can put the result in unexpected locations. 1.466 + let caretLocation = Math.max(Math.min(Math.round(rect.top + (rect.height * .5)), 1.467 + viewBottom), 0); 1.468 + 1.469 + // Caret is above the bottom of the new view bounds, no need to shift. 1.470 + if (caretLocation <= aNewViewHeight) { 1.471 + return 0; 1.472 + } 1.473 + 1.474 + // distance from the top of the keyboard down to the caret location 1.475 + return caretLocation - aNewViewHeight; 1.476 + }, 1.477 + 1.478 + /************************************************* 1.479 + * Events 1.480 + */ 1.481 + 1.482 + /* 1.483 + * Scroll + selection advancement timer when the monocle is 1.484 + * outside the bounds of an input control. 1.485 + */ 1.486 + scrollTimerCallback: function scrollTimerCallback() { 1.487 + let result = SelectionHandler.updateTextEditSelection(); 1.488 + // Update monocle position and speed if we've dragged off to one side 1.489 + if (result.trigger) { 1.490 + SelectionHandler._updateSelectionUI("update", result.start, result.end); 1.491 + } 1.492 + }, 1.493 + 1.494 + receiveMessage: function sh_receiveMessage(aMessage) { 1.495 + if (this._debugEvents && aMessage.name != "Browser:SelectionMove") { 1.496 + Util.dumpLn("SelectionHandler:", aMessage.name); 1.497 + } 1.498 + let json = aMessage.json; 1.499 + switch (aMessage.name) { 1.500 + case "Browser:SelectionStart": 1.501 + this._onSelectionStart(json); 1.502 + break; 1.503 + 1.504 + case "Browser:SelectionAttach": 1.505 + this._onSelectionAttach(json.xPos, json.yPos); 1.506 + break; 1.507 + 1.508 + case "Browser:CaretAttach": 1.509 + this._onCaretAttach(json.xPos, json.yPos); 1.510 + break; 1.511 + 1.512 + case "Browser:CaretMove": 1.513 + this._onCaretMove(json.caret.xPos, json.caret.yPos); 1.514 + break; 1.515 + 1.516 + case "Browser:CaretUpdate": 1.517 + this._onCaretPositionUpdate(json.caret.xPos, json.caret.yPos); 1.518 + break; 1.519 + 1.520 + case "Browser:SelectionSwitchMode": 1.521 + this._onSwitchMode(json.newMode, json.change, json.xPos, json.yPos); 1.522 + break; 1.523 + 1.524 + case "Browser:SelectionClose": 1.525 + this._onSelectionClose(json.clearSelection); 1.526 + break; 1.527 + 1.528 + case "Browser:SelectionMoveStart": 1.529 + this._onSelectionMoveStart(json); 1.530 + break; 1.531 + 1.532 + case "Browser:SelectionMove": 1.533 + this._onSelectionMove(json); 1.534 + break; 1.535 + 1.536 + case "Browser:SelectionMoveEnd": 1.537 + this._onSelectionMoveEnd(json); 1.538 + break; 1.539 + 1.540 + case "Browser:SelectionCopy": 1.541 + this._onSelectionCopy(json); 1.542 + break; 1.543 + 1.544 + case "Browser:SelectionDebug": 1.545 + this._onSelectionDebug(json); 1.546 + break; 1.547 + 1.548 + case "Browser:SelectionUpdate": 1.549 + this._onSelectionUpdate(json); 1.550 + break; 1.551 + 1.552 + case "Browser:RepositionInfoRequest": 1.553 + // This message is sent simultaneously with a tap event. 1.554 + // Wait a bit to make sure we have the most up-to-date tap co-ordinates 1.555 + // before a call to _calcNewContentPosition() which accesses them. 1.556 + content.setTimeout (function () { 1.557 + SelectionHandler._repositionInfoRequest(json); 1.558 + }, 50); 1.559 + break; 1.560 + 1.561 + case "Browser:SelectionHandlerPing": 1.562 + this._onPing(json.id); 1.563 + break; 1.564 + 1.565 + case "Browser:ResetLastPos": 1.566 + this.onClickCoords(json.xPos, json.yPos); 1.567 + break; 1.568 + } 1.569 + }, 1.570 + 1.571 + /************************************************* 1.572 + * Utilities 1.573 + */ 1.574 + 1.575 + _getDocShell: function _getDocShell(aWindow) { 1.576 + if (aWindow == null) 1.577 + return null; 1.578 + return aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.579 + .getInterface(Ci.nsIWebNavigation) 1.580 + .QueryInterface(Ci.nsIDocShell); 1.581 + }, 1.582 + 1.583 + _getSelectedText: function _getSelectedText() { 1.584 + let selection = this._getSelection(); 1.585 + if (selection) 1.586 + return selection.toString(); 1.587 + return ""; 1.588 + }, 1.589 + 1.590 + _getSelection: function _getSelection() { 1.591 + if (this._targetElement instanceof Ci.nsIDOMNSEditableElement) { 1.592 + return this._targetElement 1.593 + .QueryInterface(Ci.nsIDOMNSEditableElement) 1.594 + .editor.selection; 1.595 + } else if (this._contentWindow) 1.596 + return this._contentWindow.getSelection(); 1.597 + return null; 1.598 + }, 1.599 + 1.600 + _getSelectController: function _getSelectController() { 1.601 + if (this._targetElement instanceof Ci.nsIDOMNSEditableElement) { 1.602 + return this._targetElement 1.603 + .QueryInterface(Ci.nsIDOMNSEditableElement) 1.604 + .editor.selectionController; 1.605 + } else { 1.606 + let docShell = this._getDocShell(this._contentWindow); 1.607 + if (docShell == null) 1.608 + return null; 1.609 + return docShell.QueryInterface(Ci.nsIInterfaceRequestor) 1.610 + .getInterface(Ci.nsISelectionDisplay) 1.611 + .QueryInterface(Ci.nsISelectionController); 1.612 + } 1.613 + }, 1.614 +}; 1.615 +this.SelectionHandler = SelectionHandler; 1.616 + 1.617 +SelectionHandler.__proto__ = new SelectionPrototype(); 1.618 +SelectionHandler.init(); 1.619 +