Bug 846883 - Add support for native theming of <input type=range> on OS X. r=roc
authorJonathan Watt <jwatt@jwatt.org>
Sat, 16 Mar 2013 05:40:15 +0000
changeset 135403 d4c1d68f2d9f9bfbbc7e4f4b22e2b2680b324da9
parent 135402 6922ed98876a158a62ab2ec32569ddeaee9b5977
child 135404 3a1ab4d115134c4789a54856079af73bb03e16c5
push id2452
push userlsblakk@mozilla.com
push dateMon, 13 May 2013 16:59:38 +0000
treeherdermozilla-beta@d4b152d29d8d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs846883
milestone22.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 846883 - Add support for native theming of <input type=range> on OS X. r=roc
gfx/src/nsThemeConstants.h
layout/forms/nsRangeFrame.cpp
layout/forms/nsRangeFrame.h
layout/style/nsCSSKeywordList.h
layout/style/nsCSSProps.cpp
widget/cocoa/Makefile.in
widget/cocoa/nsNativeThemeCocoa.mm
widget/xpwidgets/nsNativeTheme.cpp
widget/xpwidgets/nsNativeTheme.h
--- a/gfx/src/nsThemeConstants.h
+++ b/gfx/src/nsThemeConstants.h
@@ -192,16 +192,20 @@
 // If the platform supports it, the left/right chunks
 // of the slider thumb
 #define NS_THEME_SCALE_THUMB_START                        115
 #define NS_THEME_SCALE_THUMB_END                          116
 
 // The ticks for a slider.
 #define NS_THEME_SCALE_TICK                               117
 
+// nsRangeFrame and its subparts
+#define NS_THEME_RANGE                                    120
+#define NS_THEME_RANGE_THUMB                              121
+
 // A groupbox
 #define NS_THEME_GROUPBOX                                  149
 
 // A generic container that always repaints on state
 // changes.  This is a hack to make checkboxes and
 // radio buttons work.
 #define NS_THEME_CHECKBOX_CONTAINER                        150
 #define NS_THEME_RADIO_CONTAINER                           151
--- a/layout/forms/nsRangeFrame.cpp
+++ b/layout/forms/nsRangeFrame.cpp
@@ -15,16 +15,17 @@
 #include "nsIDOMHTMLInputElement.h"
 #include "nsINameSpaceManager.h"
 #include "nsINodeInfo.h"
 #include "nsIPresShell.h"
 #include "nsGkAtoms.h"
 #include "nsHTMLInputElement.h"
 #include "nsPresContext.h"
 #include "nsNodeInfoManager.h"
+#include "nsRenderingContext.h"
 #include "mozilla/dom/Element.h"
 #include "prtypes.h"
 
 #include <algorithm>
 
 #define LONG_SIDE_TO_SHORT_SIDE_RATIO 10
 
 nsIFrame*
@@ -324,19 +325,35 @@ nsRangeFrame::GetValueAtEventPoint(nsGUI
   } else {
     absPoint = aEvent->refPoint;
   }
   nsPoint point =
     nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, absPoint, this);
 
   nsRect rangeContentRect = GetContentRectRelativeToSelf();
   nsSize thumbSize;
