Bug 935508 - Implement native theming of <input type=number>. r=roc
authorJonathan Watt <jwatt@jwatt.org>
Thu, 05 Dec 2013 16:20:34 +0000
changeset 174737 45decd50b1180a19bd4e57f44f6db969f331ee68
parent 174736 8a9e9debc9b19226ef62a5c434d538a634d6d1bc
child 174738 895031613c5021d5e796def2bf1e7c0250fa82e3
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs935508
milestone28.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 935508 - Implement native theming of <input type=number>. r=roc
content/html/content/src/HTMLInputElement.cpp
content/html/content/src/HTMLInputElement.h
layout/forms/nsNumberControlFrame.cpp
layout/forms/nsNumberControlFrame.h
layout/style/forms.css
widget/cocoa/nsNativeThemeCocoa.h
widget/cocoa/nsNativeThemeCocoa.mm
widget/xpwidgets/nsNativeTheme.cpp
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -3323,27 +3323,37 @@ HTMLInputElement::PreHandleEvent(nsEvent
       // PostHandleEvent) because we don't want to let content preventDefault()
       // the end of the spin.
       if (aVisitor.mEvent->message == NS_MOUSE_MOVE) {
         // Be aggressive about stopping the spin:
         bool stopSpin = true;
         nsNumberControlFrame* numberControlFrame =
           do_QueryFrame(GetPrimaryFrame());
         if (numberControlFrame) {
+          bool oldNumberControlSpinTimerSpinsUpValue =
+                 mNumberControlSpinnerSpinsUp;
           switch (numberControlFrame->GetSpinButtonForPointerEvent(
                     aVisitor.mEvent->AsMouseEvent())) {
           case nsNumberControlFrame::eSpinButtonUp:
             mNumberControlSpinnerSpinsUp = true;
             stopSpin = false;
             break;
           case nsNumberControlFrame::eSpinButtonDown:
             mNumberControlSpinnerSpinsUp = false;
             stopSpin = false;
             break;
           }
+          if (mNumberControlSpinnerSpinsUp !=
+                oldNumberControlSpinTimerSpinsUpValue) {
+            nsNumberControlFrame* numberControlFrame =
+              do_QueryFrame(GetPrimaryFrame());
+            if (numberControlFrame) {
+              numberControlFrame->SpinnerStateChanged();
+            }
+          }
         }
         if (stopSpin) {
           StopNumberControlSpinnerSpin();
         }
       } else if (aVisitor.mEvent->message == NS_MOUSE_BUTTON_UP) {
         StopNumberControlSpinnerSpin();
       }
     }
@@ -3355,16 +3365,25 @@ HTMLInputElement::PreHandleEvent(nsEvent
           // Tell our frame it's getting focus so that it can make sure focus
           // is moved to our anonymous text control.
           nsNumberControlFrame* numberControlFrame =
             do_QueryFrame(GetPrimaryFrame());
           if (numberControlFrame) {
             numberControlFrame->HandleFocusEvent(aVisitor.mEvent);
           }
         }
