Bug 1429573 - Use input[type=number] in textbox[type=number] implementation. r=Paolo,surkov
authorTim Nguyen <ntim.bugs@gmail.com>
Fri, 09 Feb 2018 21:54:36 +0000
changeset 403257 81891dee83daa9acc2174784ee11b8aec576781e
parent 403256 c0a2f744ef5c3cbddcf915f7532860a3c466214f
child 403258 fe8269ac41f5a40fd1e5a2e06ae193bc9462438c
push id99755
push userbtara@mozilla.com
push dateSat, 10 Feb 2018 10:02:59 +0000
treeherdermozilla-inbound@0417e1acfc10 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersPaolo, surkov
bugs1429573
milestone60.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 1429573 - Use input[type=number] in textbox[type=number] implementation. r=Paolo,surkov * The number is no longer selected on number input focus MozReview-Commit-ID: AmR5c6YKTCP
accessible/tests/mochitest/tree/test_txtctrl.xul
editor/reftests/xul/input.css
toolkit/content/tests/chrome/test_textbox_number.xul
toolkit/content/widgets/numberbox.xml
toolkit/content/widgets/textbox.xml
toolkit/themes/linux/global/in-content/common.css
toolkit/themes/linux/global/numberbox.css
toolkit/themes/osx/global/in-content/common.css
toolkit/themes/osx/global/numberbox.css
toolkit/themes/shared/in-content/common.inc.css
toolkit/themes/shared/non-mac.jar.inc.mn
toolkit/themes/windows/global/jar.mn
toolkit/themes/windows/global/numberbox.css
--- a/accessible/tests/mochitest/tree/test_txtctrl.xul
+++ b/accessible/tests/mochitest/tree/test_txtctrl.xul
@@ -70,20 +70,22 @@
 
       testAccessibleTree("txc_search_searchbutton", accTree);
 
       //////////////////////////////////////////////////////////////////////////
       // number textbox
 
       accTree =
         { SECTION: [
-          { ENTRY: [ { TEXT_LEAF: [] } ] },
+          { SPINBUTTON: [
+            { ENTRY: [ { TEXT_LEAF: [] } ] },
+            { PUSHBUTTON: [ ] },
+            { PUSHBUTTON: [ ] }
+          ] },
           { MENUPOPUP: [] },
-          { PUSHBUTTON: [] },
-          { PUSHBUTTON: [] }
         ] };
 
       testAccessibleTree("txc_number", accTree);
 
       //////////////////////////////////////////////////////////////////////////
       // password textbox
 
       accTree =
--- a/editor/reftests/xul/input.css
+++ b/editor/reftests/xul/input.css
@@ -34,26 +34,17 @@ html|input.empty {
     font-style: italic;
   }
 }
 
 html|input.num {
   text-align: end;
 }
 
-#mac html|input.num {
-  margin-inline-end: 8px;
-}
-
-#win html|input.num {
-  padding: 0 !important;
-}
-
+/* .textbox-input has 1px extra padding on Linux */
 #linux html|input.num {
-  margin-inline-end: 3px;
-  padding: 3px 4px;
+  padding-inline-end: 3px;
 }
 
 html|div.plainfield {
   color: -moz-fieldtext;
   white-space: pre;
 }
-
--- a/toolkit/content/tests/chrome/test_textbox_number.xul
+++ b/toolkit/content/tests/chrome/test_textbox_number.xul
@@ -1,18 +1,18 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
 <!--
   XUL Widget Test for textbox type="number"
   -->
 <window title="Textbox type='number' test" width="500" height="600"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>  
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>  
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
 
 <hbox>
   <textbox id="n1" type="number" size="4"/>
   <textbox id="n2" type="number" value="10" min="5" max="15"/>
 </hbox>
 <hbox>
   <textbox id="n4" type="number" size="4" value="-2" min="-8" max="18"/>
   <textbox id="n5" type="number" value="-17" min="-10" max="-3"/>