-  nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
-  if (thumbFrame) { // diplay:none?
-    thumbSize = thumbFrame->GetSize();
+
+  if (IsThemed()) {
+    // We need to get the size of the thumb from the theme.
+    nsPresContext *presContext = PresContext();
+    nsRefPtr<nsRenderingContext> tmpCtx =
+      presContext->PresShell()->GetReferenceRenderingContext();
+    bool notUsedCanOverride;
+    nsIntSize size;
+    presContext->GetTheme()->
+      GetMinimumWidgetSize(tmpCtx.get(), this, NS_THEME_RANGE_THUMB, &size,
+                           &notUsedCanOverride);
+    thumbSize.width = presContext->DevPixelsToAppUnits(size.width);
+    thumbSize.height = presContext->DevPixelsToAppUnits(size.height);
+    MOZ_ASSERT(thumbSize.width > 0 && thumbSize.height > 0);
+  } else {
+    nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
+    if (thumbFrame) { // diplay:none?
+      thumbSize = thumbFrame->GetSize();
+    }
   }
 
   double fraction;
   if (IsHorizontal()) {
     nscoord traversableDistance = rangeContentRect.width - thumbSize.width;
     if (traversableDistance <= 0) {
       return minimum;
     }
@@ -370,16 +387,21 @@ nsRangeFrame::UpdateThumbPositionForValu
   if (NS_SUBTREE_DIRTY(this)) {
     return; // we're going to be updated when we reflow
   }
   nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
   if (!thumbFrame) {
     return; // diplay:none?
   }
   DoUpdateThumbPosition(thumbFrame, GetSize());
+  if (IsThemed()) {
+    // We don't know the exact dimensions or location of the thumb when native
+    // theming is applied, so we just repaint the entire range.
+    InvalidateFrame();
+  }
   SchedulePaint();
 }
 
 void
 nsRangeFrame::DoUpdateThumbPosition(nsIFrame* aThumbFrame,
                                     const nsSize& aRangeSize)
 {
   MOZ_ASSERT(aThumbFrame);
@@ -530,19 +552,48 @@ nsRangeFrame::GetPrefWidth(nsRenderingCo
 }
 
 bool
 nsRangeFrame::IsHorizontal(const nsSize *aFrameSizeOverride) const
 {
   return true; // until we decide how to support vertical range (bug 840820)
 }
 
+double
+nsRangeFrame::GetMin() const
+{
+  return static_cast<nsHTMLInputElement*>(mContent)->GetMinimum();
+}
+
+double
+nsRangeFrame::GetMax() const
+{
+  return static_cast<nsHTMLInputElement*>(mContent)->GetMaximum();
+}
+
+double
+nsRangeFrame::GetValue() const
+{
+  return static_cast<nsHTMLInputElement*>(mContent)->GetValueAsDouble();
+}
+
 nsIAtom*
 nsRangeFrame::GetType() const
 {
   return nsGkAtoms::rangeFrame;
 }
 
+#define STYLES_DISABLING_NATIVE_THEMING \
+  NS_AUTHOR_SPECIFIED_BACKGROUND | \
+  NS_AUTHOR_SPECIFIED_PADDING | \
+  NS_AUTHOR_SPECIFIED_BORDER
+
 bool
 nsRangeFrame::ShouldUseNativeStyle() const
 {
-  return false; // TODO
+  return (StyleDisplay()->mAppearance == NS_THEME_RANGE) &&
+         !PresContext()->HasAuthorSpecifiedRules(const_cast<nsRangeFrame*>(this),
+                                                 STYLES_DISABLING_NATIVE_THEMING) &&
+         !PresContext()->HasAuthorSpecifiedRules(mTrackDiv->GetPrimaryFrame(),
+                                                 STYLES_DISABLING_NATIVE_THEMING) &&
+         !PresContext()->HasAuthorSpecifiedRules(mThumbDiv->GetPrimaryFrame(),
+                                                 STYLES_DISABLING_NATIVE_THEMING);
 }
--- a/layout/forms/nsRangeFrame.h
+++ b/layout/forms/nsRangeFrame.h
@@ -80,16 +80,20 @@ public:
    * aOverrideFrameSize If specified, this will be used instead of the size of
    *   the frame's rect (i.e. the frame's border-box size) if the frame's
    *   rect would have otherwise been examined. This should only be specified
    *   during reflow when the frame's [new] border-box size has not yet been
    *   stored in its mRect.
    */
   bool IsHorizontal(const nsSize *aFrameSizeOverride = nullptr) const;
 
+  double GetMin() const;
+  double GetMax() const;
+  double GetValue() const;
+
   /**
    * Returns whether the frame and its child should use the native style.
    */
   bool ShouldUseNativeStyle() const;
 
   double GetValueAtEventPoint(nsGUIEvent* aEvent);
 
   /**
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -556,16 +556,17 @@ CSS_KEY(menuarrow, menuarrow)
 CSS_KEY(menuimage, menuimage)
 CSS_KEY(menuitemtext, menuitemtext)
 CSS_KEY(menulist, menulist)
 CSS_KEY(menulist-button, menulist_button)
 CSS_KEY(menulist-text, menulist_text)
 CSS_KEY(menulist-textfield, menulist_textfield)
 CSS_KEY(meterbar, meterbar)
 CSS_KEY(meterchunk, meterchunk)
+CSS_KEY(range, range)
 CSS_KEY(scale-horizontal, scale_horizontal)
 CSS_KEY(scale-vertical, scale_vertical)
 CSS_KEY(scalethumb-horizontal, scalethumb_horizontal)
 CSS_KEY(scalethumb-vertical, scalethumb_vertical)
 CSS_KEY(scalethumbstart, scalethumbstart)
 CSS_KEY(scalethumbend, scalethumbend)
 CSS_KEY(scalethumbtick, scalethumbtick)
 CSS_KEY(groupbox, groupbox)
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -557,16 +557,17 @@ const int32_t nsCSSProps::kAppearanceKTa
   eCSSKeyword_textfield,              NS_THEME_TEXTFIELD,
   eCSSKeyword_textfield_multiline,    NS_THEME_TEXTFIELD_MULTILINE,
   eCSSKeyword_caret,                  NS_THEME_TEXTFIELD_CARET,
   eCSSKeyword_searchfield,            NS_THEME_SEARCHFIELD,
   eCSSKeyword_menulist,               NS_THEME_DROPDOWN,
   eCSSKeyword_menulist_button,        NS_THEME_DROPDOWN_BUTTON,
   eCSSKeyword_menulist_text,          NS_THEME_DROPDOWN_TEXT,
   eCSSKeyword_menulist_textfield,     NS_THEME_DROPDOWN_TEXTFIELD,
+  eCSSKeyword_range,                  NS_THEME_RANGE,
   eCSSKeyword_scale_horizontal,       NS_THEME_SCALE_HORIZONTAL,
   eCSSKeyword_scale_vertical,         NS_THEME_SCALE_VERTICAL,
   eCSSKeyword_scalethumb_horizontal,  NS_THEME_SCALE_THUMB_HORIZONTAL,
   eCSSKeyword_scalethumb_vertical,    NS_THEME_SCALE_THUMB_VERTICAL,
   eCSSKeyword_scalethumbstart,        NS_THEME_SCALE_THUMB_START,
   eCSSKeyword_scalethumbend,          NS_THEME_SCALE_THUMB_END,
   eCSSKeyword_scalethumbtick,         NS_THEME_SCALE_TICK,
   eCSSKeyword_groupbox,               NS_THEME_GROUPBOX,
--- a/widget/cocoa/Makefile.in
+++ b/widget/cocoa/Makefile.in
@@ -98,15 +98,18 @@ endif
 
 export::
 	$(INSTALL) $(srcdir)/cursors $(DIST)/bin/res
 
 LOCAL_INCLUDES	= \
 	$(TK_CFLAGS) \
 	-I$(srcdir)/../xpwidgets \
 	-I$(srcdir)/../shared \
+	-I$(topsrcdir)/layout/forms \
+	-I$(topsrcdir)/layout/generic \
+	-I$(topsrcdir)/layout/xul/base/src \
 	$(NULL)
 
 LDFLAGS	+= \
 	-framework QuickTime \
 	-framework IOKit \
 	-F/System/Library/PrivateFrameworks -framework CoreUI \
 	$(NULL)
--- 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 "nsRangeFrame.h"
 #include "nsRenderingContext.h"
 #include "nsRect.h"
 #include "nsSize.h"
 #include "nsThemeConstants.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
@@ -2258,16 +2259,44 @@ nsNativeThemeCocoa::DrawWidgetBackground
     }
       break;
 
     case NS_THEME_SCALE_THUMB_HORIZONTAL:
     case NS_THEME_SCALE_THUMB_VERTICAL:
       // do nothing, drawn by scale
       break;
 
+    case NS_THEME_RANGE: {
+      nsRangeFrame *rangeFrame = do_QueryFrame(aFrame);
+      if (!rangeFrame) {
+        break;
+      }
+      int32_t value = rangeFrame->GetValue();
+      int32_t min = rangeFrame->GetMin();
+      int32_t max = rangeFrame->GetMax();
+      if (max < min) {
+        // The spec says that the only valid value is the minimum. For the
+        // purposes of drawing the range, we need to have a max that's greater
+        // than min though (it doesn't really matter what the value is, as long
+        // as the thumb is painted at the min.
+        max = min + 1;
+        value = min;
+      }
+      MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(value) &&
+                 MOZ_DOUBLE_IS_FINITE(min) &&
+                 MOZ_DOUBLE_IS_FINITE(max) &&
+                 value >= min && value <= max);
+      bool reverseDir =
+        rangeFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+      bool isVertical = !IsRangeHorizontal(aFrame);
+      DrawScale(cgContext, macRect, eventState, isVertical, reverseDir,
+                value, min, max, aFrame);
+      break;
+    }
+
     case NS_THEME_SCROLLBAR_SMALL:
     case NS_THEME_SCROLLBAR: {
       DrawScrollbar(cgContext, macRect, aFrame);
     }
       break;
     case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
     case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
 #if SCROLLBARS_VISUAL_DEBUG
@@ -2658,16 +2687,45 @@ nsNativeThemeCocoa::GetMinimumWidgetSize
     }
 
     case NS_THEME_TAB:
     {
       aResult->SizeTo(0, tabHeights[miniControlSize]);
       break;
     }
 
+    case NS_THEME_RANGE:
+    {
+      // The Mac Appearance Manager API (the old API we're currently using)
+      // doesn't define constants to obtain a minimum size for sliders. We use
+      // the "thickness" of a slider that has default dimensions for both the
+      // minimum width and height to get something sane and so that paint
+      // invalidation works.
+      SInt32 size = 0;
+      if (IsRangeHorizontal(aFrame)) {
+        ::GetThemeMetric(kThemeMetricHSliderHeight, &size);
+      } else {
+        ::GetThemeMetric(kThemeMetricVSliderWidth, &size);
+      }
+      aResult->SizeTo(size, size);
+      *aIsOverridable = true;
+      break;
+    }
+
+    case NS_THEME_RANGE_THUMB:
+    {
+      SInt32 width = 0;
+      SInt32 height = 0;
+      ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
+      ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
+      aResult->SizeTo(width, height);
+      *aIsOverridable = false;
+      break;
+    }
+
     case NS_THEME_SCALE_HORIZONTAL:
     {
       SInt32 scaleHeight = 0;
       ::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight);
       aResult->SizeTo(scaleHeight, scaleHeight);
       *aIsOverridable = false;
       break;
     }
@@ -2898,16 +2956,18 @@ nsNativeThemeCocoa::ThemeSupportsWidget(
     case NS_THEME_TREEVIEW_TWISTY_OPEN:
     case NS_THEME_TREEVIEW:
     case NS_THEME_TREEVIEW_HEADER:
     case NS_THEME_TREEVIEW_HEADER_CELL:
     case NS_THEME_TREEVIEW_HEADER_SORTARROW:
     case NS_THEME_TREEVIEW_TREEITEM:
     case NS_THEME_TREEVIEW_LINE:
 
+    case NS_THEME_RANGE:
+
     case NS_THEME_SCALE_HORIZONTAL:
     case NS_THEME_SCALE_THUMB_HORIZONTAL:
     case NS_THEME_SCALE_VERTICAL:
     case NS_THEME_SCALE_THUMB_VERTICAL:
 
     case NS_THEME_SCROLLBAR:
     case NS_THEME_SCROLLBAR_SMALL:
     case NS_THEME_SCROLLBAR_BUTTON_UP:
@@ -2951,16 +3011,17 @@ nsNativeThemeCocoa::WidgetIsContainer(ui
 {
   // flesh this out at some point
   switch (aWidgetType) {
    case NS_THEME_DROPDOWN_BUTTON:
    case NS_THEME_RADIO:
    case NS_THEME_CHECKBOX:
    case NS_THEME_PROGRESSBAR:
    case NS_THEME_METERBAR:
+   case NS_THEME_RANGE:
     return false;
     break;
   }
   return true;
 }
 
 bool
 nsNativeThemeCocoa::ThemeDrawsFocusForWidget(nsPresContext* aPresContext, nsIFrame* aFrame, uint8_t aWidgetType)
--- a/widget/xpwidgets/nsNativeTheme.cpp
+++ b/widget/xpwidgets/nsNativeTheme.cpp
@@ -17,16 +17,17 @@
 #include "nsIDOMHTMLProgressElement.h"
 #include "nsIDOMXULMenuListElement.h"
 #include "nsThemeConstants.h"
 #include "nsIComponentManager.h"
 #include "nsPIDOMWindow.h"
 #include "nsProgressFrame.h"
 #include "nsMeterFrame.h"
 #include "nsMenuFrame.h"
+#include "nsRangeFrame.h"
 #include "mozilla/dom/Element.h"
 #include <algorithm>
 
 nsNativeTheme::nsNativeTheme()
 : mAnimatedContentTimeout(UINT32_MAX)
 {
 }
 
@@ -299,16 +300,26 @@ nsNativeTheme::IsWidgetStyled(nsPresCont
       aWidgetType == NS_THEME_METERBAR) {
     nsMeterFrame* meterFrame = do_QueryFrame(aWidgetType == NS_THEME_METERBAR_CHUNK
                                        ? aFrame->GetParent() : aFrame);
     if (meterFrame) {
       return !meterFrame->ShouldUseNativeStyle();
     }
   }
 
+  /**
+   * nsRangeFrame owns the logic and will tell us what we should do.
+   */
+  if (aWidgetType == NS_THEME_RANGE) {
+    nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
+    if (rangeFrame) {
+      return !rangeFrame->ShouldUseNativeStyle();
+    }
+  }
+
   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 |
@@ -644,8 +655,16 @@ nsNativeTheme::GetAdjacentSiblingFrameWi
   // Check same appearance and adjacency.
   if (!sibling ||
       sibling->StyleDisplay()->mAppearance != aFrame->StyleDisplay()->mAppearance ||
       (sibling->GetRect().XMost() != aFrame->GetRect().x &&
        aFrame->GetRect().XMost() != sibling->GetRect().x))
     return nullptr;
   return sibling;
 }
+
+bool
+nsNativeTheme::IsRangeHorizontal(nsIFrame* aFrame)
+{
+  MOZ_ASSERT(aFrame->GetType() == nsGkAtoms::rangeFrame);
+
+  return static_cast<nsRangeFrame*>(aFrame)->IsHorizontal();
+}
--- a/widget/xpwidgets/nsNativeTheme.h
+++ b/widget/xpwidgets/nsNativeTheme.h
@@ -166,13 +166,15 @@ class nsNativeTheme : public nsITimerCal
   bool GetIndeterminate(nsIFrame* aFrame);
 
   bool QueueAnimatedContentForRefresh(nsIContent* aContent,
                                         uint32_t aMinimumFrameRate);
 
   nsIFrame* GetAdjacentSiblingFrameWithSameAppearance(nsIFrame* aFrame,
                                                       bool aNextSibling);
 
+  bool IsRangeHorizontal(nsIFrame* aFrame);
+
  private:
   uint32_t mAnimatedContentTimeout;
   nsCOMPtr<nsITimer> mAnimatedContentTimer;
   nsAutoTArray<nsCOMPtr<nsIContent>, 20> mAnimatedContentList;
 };