+        if (frame->IsThemed()) {
+          // Our frame's nested <input type=text> will be invalidated when it
+          // loses focus, but since we are also native themed we need to make
+          // sure that our entire area is repainted since any focus highlight
+          // from the theme should be removed from us (the repainting of the
+          // sub-area occupied by the anon text control is not enough to do
+          // that).
+          frame->InvalidateFrame();
+        }
       }
     } else if (aVisitor.mEvent->message == NS_KEY_UP) {
       WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
       if ((keyEvent->keyCode == NS_VK_UP || keyEvent->keyCode == NS_VK_DOWN) &&
           !(keyEvent->IsShift() || keyEvent->IsControl() ||
             keyEvent->IsAlt() || keyEvent->IsMeta() ||
             keyEvent->IsAltGraph() || keyEvent->IsFn() ||
             keyEvent->IsOS())) {
@@ -3506,31 +3525,43 @@ HTMLInputElement::StartNumberControlSpin
 
   mNumberControlSpinnerIsSpinning = true;
 
   nsRepeatService::GetInstance()->Start(HandleNumberControlSpin, this);
 
   // Capture the mouse so that we can tell if the pointer moves from one
   // spin button to the other, or to some other element:
   nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
+
+  nsNumberControlFrame* numberControlFrame =
+    do_QueryFrame(GetPrimaryFrame());
+  if (numberControlFrame) {
+    numberControlFrame->SpinnerStateChanged();
+  }
 }
 
 void
 HTMLInputElement::StopNumberControlSpinnerSpin()
 {
   if (mNumberControlSpinnerIsSpinning) {
     if (nsIPresShell::GetCapturingContent() == this) {
       nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
     }
 
     nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
 
     mNumberControlSpinnerIsSpinning = false;
 
     FireChangeEventIfNeeded();
+
+    nsNumberControlFrame* numberControlFrame =
+      do_QueryFrame(GetPrimaryFrame());
+    if (numberControlFrame) {
+      numberControlFrame->SpinnerStateChanged();
+    }
   }
 }
 
 void
 HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection)
 {
   Decimal newValue = Decimal::nan(); // unchanged if value will not change
 
--- a/content/html/content/src/HTMLInputElement.h
+++ b/content/html/content/src/HTMLInputElement.h
@@ -683,16 +683,26 @@ public:
   void StepNumberControlForUserEvent(int32_t aDirection);
 
   /**
    * The callback function used by the nsRepeatService that we use to spin the
    * spinner for <input type=number>.
    */
   static void HandleNumberControlSpin(void* aData);
 
+  bool NumberSpinnerUpButtonIsDepressed() const
+  {
+    return mNumberControlSpinnerIsSpinning && mNumberControlSpinnerSpinsUp;
+  }
+
+  bool NumberSpinnerDownButtonIsDepressed() const
+  {
+    return mNumberControlSpinnerIsSpinning && !mNumberControlSpinnerSpinsUp;
+  }
+
   bool MozIsTextField(bool aExcludePassword);
 
   nsIEditor* GetEditor();
 
   // XPCOM SetUserInput() is OK
 
   // XPCOM GetPhonetic() is OK
 
--- a/layout/forms/nsNumberControlFrame.cpp
+++ b/layout/forms/nsNumberControlFrame.cpp
@@ -9,16 +9,17 @@
 #include "nsIFocusManager.h"
 #include "nsIPresShell.h"
 #include "nsFocusManager.h"
 #include "nsFontMetrics.h"
 #include "nsFormControlFrame.h"
 #include "nsGkAtoms.h"
 #include "nsINodeInfo.h"
 #include "nsINameSpaceManager.h"
+#include "nsThemeConstants.h"
 #include "mozilla/BasicEvents.h"
 #include "nsContentUtils.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsContentList.h"
 #include "nsStyleSet.h"
 #include "nsIDOMMutationEvent.h"
 
 using namespace mozilla;
@@ -313,40 +314,159 @@ nsNumberControlFrame::GetType() const
 }
 
 HTMLInputElement*
 nsNumberControlFrame::GetAnonTextControl()
 {
   return mTextField ? HTMLInputElement::FromContent(mTextField) : nullptr;
 }
 
