Bug 1255819 - Fix SelectionHandler copy/paste in type=number text fields, r=margaret
authorMark Capella <markcapella@twcny.rr.com>
Wed, 23 Mar 2016 20:03:07 -0400
changeset 290039 b999fac0569d760e971b6579c420eb887d645433
parent 290038 147628d074c876e6424deda66aa0128209f50486
child 290223 24c5fbde4488e06ef79905e1c520027cddcd1189
child 290313 faa774eaf02c4128d0a80ad4d465c368c0cf023f
push id30113
push usercbook@mozilla.com
push dateThu, 24 Mar 2016 15:06:00 +0000
treeherdermozilla-central@b999fac0569d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret
bugs1255819
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1255819 - Fix SelectionHandler copy/paste in type=number text fields, r=margaret
mobile/android/chrome/content/SelectionHandler.js
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -75,26 +75,31 @@ var SelectionHandler = {
       return this._contentWindowRef.get();
     return null;
   },
 
   set _contentWindow(aContentWindow) {
     this._contentWindowRef = Cu.getWeakReference(aContentWindow);
   },
 
+  // Main target element, always provides editor for editables.
   get _targetElement() {
     if (this._targetElementRef)
       return this._targetElementRef.get();
     return null;
   },
 
   set _targetElement(aTargetElement) {
     this._targetElementRef = Cu.getWeakReference(aTargetElement);
   },
 
