Bug 472302 - get rid of fragile timeouts in textbox binding. r=enn
authorDão Gottwald <dao@mozilla.com>
Mon, 12 Jan 2009 20:55:37 +0100
changeset 23560 6e4203c77ce979d72b159008400d901f16158537
parent 23559 a12bffb28598d7679cafe78376be76e3dee76b88
child 23562 ffd18ce16f58537cc75b8db7a47ae4b19b594830
push id4605
push userdgottwald@mozilla.com
push dateMon, 12 Jan 2009 19:57:21 +0000
treeherdermozilla-central@6e4203c77ce9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersenn
bugs472302
milestone1.9.2a1pre
Bug 472302 - get rid of fragile timeouts in textbox binding. r=enn
toolkit/content/tests/chrome/test_bug418874.xul
toolkit/content/tests/widgets/test_textbox_emptytext.xul
toolkit/content/widgets/textbox.xml
toolkit/content/xul.css
--- a/toolkit/content/tests/chrome/test_bug418874.xul
+++ b/toolkit/content/tests/chrome/test_bug418874.xul
@@ -63,37 +63,31 @@
     <div id="content" style="display: none">
     </div>
     <pre id="test">
     </pre>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
-    SimpleTest.waitForExplicitFinish();
-
     function doTest() {
       var t1 = $("t1");
       var t2 = $("t2");
       t1.focus();
       synthesizeKey("1", {});
       var t1Enabled = {};
       var t1CanUndo = {};
       t1.editor.canUndo(t1Enabled, t1CanUndo);
       ok(t1CanUndo.value, "undo correctly enabled when emptyText was not changed through property");
+
       t2.emptyText = "reallyempty";
-
-      // Remaining tests after timeout - emptyText is displayed asynchronously.
-      setTimeout(function () {
-        is(t2.inputField.value, "reallyempty", "updated emptyText displayed");
-        t2.click();
-        synthesizeKey("2", {});
-        var t2Enabled = {};
-        var t2CanUndo = {};
-        t2.editor.canUndo(t2Enabled, t2CanUndo);
-        ok(t2CanUndo.value, "undo correctly enabled when emptyText explicitly changed through property");
-        SimpleTest.finish();
-      }, 100);
+      is(t2.inputField.value, "reallyempty", "updated emptyText displayed");
+      t2.click();
+      synthesizeKey("2", {});
+      var t2Enabled = {};
+      var t2CanUndo = {};
+      t2.editor.canUndo(t2Enabled, t2CanUndo);
+      ok(t2CanUndo.value, "undo correctly enabled when emptyText explicitly changed through property");
    }
 
   ]]></script>
 
 </window>
--- a/toolkit/content/tests/widgets/test_textbox_emptytext.xul
+++ b/toolkit/content/tests/widgets/test_textbox_emptytext.xul
@@ -38,23 +38,20 @@ function doTests() {
   ok("1" === t1.emptyText,      "emptyText persists after setting label");
 
   t1.value = 3;
   ok(!t1.hasAttribute("empty"), "value present => 'empty' attribute not present");
   ok("3" === t1.value,          "value setter/getter works while emptyText is present");
   ok("1" === t1.emptyText,      "emptyText persists after setting value");
 
   t1.value = "";
-  // More tests after a timeout, because the emptyText is displayed asynchronously.
-  setTimeout(function () {
-    is(t1.inputField.value, 1,  "emptyText is displayed");
-    is(t1.textLength, 0,        "textLength while emptyText is displayed");
+  is(t1.inputField.value, 1,    "emptyText is displayed");
+  is(t1.textLength, 0,          "textLength while emptyText is displayed");
 
-    t1.focus();
-    is(t1.inputField.value, "", "emptyText is not displayed as the textbox has focus");
+  t1.focus();
+  is(t1.inputField.value, "",   "emptyText is not displayed as the textbox has focus");
 
-    SimpleTest.finish();
-  }, 100);
+  SimpleTest.finish();
 }
 
   ]]></script>
 
 </window>