+/* static */ nsNumberControlFrame*
+nsNumberControlFrame::GetNumberControlFrameForTextField(nsIFrame* aFrame)
+{
+  // If aFrame is the anon text field for an <input type=number> then we expect
+  // the frame of its mContent's grandparent to be that input's frame. We
+  // have to check for this via the content tree because we don't know whether
+  // extra frames will be wrapped around any of the elements between aFrame and
+  // the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
+  nsIContent* content = aFrame->GetContent();
+  if (content->IsInNativeAnonymousSubtree() &&
+      content->GetParent() && content->GetParent()->GetParent()) {
+    nsIContent* grandparent = content->GetParent()->GetParent();
+    if (grandparent->IsHTML(nsGkAtoms::input) &&
+        grandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+                                 nsGkAtoms::number, eCaseMatters)) {
+      return do_QueryFrame(grandparent->GetPrimaryFrame());
+    }
+  }
+  return nullptr;
+}
+
+/* static */ nsNumberControlFrame*
+nsNumberControlFrame::GetNumberControlFrameForSpinButton(nsIFrame* aFrame)
+{
+  // If aFrame is a spin button for an <input type=number> then we expect the
+  // frame of its mContent's great-grandparent to be that input's frame. We
+  // have to check for this via the content tree because we don't know whether
+  // extra frames will be wrapped around any of the elements between aFrame and
+  // the nsNumberControlFrame that we're looking for (e.g. flex wrappers).
+  nsIContent* content = aFrame->GetContent();
+  if (content->IsInNativeAnonymousSubtree() &&
+      content->GetParent() && content->GetParent()->GetParent() &&
+      content->GetParent()->GetParent()->GetParent()) {
+    nsIContent* greatgrandparent = content->GetParent()->GetParent()->GetParent();
+    if (greatgrandparent->IsHTML(nsGkAtoms::input) &&
+        greatgrandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+                                      nsGkAtoms::number, eCaseMatters)) {
+      return do_QueryFrame(greatgrandparent->GetPrimaryFrame());
+    }
+  }
+  return nullptr;
+}
+
 int32_t
 nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const
 {
   MOZ_ASSERT(aEvent->eventStructType == NS_MOUSE_EVENT,
              "Unexpected event type");
 
   if (aEvent->originalTarget == mSpinUp) {
     return eSpinButtonUp;
   }
   if (aEvent->originalTarget == mSpinDown) {
     return eSpinButtonDown;
   }
+  if (aEvent->originalTarget == mSpinBox) {
+    // In the case that the up/down buttons are hidden (display:none) we use
+    // just the spin box element, spinning up if the pointer is over the top
+    // half of the element, or down if it's over the bottom half. This is
+    // important to handle since this is the state things are in for the
+    // default UA style sheet. See the comment in forms.css for why.
+    LayoutDeviceIntPoint absPoint = aEvent->refPoint;
+    nsPoint point =
+      nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
+                       LayoutDeviceIntPoint::ToUntyped(absPoint),
+                       mSpinBox->GetPrimaryFrame());
+    if (point != nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
+      if (point.y < mSpinBox->GetPrimaryFrame()->GetSize().height / 2) {
+        return eSpinButtonUp;
+      }
+      return eSpinButtonDown;
+    }
+  }
   return eSpinButtonNone;
 }
 
 void
+nsNumberControlFrame::SpinnerStateChanged() const
+{
+  nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame();
+  if (spinUpFrame && spinUpFrame->IsThemed()) {
+    spinUpFrame->InvalidateFrame();
+  }
+  nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame();
+  if (spinDownFrame && spinDownFrame->IsThemed()) {
+    spinDownFrame->InvalidateFrame();
+  }
+}
+
+bool
+nsNumberControlFrame::SpinnerUpButtonIsDepressed() const
+{
+  return HTMLInputElement::FromContent(mContent)->
+           NumberSpinnerUpButtonIsDepressed();
+}
+
+bool
+nsNumberControlFrame::SpinnerDownButtonIsDepressed() const
+{
+  return HTMLInputElement::FromContent(mContent)->
+           NumberSpinnerDownButtonIsDepressed();
+}
+
+bool
+nsNumberControlFrame::IsFocused() const
+{
+  // Normally this depends on the state of our anonymous text control (which
+  // takes focus for us), but in the case that it does not have a frame we will
+  // have focus ourself.
+  return mTextField->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS) ||
+         mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS);
+}
+
+void
 nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent)
 {
   if (aEvent->originalTarget != mTextField) {
     // Move focus to our text field
     HTMLInputElement::FromContent(mTextField)->Focus();
   }
 }
 
