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 169510 7dabe9719703448804d5e58b156b281869bef3ac
parent 169509 dc6f5c320e6884b73c15848b2d867ec65d12c434
child 169511 f7f9328980d0a49408716f73d98698d18062a48e
push id26255
push userryanvm@gmail.com
push dateWed, 19 Feb 2014 20:34:53 +0000
treeherdermozilla-central@8497ffecbacd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm
bugs972428
milestone30.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 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