Bug 1429573 - Use input[type=number] in textbox[type=number] implementation. r=Paolo, surkov draft
authorTim Nguyen <ntim.bugs@gmail.com>
Fri, 09 Feb 2018 13:47:44 +0000
changeset 753023 f9b447dfbdd286f4411046552a8e85b9e59d8603
parent 752003 ea5e2a77362eabe55106bc001e14dabb4e3792bd
child 753024 5814692db66a39aaee880b422ecd85f680198818
push id98451
push userbmo:ntim.bugs@gmail.com
push dateFri, 09 Feb 2018 13:54:15 +0000
reviewersPaolo, surkov
bugs1429573
milestone60.0a1
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: 6XQdnJP65m0
accessible/tests/mochitest/tree/test_txtctrl.xul
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/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
@@ -16,18 +16,11 @@ textbox[type="number"] {
   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
@@ -2,24 +2,22 @@
  * 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
@@ -13,12 +13,11 @@ 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;
 }
-