+#define STYLES_DISABLING_NATIVE_THEMING \
+  NS_AUTHOR_SPECIFIED_BACKGROUND | \
+  NS_AUTHOR_SPECIFIED_PADDING | \
+  NS_AUTHOR_SPECIFIED_BORDER
+
+bool
+nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const
+{
+  nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame();
+  nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame();
+
+  return spinUpFrame &&
+    spinUpFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_UP_BUTTON &&
+    !PresContext()->HasAuthorSpecifiedRules(spinUpFrame,
+                                            STYLES_DISABLING_NATIVE_THEMING) &&
+    spinDownFrame &&
+    spinDownFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_DOWN_BUTTON &&
+    !PresContext()->HasAuthorSpecifiedRules(spinDownFrame,
+                                            STYLES_DISABLING_NATIVE_THEMING);
+}
+
 void
 nsNumberControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
                                                uint32_t aFilter)
 {
   // Only one direct anonymous child:
   aElements.MaybeAppendElement(mOuterWrapper);
 }
 
--- a/layout/forms/nsNumberControlFrame.h
+++ b/layout/forms/nsNumberControlFrame.h
@@ -39,17 +39,17 @@ class nsNumberControlFrame MOZ_FINAL : p
 
 public:
   NS_DECL_QUERYFRAME_TARGET(nsNumberControlFrame)
   NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS
 
   virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE;
 