@@ -43,18 +43,16 @@ function doTests() {
   var n6 = $("n6");
 
   testValsMinMax(n1, "initial n1", 0, 0, Infinity);
   testValsMinMax(n2, "initial n2", 10, 5, 15);
   testValsMinMax(n4, "initial n4", -2, -8, 18);
   testValsMinMax(n5, "initial n5", -10, -10, -3);
   testValsMinMax(n6, "initial n6", 12, 12, 12);
 
-  ok(n1.spinButtons != null && n1.spinButtons.localName == "spinbuttons", "spinButtons set");
-
   // test changing the value
   n1.value = "1700";
   testVals(n1, "set value,", 1700);
   n1.value = 1600;
   testVals(n1, "set value int,", 1600);
   n2.value = "2";
   testVals(n2, "set value below min,", 5);
   n2.value = 2;
@@ -88,77 +86,29 @@ function doTests() {
   n1.min = 8;
   testValsMinMax(n1, "set integer min,", 8, 8, Infinity);
 
   // test changing the max
   n1.value = 25;
   n1.max = 22;
   testValsMinMax(n1, "set integer max,", 22, 8, 22);
 
-  // test increase and decrease via the keyboard and the spinbuttons
-  testIncreaseDecrease(n1, "integer", 1, 0, 8, 22);
-
-  // UI tests
-  n1.min = 5;
-  n1.max = 15;
-  n1.value = 5;
-  n1.focus();
-
-  var sb = n1.spinButtons;
-  var sbbottom = sb.getBoundingClientRect().bottom - sb.getBoundingClientRect().top - 2;
-
-  synthesizeKey("VK_UP", {});
-  testVals(n1, "key up", 6);
-
-  synthesizeKey("VK_DOWN", {});
-  testVals(n1, "key down", 5);
-
-  synthesizeMouse(sb, 2, 2, {});
-  testVals(n1, "spinbuttons up", 6);
-  synthesizeMouse(sb, 2, sbbottom, {});
-  testVals(n1, "spinbuttons down", 5);
-
-  n1.value = 15;
-  synthesizeKey("VK_UP", {});
-  testVals(n1, "key up at max", 15);
-  synthesizeMouse(sb, 2, 2, {});
-  testVals(n1, "spinbuttons up at max", 15);
-
-  n1.value = 5;
-  synthesizeKey("VK_DOWN", {});
-  testVals(n1, "key down at min", 5);
-  synthesizeMouse(sb, 2, sbbottom, {});
-  testVals(n1, "spinbuttons down at min", 5);
-
   // check read only state
   n1.readOnly = true;
   n1.min = -10;
   n1.max = 15;
   n1.value = 12;
+  n1.inputField.focus();
   // no events should fire and no changes should occur when the field is read only
   synthesizeKeyExpectEvent("VK_UP", { }, n1, "!change", "key up read only");
   is(n1.value, "12", "key up read only value");
   synthesizeKeyExpectEvent("VK_DOWN", { }, n1, "!change", "key down read only");
   is(n1.value, "12", "key down read only value");
 
-  synthesizeMouseExpectEvent(sb, 2, 2, { }, n1, "!change", "mouse up read only");
-  is(n1.value, "12", "mouse up read only value");
-  synthesizeMouseExpectEvent(sb, 2, sbbottom, { }, n1, "!change", "mouse down read only");
-  is(n1.value, "12", "mouse down read only value");
-
   n1.readOnly = false;
-  n1.disabled = true;
-  synthesizeMouseExpectEvent(sb, 2, 2, { }, n1, "!change", "mouse up disabled");
-  is(n1.value, "12", "mouse up disabled value");
-  synthesizeMouseExpectEvent(sb, 2, sbbottom, { }, n1, "!change", "mouse down disabled");
-  is(n1.value, "12", "mouse down disabled value");
-
-  var nsbrect = $("n8").spinButtons.getBoundingClientRect();
-  ok(nsbrect.left == 0 && nsbrect.top == 0 && nsbrect.right == 0, nsbrect.bottom == 0,
-     "hidespinbuttons");
 
   var n9 = $("n9");
   is(n9.value, "0", "initial value");
   n9.select();
   synthesizeKey("4", {});
   is(inputEventCount, 1, "input event count");
   is(inputEventValue, "4", "input value");
   is(n9.value, "4", "updated value");
@@ -197,45 +147,13 @@ function testVals(nb, name, valueNumber,
 }
 
 function testValsMinMax(nb, name, valueNumber, min, max, valueFieldNumber) {
   testVals(nb, name, valueNumber, valueFieldNumber);
   SimpleTest.is(nb.min, min, name + " min is " + min);
   SimpleTest.is(nb.max, max, name + " max is " + max);
 }
 
-function testIncreaseDecrease(nb, testid, increment, fixedCount, min, max) {
-  testid += " ";
-
-  nb.focus();
-  nb.value = min;
-
-  // pressing the cursor up and down keys should adjust the value
-  synthesizeKeyExpectEvent("VK_UP", { }, nb, "change", testid + "key up");
-  is(nb.value, String(min + increment), testid + "key up");
-  nb.value = max;
-  synthesizeKeyExpectEvent("VK_UP", { }, nb, "!change", testid + "key up at max");
-  is(nb.value, String(max), testid + "key up at max");
-  synthesizeKeyExpectEvent("VK_DOWN", { }, nb, "change", testid + "key down");
-  is(nb.value, String(max - increment), testid + "key down");
-  nb.value = min;
-  synthesizeKeyExpectEvent("VK_DOWN", { }, nb, "!change", testid + "key down at min");
-  is(nb.value, String(min), testid + "key down at min");
-
-  // check pressing the spinbutton arrows
-  var sb = nb.spinButtons;
-  var sbbottom = sb.getBoundingClientRect().bottom - sb.getBoundingClientRect().top - 2;
-  nb.value = min;
-  synthesizeMouseExpectEvent(sb, 2, 2, { }, nb, "change", testid + "mouse up");
-  is(nb.value, String(min + increment), testid + "mouse up");
-  nb.value = max;
-  synthesizeMouseExpectEvent(sb, 2, 2, { }, nb, "!change", testid + "mouse up at max");
-  synthesizeMouseExpectEvent(sb, 2, sbbottom, { }, nb, "change", testid + "mouse down");
-  is(nb.value, String(max - increment), testid + "mouse down");
-  nb.value = min;
-  synthesizeMouseExpectEvent(sb, 2, sbbottom, { }, nb, "!change", testid + "mouse down at min");
-}
-
 SimpleTest.waitForFocus(doTests);
 
   ]]></script>
 
 </window>