--- a/toolkit/content/widgets/textbox.xml
+++ b/toolkit/content/widgets/textbox.xml
@@ -159,33 +159,34 @@
             }
           }
         ]]></body>
       </method>
 
       <method name="_updateVisibleText">
         <body><![CDATA[
           if (!this.hasAttribute("focused") &&
-              !this.hasAttribute("empty") &&
               !this.value &&
               this.emptyText) {
-            // This section is a wee bit hacky; without the timeout, the CSS
-            // style corresponding to the "empty" attribute doesn't kick in
-            // until the text has changed, leading to an unpleasant moment
-            // where the emptyText flashes black before turning gray.
-            this.setAttribute("empty", true);
+
+            if (!this.hasAttribute("empty")) {
+              this.setAttribute("empty", "true");
 
-            setTimeout(function (textbox) {
-              if (textbox.hasAttribute("empty")) {
-                try {
-                  textbox.editor.transactionManager.beginBatch();
-                } catch (e) {}
-                textbox.inputField.value = textbox.emptyText;
-              }
-            }, 0, this);
+              // Hide the emptytext for a bit, in case the textbox will be focused subsequently
+              this.inputField.setAttribute("emptytextdelay", "true");
+              setTimeout(function (textbox) {
+                textbox.inputField.removeAttribute("emptytextdelay");
+              }, 100, this);
+
+              try {
+                this.editor.transactionManager.beginBatch();
+              } catch (e) {}
+            }
+
+            this.inputField.value = this.emptyText;
           }
         ]]></body>
       </method>
 
       <method name="_clearEmptyText">
         <body><![CDATA[
           if (this.hasAttribute("empty")) {
             this.inputField.value = "";
@@ -199,59 +200,61 @@
 
       <constructor><![CDATA[
         var str = this.boxObject.getProperty("value");
         if (str) {
           this.inputField.value = str;
           this.boxObject.removeProperty("value");
         }
 
-        // this.editor may not be initialized yet in
-        // bindings that inherit from xul:textbox, so
-        // do this after construction
-        setTimeout(function (a) {
-          a._updateVisibleText();
-          a._setNewlineHandling();
-        }, 0, this);
+        this._updateVisibleText();
+        this._setNewlineHandling();
       ]]></constructor>
 
       <destructor>
         <![CDATA[
           if (this.inputField.value)
             this.boxObject.setProperty('value', this.inputField.value);
           this.mInputField = null;
         ]]>
       </destructor>
 
     </implementation>
 
     <handlers>
       <handler event="focus" phase="capturing">
         <![CDATA[
-          this._clearEmptyText();
+          if (this.hasAttribute("focused"))
+            return;
+
+          switch (event.originalTarget) {
+            case this:
+              // Forward focus to actual HTML input
+              this.inputField.focus();
+              break;
+            case this.inputField:
+              this._clearEmptyText();
 
-          if (!this.hasAttribute("focused")) {
-            if (event.originalTarget == this)
-              this.inputField.focus(); // Forward focus to actual HTML input
-            else if (event.originalTarget != this.inputField)
-              return; // Allow other children (e.g. URL bar buttons) to get focus
-            else if (this.mIgnoreFocus)
-              this.mIgnoreFocus = false;
-            else if (this.clickSelectsAll) {
-              try {
-                const nsIEditorIMESupport =
-                        Components.interfaces.nsIEditorIMESupport;
-                var imeEditor = this.editor.QueryInterface(nsIEditorIMESupport);
-                if (!imeEditor || !imeEditor.composing)
-                  this.editor.selectAll();
-              } catch (e) {}
-            }
-
-            this.setAttribute("focused", "true");
+              if (this.mIgnoreFocus) {
+                this.mIgnoreFocus = false;
+              } else if (this.clickSelectsAll) {
+                try {
+                  const nsIEditorIMESupport =
+                          Components.interfaces.nsIEditorIMESupport;
+                  let imeEditor = this.editor.QueryInterface(nsIEditorIMESupport);
+                  if (!imeEditor || !imeEditor.composing)
+                    this.editor.selectAll();
+                } catch (e) {}
+              }
+              break;
+            default:
+              // Allow other children (e.g. URL bar buttons) to get focus
+              return;
           }
+          this.setAttribute("focused", "true");
         ]]>
       </handler>
 
       <handler event="blur" phase="capturing">
         <![CDATA[
           this.removeAttribute('focused');
           this._updateVisibleText();
         ]]>
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -653,16 +653,21 @@ html|*.textbox-input {
   text-shadow: inherit;
 }
 
 html|*.textbox-textarea {
   -moz-appearance: none !important;
   text-shadow: inherit;
 }
 
+textbox[empty="true"] html|*.textbox-input[emptytextdelay="true"] ,
+textbox[empty="true"] html|*.textbox-textarea[emptytextdelay="true"] {
+  color: transparent !important;
+}
+
 .textbox-input-box {
   -moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box");
 }
 
 .textbox-input-box[spellcheck="true"] {
   -moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box-spell");
 }