-  virtual bool IsLeaf() const MOZ_OVERRIDE { return true; }
+  virtual bool IsLeaf() const MOZ_OVERRIDE { return false; }
 
   NS_IMETHOD Reflow(nsPresContext*           aPresContext,
                     nsHTMLReflowMetrics&     aDesiredSize,
                     const nsHTMLReflowState& aReflowState,
                     nsReflowStatus&          aStatus) MOZ_OVERRIDE;
 
   NS_IMETHOD AttributeChanged(int32_t  aNameSpaceID,
                               nsIAtom* aAttribute,
@@ -86,33 +86,54 @@ public:
    */
   void HandlingInputEvent(bool aHandlingEvent)
   {
     mHandlingInputEvent = aHandlingEvent;
   }
 
   HTMLInputElement* GetAnonTextControl();
 
+  /**
+   * If the frame is the frame for an nsNumberControlFrame's anonymous text
+   * field, returns the nsNumberControlFrame. Else returns nullptr.
+   */
+  static nsNumberControlFrame* GetNumberControlFrameForTextField(nsIFrame* aFrame);
+
+  /**
+   * If the frame is the frame for an nsNumberControlFrame's up or down spin
+   * button, returns the nsNumberControlFrame. Else returns nullptr.
+   */
+  static nsNumberControlFrame* GetNumberControlFrameForSpinButton(nsIFrame* aFrame);
+
   enum SpinButtonEnum {
     eSpinButtonNone,
     eSpinButtonUp,
     eSpinButtonDown
   };
 
   /**
    * Returns one of the SpinButtonEnum values to depending on whether the
    * pointer event is over the spin-up button, the spin-down button, or
    * neither.
    */
   int32_t GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const;
 
+  void SpinnerStateChanged() const;
+
+  bool SpinnerUpButtonIsDepressed() const;
+  bool SpinnerDownButtonIsDepressed() const;
+
+  bool IsFocused() const;
+
   void HandleFocusEvent(WidgetEvent* aEvent);
 
   virtual Element* GetPseudoElement(nsCSSPseudoElements::Type aType) MOZ_OVERRIDE;
 
+  bool ShouldUseNativeStyleForSpinner() const;
+
 private:
 
   nsresult MakeAnonymousElement(Element** aResult,
                                 nsTArray<ContentInfo>& aElements,
                                 nsIAtom* aTagName,
                                 nsCSSPseudoElements::Type aPseudoType,
                                 nsStyleContext* aParentContext);
 
--- a/layout/style/forms.css
+++ b/layout/style/forms.css
@@ -892,19 +892,16 @@ input[type="number"] {
   width: 149px; /* to match type=text */
 }
 
 input[type=number]::-moz-number-wrapper {
   /* Prevent styling that would change the type of frame we construct. */
   display: flex;
   float: none !important;
   position: static !important;
-  -moz-box-sizing: border-box;
-  width: 100%;
-  height: 100%;
 }
 
 input[type=number]::-moz-number-text {
   -moz-appearance: none;
   /* work around autofocus bug 939248 on initial load */
   -moz-user-modify: read-write;
   /* This pseudo-element is also an 'input' element (nested inside and
    * distinct from the <input type=number> element) so we need to prevent the
@@ -917,44 +914,48 @@ input[type=number]::-moz-number-text {
   padding: 0;
   border: 0;
   margin: 0;
 }
 
 input[type=number]::-moz-number-spin-box {
   display: flex;
   flex-direction: column;
-  flex: 0 8px;
-  cursor: default;
-  padding: 1px;
+%ifdef XP_WIN
+  /* The Window's Theme's spin buttons have a very narrow minimum width, so
+   * make it something reasonable:
+   */
+  width: 16px;
+%endif
+  height: 0;
+  align-self: center;
+  justify-content: center;
 }
 
 input[type=number]::-moz-number-spin-up {
-  /* We should be "display:block" so that we don't get wrapped in an anonymous
-   * flex item that will prevent the setting of the "flex" property below from
-   * working.
-   */
-  display: block;
-  flex: 1;
+  -moz-appearance: spinner-upbutton;
+  display: block; /* bug 926670 */
+  flex: none;
+  cursor: default;
+  /* Style for when native theming is off: */
   background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="6" height="5"><path d="M1,4 L3,0 5,4" fill="dimgrey"/></svg>');
   background-repeat: no-repeat;
   background-position: center bottom;
   border: 1px solid darkgray;
   border-bottom: none;
   border-top-left-radius: 4px;
   border-top-right-radius: 4px;
 }
 
 input[type=number]::-moz-number-spin-down {
-  /* We should be "display:block" so that we don't get wrapped in an anonymous
-   * flex item that will prevent the setting of the "flex" property below from
-   * working.
-   */
-  display: block;
-  flex: 1;
+  -moz-appearance: spinner-downbutton;
+  display: block; /* bug 926670 */
+  flex: none;
+  cursor: default;
+  /* Style for when native theming is off: */
   background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="6" height="5"><path d="M1,1 L3,5 5,1" fill="dimgrey"/></svg>');
   background-repeat: no-repeat;
   background-position: center top;
   border: 1px solid darkgray;
   border-top: none;
   border-bottom-left-radius: 4px;
   border-bottom-right-radius: 4px;
 }
--- a/widget/cocoa/nsNativeThemeCocoa.h
+++ b/widget/cocoa/nsNativeThemeCocoa.h
@@ -99,16 +99,20 @@ protected:
                   nsEventStates inState, nsIFrame* aFrame);
   void DrawDropdown(CGContextRef context, const HIRect& inBoxRect,
                     nsEventStates inState, uint8_t aWidgetType,
                     nsIFrame* aFrame);
   void DrawSpinButtons(CGContextRef context, ThemeButtonKind inKind,
                        const HIRect& inBoxRect, ThemeDrawState inDrawState,
                        ThemeButtonAdornment inAdornment, nsEventStates inState,
                        nsIFrame* aFrame);
+  void DrawSpinButton(CGContextRef context, ThemeButtonKind inKind,
+                      const HIRect& inBoxRect, ThemeDrawState inDrawState,
+                      ThemeButtonAdornment inAdornment, nsEventStates inState,
+                      nsIFrame* aFrame, uint8_t aWidgetType);
   void DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
                           NSWindow* aWindow);
   void DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
                      nsIFrame *aFrame);
   void DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
                           CGFloat aUnifiedHeight, BOOL aIsMain);
   void DrawResizer(CGContextRef cgContext, const HIRect& aRect, nsIFrame *aFrame);
 
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #include "nsNativeThemeCocoa.h"
 #include "nsObjCExceptions.h"