--- a/toolkit/content/widgets/numberbox.xml
+++ b/toolkit/content/widgets/numberbox.xml
@@ -14,38 +14,26 @@
            extends="chrome://global/content/bindings/textbox.xml#textbox">
 
     <resources>
       <stylesheet src="chrome://global/skin/numberbox.css"/>
     </resources>
 
     <content>
       <xul:hbox class="textbox-input-box numberbox-input-box" flex="1" xbl:inherits="context,disabled,focused">
-        <html:input class="numberbox-input textbox-input" anonid="input"
-                    xbl:inherits="value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
+        <html:input class="numberbox-input textbox-input" type="number" anonid="input"
+                    xbl:inherits="value,min,max,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
       </xul:hbox>
-      <xul:spinbuttons anonid="buttons" xbl:inherits="disabled,hidden=hidespinbuttons"/>
     </content>
 
     <implementation>
       <field name="_valueEntered">false</field>
-      <field name="_spinButtons">null</field>
       <field name="_value">0</field>
 
-      <property name="spinButtons" readonly="true">
-        <getter>
-          <![CDATA[
-            if (!this._spinButtons)
-              this._spinButtons = document.getAnonymousElementByAttribute(this, "anonid", "buttons");
-            return this._spinButtons;
-          ]]>
-        </getter>
-      </property>
-
-      <property name="value" onget="return '' + this.valueNumber"
+      <property name="value" onget="return String(this.valueNumber)"
                              onset="return this.valueNumber = val;"/>
 
       <property name="valueNumber">
         <getter>
           if (this._valueEntered) {
             var newval = this.inputField.value;
             this._validateValue(newval);
           }
