Bug 472302 - get rid of fragile timeouts in textbox binding. r=enn
--- 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");
}