+#include "nsNumberControlFrame.h"
 #include "nsRangeFrame.h"
 #include "nsRenderingContext.h"
 #include "nsRect.h"
 #include "nsSize.h"
 #include "nsThemeConstants.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsIContent.h"
@@ -1205,16 +1206,36 @@ nsNativeThemeCocoa::DrawDropdown(CGConte
 
   const CellRenderSettings& settings = isEditable ? editableMenulistSettings : dropdownSettings;
   DrawCellWithSnapping(cell, cgContext, inBoxRect, settings,
                        0.5f, mCellDrawView, IsFrameRTL(aFrame));
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
+static const CellRenderSettings spinnerSettings = {
+  {
+    NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
+    NSMakeSize(15, 22), // small
+    NSMakeSize(19, 27)  // regular
+  },
+  {
+    NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
+    NSMakeSize(15, 22), // small
+    NSMakeSize(19, 27)  // regular
+  },
+  {
+    { // Leopard
+      {0, 0, 0, 0},    // mini
+      {0, 0, 0, 0},    // small
+      {0, 0, 0, 0}     // regular
+    }
+  }
+};
+
 void
 nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind,
                                     const HIRect& inBoxRect, ThemeDrawState inDrawState,
                                     ThemeButtonAdornment inAdornment,
                                     nsEventStates inState, nsIFrame* aFrame)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
@@ -1230,16 +1251,66 @@ nsNativeThemeCocoa::DrawSpinButtons(CGCo
     bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
 
   HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 void
+nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
+                                   ThemeButtonKind inKind,
+                                   const HIRect& inBoxRect,
+                                   ThemeDrawState inDrawState,
+                                   ThemeButtonAdornment inAdornment,
+                                   nsEventStates inState,
+                                   nsIFrame* aFrame,
+                                   uint8_t aWidgetType)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+  MOZ_ASSERT(aWidgetType == NS_THEME_SPINNER_UP_BUTTON ||
+             aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON);
+
+  HIThemeButtonDrawInfo bdi;
+  bdi.version = 0;
+  bdi.kind = inKind;
+  bdi.value = kThemeButtonOff;
+  bdi.adornment = inAdornment;
+
+  if (IsDisabled(aFrame, inState))
+    bdi.state = kThemeStateUnavailable;
+  else
+    bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
+
+  // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
+  // together as a single unit (presumably because when one button is active,
+  // the appearance of both changes (in different ways)). Here we have to paint
+  // both buttons, using clip to hide the one we don't want to paint.
+  HIRect drawRect = inBoxRect;
+  drawRect.size.height *= 2;
+  if (aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON) {
+    drawRect.origin.y -= inBoxRect.size.height;
+  }
+
+  // Shift the drawing a little to the left, since cocoa paints with more
+  // blank space around the visual buttons than we'd like:
+  drawRect.origin.x -= 1;
+
+  CGContextSaveGState(cgContext);
+  CGContextClipToRect(cgContext, inBoxRect);
+
+  HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
+
+  CGContextRestoreGState(cgContext);
+
+  NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
 nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind,
                               const HIRect& inBoxRect, bool inDisabled,
                               nsEventStates inState)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   HIThemeFrameDrawInfo fdi;
   fdi.version = 0;