@@ -88,57 +76,16 @@
           this.setAttribute("max", val);
           if (this.valueNumber > val)
             this._validateValue(val);
           return val;
         ]]>
         </setter>
       </property>
 
-      <method name="_modifyUp">
-        <body>
-          <![CDATA[
-            if (this.disabled || this.readOnly)
-              return;
-            var oldval = this.valueNumber;
-            var newval = this._validateValue(this.valueNumber + 1);
-            this.inputField.select();
-            if (oldval != newval)
-              this._fireChange();
-          ]]>
-        </body>
-      </method>
-      <method name="_modifyDown">
-        <body>
-          <![CDATA[
-            if (this.disabled || this.readOnly)
-              return;
-            var oldval = this.valueNumber;
-            var newval = this._validateValue(this.valueNumber - 1);
-            this.inputField.select();
-            if (oldval != newval)
-              this._fireChange();
-          ]]>
-        </body>
-      </method>
-
-      <method name="_enableDisableButtons">
-        <body>
-          <![CDATA[
-            var buttons = this.spinButtons;
-            if (this.disabled || this.readOnly) {
-              buttons.decreaseDisabled = buttons.increaseDisabled = true;
-            } else {
-              buttons.decreaseDisabled = (this.valueNumber <= this.min);
-              buttons.increaseDisabled = (this.valueNumber >= this.max);
-            }
-          ]]>
-        </body>
-      </method>
-
       <method name="_validateValue">
         <parameter name="aValue"/>
         <body>
           <![CDATA[
             aValue = Number(aValue) || 0;
             aValue = Math.round(aValue);
 
             var min = this.min;
@@ -147,18 +94,16 @@
               aValue = min;
             else if (aValue > max)
               aValue = max;
 
             this._valueEntered = false;
             this._value = Number(aValue);
             this.inputField.value = aValue;
 
-            this._enableDisableButtons();
-
             return aValue;
           ]]>
         </body>
       </method>
 
       <method name="_fireChange">
         <body>
           var evt = document.createEvent("Events");
@@ -189,35 +134,18 @@
               return;
 
             if (event.charCode < 48 || event.charCode > 57)
               event.preventDefault();
           }
         ]]>
       </handler>
 
-      <handler event="keypress" keycode="VK_UP">
-        this._modifyUp();
-      </handler>
-
-      <handler event="keypress" keycode="VK_DOWN">
-        this._modifyDown();
-      </handler>
-
-      <handler event="up" preventdefault="true">
-        this._modifyUp();
-      </handler>
-
-      <handler event="down" preventdefault="true">
-        this._modifyDown();
-      </handler>
-
       <handler event="change">
         if (event.originalTarget == this.inputField) {
-          var newval = this.inputField.value;
-          this._validateValue(newval);
+          this._validateValue(this.inputField.value);
         }
       </handler>
     </handlers>
 
   </binding>
 
 </bindings>
--- a/toolkit/content/widgets/textbox.xml
+++ b/toolkit/content/widgets/textbox.xml
@@ -122,17 +122,22 @@
                                       onget="return this.inputField.selectionStart;"/>
       <property name="selectionEnd"   onset="this.inputField.selectionEnd = val; return val;"
                                       onget="return this.inputField.selectionEnd;"/>
 
       <method name="setSelectionRange">
         <parameter name="aSelectionStart"/>
         <parameter name="aSelectionEnd"/>
         <body>