+  // Alternate target element. Always provides public DOM node for editables
+  // that contain anonymous inner content structures.
+  _targetDOMCaretNode: null,
+
   get _domWinUtils() {
     return BrowserApp.selectedBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
                                                     getInterface(Ci.nsIDOMWindowUtils);
   },
 
   // Provides UUID service for selection ID's.
   get _idService() {
     delete this._idService;
@@ -681,17 +686,18 @@ var SelectionHandler = {
    * Actionbar methods.
    */
   actions: {
     SELECT_ALL: {
       label: Strings.browser.GetStringFromName("contextmenu.selectAll"),
       id: "selectall_action",
       icon: "drawable://ab_select_all",
       action: function(aElement) {
-        SelectionHandler.startSelection(aElement);
+        // Use the public DOMNode for startSelection(), not any anonymous inner.
+        SelectionHandler.startSelection(SelectionHandler._targetDOMCaretNode);
         UITelemetry.addEvent("action.1", "actionbar", null, "select_all");
       },
       order: 5,
       selector: {
         matches: function(aElement) {
           return (aElement.textLength != 0);
         }
       }
@@ -729,18 +735,17 @@ var SelectionHandler = {
       action: function() {
         SelectionHandler.copySelection();
         UITelemetry.addEvent("action.1", "actionbar", null, "copy");
       },
       order: 3,
       selector: {
         matches: function(aElement) {
           // Don't include "copy" for password fields.
-          // mozIsTextField(true) tests for only non-password fields.
-          if (aElement instanceof Ci.nsIDOMHTMLInputElement && !aElement.mozIsTextField(true)) {
+          if (aElement instanceof Ci.nsIDOMHTMLInputElement && (aElement.type === "password")) {
             return false;
           }
           return SelectionHandler.isSelectionActive();
         }
       }
     },
 
     PASTE: {
@@ -896,28 +901,50 @@ var SelectionHandler = {
       type: "TextSelection:ShowHandles",
       handles: [this.HANDLE_TYPE_CARET]
     });
     this._updateMenu();
 
     return this.ERROR_NONE;
   },
 
+  /**
+   * <input> editables of type=number are special cases, bearing unique anonymous
+   * internal content to facilitate up/down arrow UI controls. We will maintain a
+   * reference to their public DOMNode, as well as their internal node, which
+   * holds reference to it's editor.
+   */
+  _setTargetElements: function(element) {
+    // Default, both values are the same.
+    this._targetDOMCaretNode = element;
+    this._targetElement = element;
+    if (element.type !== "number") {
+      return;
+    }
+
+    // Set the editor bearing anonymous inner <input> element.
+    let editorNode = Services.focus.focusedElement;
+    if (editorNode instanceof HTMLInputElement && editorNode.editor) {
+      this._targetElement = editorNode;
+    }
+    return;
+  },
+
   // Target initialization for both TYPE_CURSOR and TYPE_SELECTION
   _initTargetInfo: function sh_initTargetInfo(aElement, aSelectionType) {
-    this._targetElement = aElement;
     if (aElement instanceof Ci.nsIDOMNSEditableElement) {
       if (aSelectionType === this.TYPE_SELECTION) {
         // Blur the targetElement to force IME code to undo previous style compositions
         // (visible underlines / etc generated by autoCorrection, autoSuggestion)
         aElement.blur();
       }
       // Ensure targetElement is now focused normally
       aElement.focus();
     }
+    this._setTargetElements(aElement);
 
     this._selectionID = this._idService.generateUUID().toString();
     this._stopDraggingHandles();
     this._contentWindow = aElement.ownerDocument.defaultView;
     this._targetIsRTL = (this._contentWindow.getComputedStyle(aElement, "").direction == "rtl");
 
     this._addObservers();
   },
@@ -968,23 +995,25 @@ var SelectionHandler = {
   },
 
   // Used by the contextmenu "matches" functions in ClipboardHelper
   isSelectionActive: function sh_isSelectionActive() {
     return (this._activeType == this.TYPE_SELECTION);
   },
 
   isElementEditableText: function (aElement) {
-    return (((aElement instanceof HTMLInputElement && aElement.mozIsTextField(false)) ||
+    return (((aElement instanceof HTMLInputElement &&
+              (aElement.mozIsTextField(false) || aElement.type === "number")) ||
             (aElement instanceof HTMLTextAreaElement)) && !aElement.readOnly) ||
             aElement.isContentEditable;
   },
 
   _isNonTextInputElement: function(aElement) {
-    return (aElement instanceof HTMLInputElement && !aElement.mozIsTextField(false));
+    return (aElement instanceof HTMLInputElement &&
+            !(aElement.mozIsTextField(false) || aElement.type === "number"));
   },
 
   /*
    * Moves the selection as the user drags a handle.
    * @param handleType: Specifies either the anchor or the focus handle.
    * @param handlePt: selection point in client coordinates.
    */
   _moveSelection: function sh_moveSelection(handleType, handlePt) {
@@ -998,17 +1027,17 @@ var SelectionHandler = {
     let cwd = this._contentWindow.document;
     let caretPos = cwd.caretPositionFromPoint(ptX, ptY);
     if (!caretPos) {
       return;
     }
 
     // Constrain text selection within editable elements.
     let targetIsEditable = this._targetElement instanceof Ci.nsIDOMNSEditableElement;
-    if (targetIsEditable && (caretPos.offsetNode != this._targetElement)) {
+    if (targetIsEditable && (caretPos.offsetNode != this._targetDOMCaretNode)) {
       return;
     }
 
     // Update the Selection for editable elements. Selection Change
     // logic is the same, regardless of RTL/LTR. Selection direction is
     // maintained always forward (startOffset <= endOffset).
     if (targetIsEditable) {
       let start = this._dragStartAnchorOffset;
@@ -1225,16 +1254,18 @@ var SelectionHandler = {
     if (this._activeType == this.TYPE_CURSOR) {
       BrowserApp.deck.removeEventListener("keyup", this);
       BrowserApp.deck.removeEventListener("compositionupdate", this);
       BrowserApp.deck.removeEventListener("compositionend", this);
     }
 
     this._contentWindow = null;
     this._targetElement = null;
+    this._targetDOMCaretNode = null;
+
     this._targetIsRTL = false;
     this._ignoreCompositionChanges = false;
     this._prevHandlePositions = [];
     this._prevTargetElementHasText = null;
 
     this._activeType = this.TYPE_NONE;
   },