Bug 972428 - Grippers not appearing under the URL field when adding text. Handle both XUL and HTML input elements. r=jimm
authorAleh Zasypkin <aleh.zasypkin@gmail.com>
Fri, 14 Feb 2014 18:32:13 +0100
changeset 169495 7dabe9719703448804d5e58b156b281869bef3ac
parent 169494 dc6f5c320e6884b73c15848b2d867ec65d12c434
child 169496 f7f9328980d0a49408716f73d98698d18062a48e
push id5173
push userryanvm@gmail.com
push dateWed, 19 Feb 2014 16:40:22 +0000
treeherderfx-team@d7fdc2f9d7b5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm
bugs972428
milestone30.0a1
Bug 972428 - Grippers not appearing under the URL field when adding text. Handle both XUL and HTML input elements. r=jimm
browser/metro/base/content/bindings/bindings.xml
browser/metro/base/content/bindings/urlbar.xml
browser/metro/base/content/helperui/ChromeSelectionHandler.js
browser/metro/base/tests/mochitest/browser_selection_urlbar.js
browser/metro/base/tests/mochitest/head.js
--- a/browser/metro/base/content/bindings/bindings.xml
+++ b/browser/metro/base/content/bindings/bindings.xml
@@ -290,17 +290,17 @@
       </method>
     </implementation>
     <handlers>
       <handler event="click" phase="capturing">
         <![CDATA[
           if (event.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) {
             if (typeof SelectionHelperUI != 'undefined') {
               SelectionHelperUI.attachEditSession(ChromeSelectionHandler,
-                  event.clientX, event.clientY, this);
+                  event.clientX, event.clientY, event.target);
             } else {
               // If we don't have access to SelectionHelperUI then we are using this
               // binding for browser content (e.g. about:config)
               Services.obs.notifyObservers(event, "attach_edit_session_to_content", "");
             }
           }
         ]]>
       </handler>
--- a/browser/metro/base/content/bindings/urlbar.xml
+++ b/browser/metro/base/content/bindings/urlbar.xml
@@ -288,17 +288,17 @@
               this.focus();
 
             this._clearFormatting();
             this.select();
 
             if (aShouldDismiss)
               ContextUI.dismissTabs();
 
-            if (!InputSourceHelper.isPrecise && this.textLength) {
+            if (!InputSourceHelper.isPrecise) {
               let inputRectangle = this.inputField.getBoundingClientRect();
               SelectionHelperUI.attachEditSession(ChromeSelectionHandler,
                   inputRectangle.left, inputRectangle.top, this);
             }
           ]]>
         </body>
       </method>
 
--- a/browser/metro/base/content/helperui/ChromeSelectionHandler.js
+++ b/browser/metro/base/content/helperui/ChromeSelectionHandler.js
@@ -1,16 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * Selection handler for chrome text inputs
  */
 