-          this.inputField.setSelectionRange( aSelectionStart, aSelectionEnd );
+          // According to https://html.spec.whatwg.org/#do-not-apply,
+          // setSelectionRange() is only available on a limited set of input types.
+          if (this.inputField.type == "text" ||
+              this.inputField.tagName == "html:textarea") {
+            this.inputField.setSelectionRange( aSelectionStart, aSelectionEnd );
+          }
         </body>
       </method>
 
       <method name="_setNewlineHandling">
         <body><![CDATA[
           var str = this.getAttribute("newlines");
           if (str && this.editor) {
             const nsIPlaintextEditor = Components.interfaces.nsIPlaintextEditor;
@@ -183,36 +188,39 @@
     </implementation>
 
     <handlers>
       <handler event="focus" phase="capturing">
         <![CDATA[
           if (this.hasAttribute("focused"))
             return;
 
-          switch (event.originalTarget) {
-            case this:
-              // Forward focus to actual HTML input
-              this.inputField.focus();
-              break;
-            case this.inputField:
-              if (this.mIgnoreFocus) {
-                this.mIgnoreFocus = false;
-              } else if (this.clickSelectsAll) {
-                try {
-                  if (!this.editor || !this.editor.composing)
-                    this.editor.selectAll();
-                } catch (e) {}
-              }
-              break;
-            default:
-              // Allow other children (e.g. URL bar buttons) to get focus
-              return;
+          let { originalTarget } = event;
+          if (originalTarget == this) {
+            // Forward focus to actual HTML input
+            this.inputField.focus();
+            this.setAttribute("focused", "true");
+            return;
           }
-          this.setAttribute("focused", "true");
+
+          // We check for the parent nodes to support input[type=number] where originalTarget may be an
+          // anonymous child input.
+          if (originalTarget == this.inputField ||
+              originalTarget.localName == "input" && originalTarget.parentNode.parentNode == this.inputField) {
+            if (this.mIgnoreFocus) {
+              this.mIgnoreFocus = false;
+            } else if (this.clickSelectsAll) {
+              try {
+                if (!this.editor || !this.editor.composing)
+                  this.editor.selectAll();
+              } catch (e) {}
+            }
+            this.setAttribute("focused", "true");
+          }
+          // Otherwise, allow other children (e.g. URL bar buttons) to get focus
         ]]>
       </handler>
 
       <handler event="blur" phase="capturing">
         <![CDATA[
           this.removeAttribute("focused");
 
           // don't trigger clickSelectsAll when switching application windows
@@ -224,17 +232,17 @@
       </handler>
 
       <handler event="mousedown">
         <![CDATA[
           this.mIgnoreClick = this.hasAttribute("focused");
 
           if (!this.mIgnoreClick) {
             this.mIgnoreFocus = true;
-            this.inputField.setSelectionRange(0, 0);
+            this.setSelectionRange(0, 0);
             if (event.originalTarget == this ||
                 event.originalTarget == this.inputField.parentNode)
               this.inputField.focus();
           }
         ]]>
       </handler>
 
       <handler event="click" action="this._maybeSelectAll();"/>
--- a/toolkit/themes/linux/global/in-content/common.css
+++ b/toolkit/themes/linux/global/in-content/common.css
@@ -76,20 +76,16 @@ xul|*.numberbox-input-box {
   border-width: 0;
 }
 
 xul|menulist:-moz-focusring > xul|*.menulist-label-box,
 html|input[type="checkbox"]:-moz-focusring + html|label:before {
   outline: 1px dotted;
 }
 
-xul|spinbuttons {
-  -moz-appearance: none;
-}
-
 xul|treechildren::-moz-tree-row(multicol, odd) {
   background-color: var(--in-content-box-background-odd);
 }
 
 /* These rules are duplicated from common.inc.css
  * because above -moz-tree-row(multicol, odd) rule
  * overrides also hover/selected states.
  */
--- a/toolkit/themes/linux/global/numberbox.css
+++ b/toolkit/themes/linux/global/numberbox.css
@@ -4,30 +4,15 @@
 
 /* ===== numberbox.css ==================================================
   == Styles used by the XUL textbox type="number" element.
   ======================================================================= */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 
-textbox[type="number"] {
-  -moz-appearance: none;
-  padding: 0 !important;
-  border: none;
-  cursor: default;
-  background-color: transparent;
-}
-
 html|*.numberbox-input {
   text-align: right;
 }
 
-.numberbox-input-box {
-  -moz-box-align: center;
-  -moz-appearance: spinner-textfield;
-  margin-right: -1px;
-  padding: 3px;
+textbox[type="number"][hidespinbuttons="true"] html|*.numberbox-input {
+  -moz-appearance: textfield !important;
 }
-
-textbox[hidespinbuttons="true"] > .numberbox-input-box {
-  -moz-appearance: textfield;
-}
--- a/toolkit/themes/osx/global/in-content/common.css
+++ b/toolkit/themes/osx/global/in-content/common.css
@@ -52,21 +52,16 @@ xul|*.help-button > xul|*.button-box > x
 xul|*.checkbox-icon {
   margin-right: 0;
 }
 
 xul|*.radio-icon {
   margin-inline-end: 0;
 }
 
-xul|*.numberbox-input-box {
-  -moz-appearance: none;
-  border-width: 0;
-}
-
 xul|*.text-link:-moz-focusring {
   color: var(--in-content-link-highlight);
   text-decoration: underline;
   box-shadow: none;
 }
 
 xul|button:-moz-focusring,
 xul|menulist:-moz-focusring,
@@ -78,39 +73,24 @@ xul|tab:-moz-focusring > .tab-middle > .
   outline-offset: 1px;
   -moz-outline-radius: 2px;
 }
 
 xul|radio[focused="true"] > .radio-check {
   -moz-outline-radius: 100%;
 }
 
-xul|spinbuttons {
-  -moz-appearance: none;
-}
-
-xul|*.spinbuttons-up {
-  margin-top: 0 !important;
+html|*.numberbox-input::-moz-number-spin-up {
   border-radius: 4px 4px 0 0;
 }
 
-xul|*.spinbuttons-down  {
-  margin-bottom: 0 !important;
+html|*.numberbox-input::-moz-number-spin-down  {
   border-radius: 0 0 4px 4px;
 }
 
-xul|*.spinbuttons-button > xul|*.button-box {
-  padding-inline-start: 2px !important;
-  padding-inline-end: 3px !important;
-}
-
-xul|*.spinbuttons-button > xul|*.button-box > xul|*.button-text {
-  display: none;
-}
-
 xul|textbox[type="search"]:not([searchbutton]) > .textbox-input-box > .textbox-search-sign {
   list-style-image: url(chrome://global/skin/icons/search-textbox.svg);
   margin-inline-end: 5px;
 }
 
 html|button {
   /* XUL button min-width */
   min-width: 79px;
--- a/toolkit/themes/osx/global/numberbox.css
+++ b/toolkit/themes/osx/global/numberbox.css
@@ -1,25 +1,15 @@
 /* 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/. */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 
-textbox[type="number"] {
-  -moz-appearance: none;
-  -moz-box-align: center;
-  padding: 0 !important;
-  border: none;
-  background-color: transparent;
-  cursor: default;
-}
-
 html|*.numberbox-input {
   text-align: right;
   padding: 0 1px !important;
 }
 
-.numberbox-input-box {
-  -moz-appearance: textfield;
-  margin-right: 4px;
+textbox[type="number"][hidespinbuttons="true"] html|*.numberbox-input {
+  -moz-appearance: textfield !important;
 }
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -160,17 +160,19 @@ html|button {
   padding: 3px;
 }
 
 /* xul buttons and menulists */
 
 *|button,
 html|select,
 xul|colorpicker[type="button"],
-xul|menulist {
+xul|menulist,
+html|*.numberbox-input::-moz-number-spin-up,
+html|*.numberbox-input::-moz-number-spin-down {
   -moz-appearance: none;
   min-height: 30px;
   color: var(--in-content-text-color);
   border: 1px solid var(--in-content-box-border-color);
   border-radius: 2px;
   background-color: var(--in-content-page-background);
   margin: 4px 8px;
   /* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */
@@ -196,32 +198,37 @@ html|select:not([size]):not([multiple]) 
 }
 
 html|select:not([size]):not([multiple]):dir(rtl){
   background-position: left 3px center;
 }
 
 html|button:enabled:hover,
 html|select:not([size]):not([multiple]):enabled:hover,
+html|*.numberbox-input::-moz-number-spin-up:hover,
+html|*.numberbox-input::-moz-number-spin-down:hover,
 xul|button:not([disabled="true"]):hover,
 xul|colorpicker[type="button"]:not([disabled="true"]):hover,
 xul|menulist:not([disabled="true"]):hover {
   background-color: var(--in-content-box-background-hover);
 }
 
 html|button:enabled:hover:active,
 html|select:not([size]):not([multiple]):enabled:hover:active,
+html|*.numberbox-input::-moz-number-spin-up:hover:active,
+html|*.numberbox-input::-moz-number-spin-down:hover:active,
 xul|button:not([disabled="true"]):hover:active,
 xul|colorpicker[type="button"]:not([disabled="true"]):hover:active,
 xul|menulist[open="true"]:not([disabled="true"]) {
   background-color: var(--in-content-box-background-active);
 }
 
 html|button:disabled,
 html|select:disabled,
+html|*.numberbox-input:disabled::-moz-number-spin-box,
 xul|button[disabled="true"],
 xul|colorpicker[type="button"][disabled="true"],
 xul|menulist[disabled="true"],
 xul|listbox[disabled="true"] {
   opacity: 0.5;
 }
 
 xul|listbox[disabled="true"] xul|listitem:hover {
@@ -342,50 +349,32 @@ html|*.help-button:hover {
   background-color: var(--in-content-category-background-hover);
 }
 
 html|*.help-button:hover:active {
   stroke: #666;
   background-color: var(--in-content-category-background-active);
 }
 
-xul|*.spinbuttons-button {
+html|*.numberbox-input::-moz-number-spin-up,
+html|*.numberbox-input::-moz-number-spin-down {
+  padding: 5px 8px;
+  margin: 1px;
+  margin-inline-start: 10px;
   min-height: initial;
-  margin-inline-start: 10px !important;
-  margin-inline-end: 2px !important;
-}
-
-xul|*.spinbuttons-up {
-  margin-top: 2px !important;
-  border-radius: 1px 1px 0 0;
-}
-
-xul|*.spinbuttons-down  {
-  margin-bottom: 2px !important;
-  border-radius: 0 0 1px 1px;
 }
 
-xul|*.spinbuttons-button > xul|*.button-box {
-  padding: 1px 5px 2px !important;
-}
-
-xul|*.spinbuttons-up > xul|*.button-box > xul|*.button-icon {
-  list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
+html|*.numberbox-input::-moz-number-spin-up {
+  border-radius: 1px 1px 0 0;
+  background-image: url("chrome://global/skin/arrow/arrow-up.gif");
 }
 
-xul|*.spinbuttons-up[disabled="true"] > xul|*.button-box > xul|*.button-icon {
-  list-style-image: url("chrome://global/skin/arrow/arrow-up-dis.gif");
-}
-
-xul|*.spinbuttons-down > xul|*.button-box > xul|*.button-icon {
-  list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
-}
-
-xul|*.spinbuttons-down[disabled="true"] > xul|*.button-box > xul|*.button-icon {
-  list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
+html|*.numberbox-input::-moz-number-spin-down  {
+  border-radius: 0 0 1px 1px;
+  background-image: url("chrome://global/skin/arrow/arrow-dn.gif");
 }
 
 xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker {
   -moz-appearance: none;
   margin-inline-end: 4px;
   padding: 0;
   border: none;
   background-color: transparent;
--- a/toolkit/themes/shared/non-mac.jar.inc.mn
+++ b/toolkit/themes/shared/non-mac.jar.inc.mn
@@ -16,19 +16,17 @@
   skin/classic/global/resizer.css                          (../../windows/global/resizer.css)
   skin/classic/global/richlistbox.css                      (../../windows/global/richlistbox.css)
   skin/classic/global/scrollbars.css                       (../../windows/global/xulscrollbars.css)
   skin/classic/global/spinbuttons.css                      (../../windows/global/spinbuttons.css)
   skin/classic/global/tabprompts.css                       (../../windows/global/tabprompts.css)
   skin/classic/global/wizard.css                           (../../windows/global/wizard.css)
 
   skin/classic/global/arrow/arrow-dn.gif                   (../../windows/global/arrow/arrow-dn.gif)
-  skin/classic/global/arrow/arrow-dn-dis.gif               (../../windows/global/arrow/arrow-dn-dis.gif)
   skin/classic/global/arrow/arrow-up.gif                   (../../windows/global/arrow/arrow-up.gif)
-  skin/classic/global/arrow/arrow-up-dis.gif               (../../windows/global/arrow/arrow-up-dis.gif)
   skin/classic/global/arrow/panelarrow-horizontal.svg      (../../windows/global/arrow/panelarrow-horizontal.svg)
   skin/classic/global/arrow/panelarrow-vertical.svg        (../../windows/global/arrow/panelarrow-vertical.svg)
 
 * skin/classic/global/dirListing/dirListing.css            (../../windows/global/dirListing/dirListing.css)
   skin/classic/global/icons/error-16.png                   (../../windows/global/icons/error-16.png)
   skin/classic/global/icons/question-16.png                (../../windows/global/icons/question-16.png)
   skin/classic/global/icons/question-64.png                (../../windows/global/icons/question-64.png)
   skin/classic/global/icons/resizer-rtl.png                (../../windows/global/icons/resizer-rtl.png)
--- a/toolkit/themes/windows/global/jar.mn
+++ b/toolkit/themes/windows/global/jar.mn
@@ -31,16 +31,18 @@ toolkit.jar:
   skin/classic/global/toolbar.css
   skin/classic/global/toolbarbutton.css
 * skin/classic/global/tree.css
 * skin/classic/global/alerts/alert.css                     (alerts/alert.css)
   skin/classic/global/arrow/arrow-lft.gif                  (arrow/arrow-lft.gif)
   skin/classic/global/arrow/arrow-lft-dis.gif              (arrow/arrow-lft-dis.gif)
   skin/classic/global/arrow/arrow-rit.gif                  (arrow/arrow-rit.gif)
   skin/classic/global/arrow/arrow-rit-dis.gif              (arrow/arrow-rit-dis.gif)
+  skin/classic/global/arrow/arrow-up-dis.gif               (arrow/arrow-up-dis.gif)
+  skin/classic/global/arrow/arrow-dn-dis.gif               (arrow/arrow-dn-dis.gif)
   skin/classic/global/dirListing/folder.png                (dirListing/folder.png)
   skin/classic/global/dirListing/up.png                    (dirListing/up.png)
   skin/classic/global/icons/blacklist_favicon.png          (icons/blacklist_favicon.png)
   skin/classic/global/icons/blacklist_large.png            (icons/blacklist_large.png)
   skin/classic/global/icons/Error.png                      (icons/Error.png)
   skin/classic/global/icons/collapse.png                   (icons/collapse.png)
   skin/classic/global/icons/expand.png                     (icons/expand.png)
   skin/classic/global/icons/folder-item.png                (icons/folder-item.png)
--- a/toolkit/themes/windows/global/numberbox.css
+++ b/toolkit/themes/windows/global/numberbox.css
@@ -4,21 +4,15 @@
 
 /* ===== numberbox.css ==================================================
   == Styles used by the XUL textbox type="number" element.
   ======================================================================= */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 
-textbox[type="number"] {
-  padding: 0 !important;
-  cursor: default;
-}
-
 html|*.numberbox-input {
   text-align: right;
 }
 
-.numberbox-input-box {
-  -moz-box-align: center;
+textbox[type="number"][hidespinbuttons="true"] html|*.numberbox-input {
+  -moz-appearance: textfield !important;
 }
-