@@ -2130,32 +2201,55 @@ nsNativeThemeCocoa::DrawWidgetBackground
 
     case NS_THEME_BUTTON_BEVEL:
       DrawButton(cgContext, kThemeMediumBevelButton, macRect,
                  IsDefaultButton(aFrame), kThemeButtonOff, kThemeAdornmentNone,
                  eventState, aFrame);
       break;
 
     case NS_THEME_SPINNER: {
+      nsIContent* content = aFrame->GetContent();
+      if (content->IsHTML()) {
+        // In HTML the theming for the spin buttons is drawn individually into
+        // their own backgrounds instead of being drawn into the background of
+        // their spinner parent as it is for XUL.
+        break;
+      }
       ThemeDrawState state = kThemeStateActive;
-      nsIContent* content = aFrame->GetContent();
       if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
                                NS_LITERAL_STRING("up"), eCaseMatters)) {
         state = kThemeStatePressedUp;
       }
       else if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
                                     NS_LITERAL_STRING("down"), eCaseMatters)) {
         state = kThemeStatePressedDown;
       }
 
       DrawSpinButtons(cgContext, kThemeIncDecButton, macRect, state,
                       kThemeAdornmentNone, eventState, aFrame);
     }
       break;
 
+    case NS_THEME_SPINNER_UP_BUTTON:
+    case NS_THEME_SPINNER_DOWN_BUTTON: {
+      nsNumberControlFrame* numberControlFrame =
+        nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
+      if (numberControlFrame) {
+        ThemeDrawState state = kThemeStateActive;
+        if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
+          state = kThemeStatePressedUp;
+        } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
+          state = kThemeStatePressedDown;
+        }
+        DrawSpinButton(cgContext, kThemeIncDecButtonMini, macRect, state,
+                       kThemeAdornmentNone, eventState, aFrame, aWidgetType);
+      }
+    }
+      break;
+
     case NS_THEME_TOOLBAR_BUTTON:
       DrawSegment(cgContext, macRect, eventState, aFrame, toolbarButtonRenderSettings);
       break;
 
     case NS_THEME_TOOLBAR_SEPARATOR: {
       HIThemeSeparatorDrawInfo sdi = { 0, kThemeStateActive };
       HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
     }
@@ -2742,22 +2836,35 @@ nsNativeThemeCocoa::GetMinimumWidgetSize
 
     case NS_THEME_TOOLBAR_BUTTON:
     {
       aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
       break;
     }
 
     case NS_THEME_SPINNER:
+    case NS_THEME_SPINNER_UP_BUTTON:
+    case NS_THEME_SPINNER_DOWN_BUTTON:
     {
       SInt32 buttonHeight = 0, buttonWidth = 0;
-      ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
-      ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
+      if (aFrame->GetContent()->IsXUL()) {
+        ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
+        ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
+      } else {
+        NSSize size =
+          spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSMiniControlSize)];
+        buttonWidth = size.width;
+        buttonHeight = size.height;
+        if (aWidgetType != NS_THEME_SPINNER) {
+          // the buttons are half the height of the spinner
+          buttonHeight /= 2;
+        }
+      }
       aResult->SizeTo(buttonWidth, buttonHeight);