+let Ci = Components.interfaces;
+
 const kCaretMode = 1;
 const kSelectionMode = 2;
 
 var ChromeSelectionHandler = {
   _mode: kSelectionMode,
 
   /*************************************************
    * Messaging wrapper
@@ -29,28 +31,34 @@ var ChromeSelectionHandler = {
 
   /*
    * General selection start method for both caret and selection mode.
    */
   _onSelectionAttach: function _onSelectionAttach(aJson) {
     this._domWinUtils = Util.getWindowUtils(window);
     this._contentWindow = window;
     this._targetElement = aJson.target;
-    this._targetIsEditable = this._targetElement instanceof Components.interfaces.nsIDOMXULTextBoxElement;
+    this._targetIsEditable = Util.isTextInput(this._targetElement) ||
+        this._targetElement instanceof Ci.nsIDOMXULTextBoxElement;
     if (!this._targetIsEditable) {
       this._onFail("not an editable?", this._targetElement);
       return;
     }
 
     let selection = this._getSelection();
     if (!selection) {
       this._onFail("no selection.");
       return;
     }
 
+    if (!this._getTargetElementValue()) {
+      this._onFail("Target element does not contain any content to select.");
+      return;
+    }
+
     if (!selection.isCollapsed) {
       this._mode = kSelectionMode;
       this._updateSelectionUI("start", true, true);
     } else {
       this._mode = kCaretMode;
       this._updateSelectionUI("caret", false, false, true);
     }
 
@@ -375,26 +383,43 @@ var ChromeSelectionHandler = {
     }
   },
 
   /*************************************************
    * Utilities
    */
 
   _getSelection: function _getSelection() {
+    let targetElementEditor = this._getTargetElementEditor();
+
+    return targetElementEditor ? targetElementEditor.selection : null;
+  },
+
+  _getTargetElementValue: function _getTargetElementValue() {
     if (this._targetElement instanceof Ci.nsIDOMXULTextBoxElement) {
-      return this._targetElement
-                 .QueryInterface(Components.interfaces.nsIDOMXULTextBoxElement)
-                 .editor.selection;
+      return this._targetElement.inputField.value;
+    } else if (Util.isTextInput(this._targetElement)) {
+      return this._targetElement.value;
     }
     return null;
   },
 
   _getSelectController: function _getSelectController() {
-    return this._targetElement
-                .QueryInterface(Components.interfaces.nsIDOMXULTextBoxElement)
-                .editor.selectionController;
+    let targetElementEditor = this._getTargetElementEditor();
+
+    return targetElementEditor ? targetElementEditor.selectionController : null;
   },
+
+  _getTargetElementEditor: function() {
+    if (this._targetElement instanceof Ci.nsIDOMXULTextBoxElement) {
+      return this._targetElement.QueryInterface(Ci.nsIDOMXULTextBoxElement)
+          .editor;
+    } else if (Util.isTextInput(this._targetElement)) {
+      return this._targetElement.QueryInterface(Ci.nsIDOMNSEditableElement)
+          .editor;
+    }
+    return null;
+  }
 };
 
 ChromeSelectionHandler.__proto__ = new SelectionPrototype();
 ChromeSelectionHandler.type = 1; // kChromeSelector
 
--- a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
+++ b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
@@ -245,15 +245,48 @@ gTests.push({
     sendTap(window, editRectangle.left + 50, editRectangle.top - 2);
 
     yield waitForCondition(function () {
       return SelectionHelperUI.isSelectionUIVisible;
     });
   }
 });
 
+gTests.push({
+  desc: "Bug 972428 - grippers not appearing under the URL field when adding " +
+        "text.",
+  run: function() {
+    let inputField = document.getElementById("urlbar-edit").inputField;
+    let inputFieldRectangle = inputField.getBoundingClientRect();
+
+    let chromeHandlerSpy = spyOnMethod(ChromeSelectionHandler, "msgHandler");
+
+    // Reset URL to empty string
+    inputField.value = "";
+    inputField.blur();
+
+    // Activate URL input
+    sendTap(window, inputFieldRectangle.left + 50, inputFieldRectangle.top + 5);
+
+    // Wait until ChromeSelectionHandler tries to attach selection
+    yield waitForCondition(() => chromeHandlerSpy.argsForCall.some(
+        (args) => args[0] == "Browser:SelectionAttach"));
+
+    ok(!SelectHelperUI.isSelectionUIVisible && !SelectHelperUI.isCaretUIVisible,
+        "Neither CaretUI nor SelectionUI is visible on empty input.");
+
+    inputField.value = "Test text";
+
+    sendTap(window, inputFieldRectangle.left + 10, inputFieldRectangle.top + 5);
+
+    yield waitForCondition(() => SelectionHelperUI.isCaretUIVisible);
+
+    chromeHandlerSpy.restore();
+  }
+});
+
 function test() {
   if (!isLandscapeMode()) {
     todo(false, "browser_selection_tests need landscape mode to run.");
     return;
   }
   runTests();
 }
--- a/browser/metro/base/tests/mochitest/head.js
+++ b/browser/metro/base/tests/mochitest/head.js
@@ -1028,21 +1028,24 @@ function runTests() {
   });
 }
 
 // wrap a method with a spy that records how and how many times it gets called
 // the spy is returned; use spy.restore() to put the original back
 function spyOnMethod(aObj, aMethod) {
   let origFunc = aObj[aMethod];
   let spy = function() {
-    spy.calledWith = Array.slice(arguments);
+    let callArguments = Array.slice(arguments);
     spy.callCount++;
+    spy.calledWith = callArguments;
+    spy.argsForCall.push(callArguments);
     return (spy.returnValue = origFunc.apply(aObj, arguments));
   };
   spy.callCount = 0;
+  spy.argsForCall = [];
   spy.restore = function() {
     return (aObj[aMethod] = origFunc);
   };
   return (aObj[aMethod] = spy);
 }
 
 // replace a method with a stub that records how and how many times it gets called
 // the stub is returned; use stub.restore() to put the original back