-      *aIsOverridable = false;
+      *aIsOverridable = true;
       break;
     }
 
     case NS_THEME_DROPDOWN:
     case NS_THEME_DROPDOWN_BUTTON:
     {
       SInt32 popupHeight = 0;
       ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
@@ -3095,16 +3202,18 @@ nsNativeThemeCocoa::ThemeSupportsWidget(
     case NS_THEME_CHECKBOX_CONTAINER:
     case NS_THEME_RADIO:
     case NS_THEME_RADIO_CONTAINER:
     case NS_THEME_GROUPBOX:
     case NS_THEME_BUTTON:
     case NS_THEME_BUTTON_BEVEL:
     case NS_THEME_TOOLBAR_BUTTON:
     case NS_THEME_SPINNER:
+    case NS_THEME_SPINNER_UP_BUTTON:
+    case NS_THEME_SPINNER_DOWN_BUTTON:
     case NS_THEME_TOOLBAR:
     case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
     case NS_THEME_STATUSBAR:
     case NS_THEME_TEXTFIELD:
     case NS_THEME_TEXTFIELD_MULTILINE:
     case NS_THEME_SEARCHFIELD:
     //case NS_THEME_TOOLBOX:
     //case NS_THEME_TOOLBAR_BUTTON:
@@ -3219,16 +3328,18 @@ nsNativeThemeCocoa::WidgetAppearanceDepe
     case NS_THEME_DIALOG:
     case NS_THEME_GROUPBOX:
     case NS_THEME_TAB_PANELS:
     case NS_THEME_MENUPOPUP:
     case NS_THEME_MENUITEM:
     case NS_THEME_MENUSEPARATOR:
     case NS_THEME_TOOLTIP:
     case NS_THEME_SPINNER:
+    case NS_THEME_SPINNER_UP_BUTTON:
+    case NS_THEME_SPINNER_DOWN_BUTTON:
     case NS_THEME_TOOLBAR_SEPARATOR:
     case NS_THEME_TOOLBOX:
     case NS_THEME_TEXTFIELD:
     case NS_THEME_TREEVIEW:
     case NS_THEME_TREEVIEW_LINE:
     case NS_THEME_TEXTFIELD_MULTILINE:
     case NS_THEME_LISTBOX:
     case NS_THEME_RESIZER:
--- a/widget/xpwidgets/nsNativeTheme.cpp
+++ b/widget/xpwidgets/nsNativeTheme.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsNativeTheme.h"
 #include "nsIWidget.h"
 #include "nsIDocument.h"
 #include "nsIContent.h"
 #include "nsIFrame.h"
 #include "nsIPresShell.h"
+#include "nsNumberControlFrame.h"
 #include "nsPresContext.h"
 #include "nsEventStateManager.h"
 #include "nsString.h"
 #include "nsINameSpaceManager.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsIDOMXULMenuListElement.h"
 #include "nsThemeConstants.h"
 #include "nsIComponentManager.h"
@@ -69,16 +70,26 @@ nsNativeTheme::GetContentState(nsIFrame*
   nsIPresShell *shell = GetPresShell(aFrame);
   if (!shell)
     return nsEventStates();
 
   nsIContent* frameContent = aFrame->GetContent();
   nsEventStates flags;
   if (frameContent->IsElement()) {
     flags = frameContent->AsElement()->State();
+
+    // <input type=number> needs special handling since its nested native
+    // anonymous <input type=text> takes focus for it.
+    if (aWidgetType == NS_THEME_TEXTFIELD &&
+        frameContent->IsHTML(nsGkAtoms::input)) {
+      nsNumberControlFrame *numberControlFrame = do_QueryFrame(aFrame);
+      if (numberControlFrame && numberControlFrame->IsFocused()) {
+        flags |= NS_EVENT_STATE_FOCUS;
+      }
+    }
   }
   
   if (isXULCheckboxRadio && aWidgetType == NS_THEME_RADIO) {
     if (IsFocused(aFrame))
       flags |= NS_EVENT_STATE_FOCUS;
   }
 
   // On Windows and Mac, only draw focus rings if they should be shown. This
@@ -308,16 +319,25 @@ nsNativeTheme::IsWidgetStyled(nsPresCont
     nsRangeFrame* rangeFrame =
       do_QueryFrame(aWidgetType == NS_THEME_RANGE_THUMB
                       ? aFrame->GetParent() : aFrame);
     if (rangeFrame) {
       return !rangeFrame->ShouldUseNativeStyle();
     }
   }
 
+  if (aWidgetType == NS_THEME_SPINNER_UP_BUTTON ||
+      aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON) {
+    nsNumberControlFrame* numberControlFrame =
+      nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
+    if (numberControlFrame) {
+      return !numberControlFrame->ShouldUseNativeStyleForSpinner();
+    }
+  }
+
   return (aWidgetType == NS_THEME_BUTTON ||
           aWidgetType == NS_THEME_TEXTFIELD ||
           aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
           aWidgetType == NS_THEME_LISTBOX ||
           aWidgetType == NS_THEME_DROPDOWN) &&
          aFrame->GetContent()->IsHTML() &&
          aPresContext->HasAuthorSpecifiedRules(aFrame,
                                                NS_AUTHOR_SPECIFIED_BORDER |