Bug 1157142 - Support logical (inline/block) in addition to physical orientation for the <input type=range> element; make inline the default behavior so that range sliders respect writing mode. r=jwatt
authorJonathan Kew <jkew@mozilla.com>
Wed, 29 Apr 2015 08:18:54 +0100
changeset 271395 1e7aa3504b2929598843de83a1b9996a16537f51
parent 271394 ebd9da2efb59a5755f3de9735479cd12422d704a
child 271396 508b6bf160489f40b55198e774e5b7f77005bfe4
push id4830
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:18:48 +0000
treeherdermozilla-beta@4c2175bb0420 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwatt
bugs1157142
milestone40.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 1157142 - Support logical (inline/block) in addition to physical orientation for the <input type=range> element; make inline the default behavior so that range sliders respect writing mode. r=jwatt
layout/forms/nsRangeFrame.cpp
layout/forms/nsRangeFrame.h
layout/style/forms.css
widget/cocoa/nsNativeThemeCocoa.mm
--- a/layout/forms/nsRangeFrame.cpp
+++ b/layout/forms/nsRangeFrame.cpp
@@ -536,17 +536,17 @@ nsRangeFrame::GetValueAtEventPoint(Widge
     nscoord traversableDistance = rangeContentRect.width - thumbSize.width;
     if (traversableDistance <= 0) {
       return minimum;
     }
     nscoord posAtStart = rangeContentRect.x + thumbSize.width/2;
     nscoord posAtEnd = posAtStart + traversableDistance;
     nscoord posOfPoint = mozilla::clamped(point.x, posAtStart, posAtEnd);
     fraction = Decimal(posOfPoint - posAtStart) / Decimal(traversableDistance);
-    if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
+    if (IsRightToLeft()) {
       fraction = Decimal(1) - fraction;
     }
   } else {
     nscoord traversableDistance = rangeContentRect.height - thumbSize.height;
     if (traversableDistance <= 0) {
       return minimum;
     }
     nscoord posAtStart = rangeContentRect.y + thumbSize.height/2;
@@ -614,23 +614,21 @@ nsRangeFrame::DoUpdateThumbPosition(nsIF
   nsSize rangeContentBoxSize(aRangeSize);
   rangeContentBoxSize.width -= borderAndPadding.LeftRight();
   rangeContentBoxSize.height -= borderAndPadding.TopBottom();
 
   nsSize thumbSize = aThumbFrame->GetSize();
   double fraction = GetValueAsFractionOfRange();
   MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
 
-  // We are called under Reflow, so we need to pass IsHorizontal a valid rect.
-  nsSize frameSizeOverride(aRangeSize.width, aRangeSize.height);
-  if (IsHorizontal(&frameSizeOverride)) {
+  if (IsHorizontal()) {
     if (thumbSize.width < rangeContentBoxSize.width) {
       nscoord traversableDistance =
         rangeContentBoxSize.width - thumbSize.width;
-      if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
+      if (IsRightToLeft()) {
         newPosition.x += NSToCoordRound((1.0 - fraction) * traversableDistance);
       } else {
         newPosition.x += NSToCoordRound(fraction * traversableDistance);
       }
       newPosition.y += (rangeContentBoxSize.height - thumbSize.height)/2;
     }
   } else {
     if (thumbSize.height < rangeContentBoxSize.height) {
@@ -665,21 +663,19 @@ nsRangeFrame::DoUpdateRangeProgressFrame
 
   nsSize rangeContentBoxSize(aRangeSize);
   rangeContentBoxSize.width -= borderAndPadding.LeftRight();
   rangeContentBoxSize.height -= borderAndPadding.TopBottom();
 
   double fraction = GetValueAsFractionOfRange();
   MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
 
-  // We are called under Reflow, so we need to pass IsHorizontal a valid rect.
-  nsSize frameSizeOverride(aRangeSize.width, aRangeSize.height);
-  if (IsHorizontal(&frameSizeOverride)) {
+  if (IsHorizontal()) {
     nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.width);
-    if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
+    if (IsRightToLeft()) {
       progRect.x += rangeContentBoxSize.width - progLength;
     }
     progRect.y += (rangeContentBoxSize.height - progSize.height)/2;
     progRect.width = progLength;
   } else {
     nscoord progLength = NSToCoordRound(fraction * rangeContentBoxSize.height);
     progRect.x += (rangeContentBoxSize.width - progSize.width)/2;
     progRect.y += rangeContentBoxSize.height - progLength;
@@ -736,20 +732,17 @@ nsRangeFrame::ComputeAutoSize(nsRenderin
                               const LogicalSize& aMargin,
                               const LogicalSize& aBorder,
                               const LogicalSize& aPadding,
                               bool aShrinkWrap)
 {
   nscoord oneEm = NSToCoordRound(StyleFont()->mFont.size *
                                  nsLayoutUtils::FontSizeInflationFor(this)); // 1em
 
-  // frameSizeOverride values just gets us to fall back to being horizontal
-  // (the actual values are irrelevant, as long as width > height):
-  nsSize frameSizeOverride(10,1);
-  bool isInlineOriented = IsHorizontal(&frameSizeOverride);
+  bool isInlineOriented = IsInlineOriented();
 
   const WritingMode wm = GetWritingMode();
   LogicalSize autoSize(wm);
 
   // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
   // given too small a size when we're natively themed. If we're themed, we set
   // our "thickness" dimension to zero below and rely on that
   // GetMinimumWidgetSize check to correct that dimension to the natural
@@ -773,44 +766,48 @@ nsRangeFrame::GetMinISize(nsRenderingCon
   // given too small a size when we're natively themed. If we aren't native
   // themed, we don't mind how small we're sized.
   return nscoord(0);
 }
 
 nscoord
 nsRangeFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
 {
-  // frameSizeOverride values just gets us to fall back to being horizontal:
-  nsSize frameSizeOverride(10,1);
-  bool isHorizontal = IsHorizontal(&frameSizeOverride);
+  bool isInline = IsInlineOriented();
 
-  if (!isHorizontal && IsThemed()) {
+  if (!isInline && IsThemed()) {
     // nsFrame::ComputeSize calls GetMinimumWidgetSize to prevent us from being
     // given too small a size when we're natively themed. We return zero and
-    // depend on that correction to get our "natuaral" width when we're a
+    // depend on that correction to get our "natural" width when we're a
     // vertical slider.
     return 0;
   }
 
-  nscoord prefWidth = NSToCoordRound(StyleFont()->mFont.size *
+  nscoord prefISize = NSToCoordRound(StyleFont()->mFont.size *
                                      nsLayoutUtils::FontSizeInflationFor(this)); // 1em
 
-  if (isHorizontal) {
-    prefWidth *= LONG_SIDE_TO_SHORT_SIDE_RATIO;
+  if (isInline) {
+    prefISize *= LONG_SIDE_TO_SHORT_SIDE_RATIO;
   }
 
-  return prefWidth;
+  return prefISize;
 }
 
 bool
-nsRangeFrame::IsHorizontal(const nsSize *aFrameSizeOverride) const
+nsRangeFrame::IsHorizontal() const
 {
-  dom::HTMLInputElement* element = static_cast<dom::HTMLInputElement*>(mContent);
-  return !element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
-                               nsGkAtoms::vertical, eCaseMatters);
+  dom::HTMLInputElement* element =
+    static_cast<dom::HTMLInputElement*>(mContent);
+  return element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
+                              nsGkAtoms::horizontal, eCaseMatters) ||
+         (!element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
+                               nsGkAtoms::vertical, eCaseMatters) &&
+          GetWritingMode().IsVertical() ==
+            element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
+                                 nsGkAtoms::block, eCaseMatters));
 }
 
 double
 nsRangeFrame::GetMin() const
 {
   return static_cast<dom::HTMLInputElement*>(mContent)->GetMinimum().toDouble();
 }
 
--- a/layout/forms/nsRangeFrame.h
+++ b/layout/forms/nsRangeFrame.h
@@ -94,24 +94,35 @@ public:
 
   nsStyleContext* GetAdditionalStyleContext(int32_t aIndex) const override;
   void SetAdditionalStyleContext(int32_t aIndex,
                                  nsStyleContext* aStyleContext) override;
 
   /**
    * Returns true if the slider's thumb moves horizontally, or else false if it
    * moves vertically.
-   *
-   * 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;
+
+  /**
+   * Returns true if the slider is oriented along the inline axis.
    */
-  bool IsHorizontal(const nsSize *aFrameSizeOverride = nullptr) const;
+  bool IsInlineOriented() const {
+    return IsHorizontal() != GetWritingMode().IsVertical();
+  }
+
+  /**
+   * Returns true if the slider's thumb moves right-to-left for increasing
+   * values; only relevant when IsHorizontal() is true.
+   */
+  bool IsRightToLeft() const {
+    MOZ_ASSERT(IsHorizontal());
+    mozilla::WritingMode wm = GetWritingMode();
+    return wm.IsVertical() ? wm.IsVerticalRL() : !wm.IsBidiLTR();
+  }
 
   double GetMin() const;
   double GetMax() const;
   double GetValue() const;
 
   /** 
    * Returns the input element's value as a fraction of the difference between
    * the input's minimum and its maximum (i.e. returns 0.0 when the value is
--- a/layout/style/forms.css
+++ b/layout/style/forms.css
@@ -820,31 +820,50 @@ meter {
 :-moz-meter-sub-sub-optimum::-moz-meter-bar {
   /* red. */
   background: linear-gradient(#f77, #f77, #fcc 20%, #d44 45%, #d44 55%);
 }
 
 input[type=range] {
   -moz-appearance: range;
   display: inline-block;
-  width: 12em; /* XXX how does orient attribute relate to vertical mode? */
-  height: 1.3em;
-  margin: 0 0.7em;
+  inline-size: 12em;
+  block-size: 1.3em;
+  margin-inline-start: 0.7em;
+  margin-inline-end: 0.7em;
+  margin-block-start: 0;
+  margin-block-end: 0;
   /* Override some rules that apply on all input types: */
   cursor: default;
   background: none;
   border: none;
   -moz-binding: none; /* we don't want any of platformHTMLBindings.xml#inputFields */
   /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
   -moz-user-select: none ! important;
 }
 
+input[type=range][orient=block] {
+  inline-size: 1.3em;
+  block-size: 12em;
+  margin-inline-start: 0;
+  margin-inline-end: 0;
+  margin-block-start: 0.7em;
+  margin-block-end: 0.7em;
+}
+
+input[type=range][orient=horizontal] {
+  width: 12em;
+  height: 1.3em;
+  margin: 0 0.7em;
+}
+
 input[type=range][orient=vertical] {
   width: 1.3em;
   height: 12em;
+  margin: 0.7em 0;
 }
 
 /**
  * Ideally we'd also require :-moz-focusring here, but that doesn't currently
  * work. Instead we only use the -moz-focus-outer border style if
  * NS_EVENT_STATE_FOCUSRING is set (the check is in
  * nsRangeFrame::BuildDisplayList).
  */
@@ -863,25 +882,33 @@ input[type=range]::-moz-focus-outer {
  */
 input[type=range]::-moz-range-track {
   /* Prevent styling that would change the type of frame we construct. */
   display: inline-block !important;
   float: none !important;
   position: static !important;
   border: none;
   background-color: #999;
-  width: 100%;
-  height: 0.2em;
+  inline-size: 100%;
+  block-size: 0.2em;
   /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
   -moz-user-select: none ! important;
 }
 
+input[type=range][orient=block]::-moz-range-track {
+  inline-size: 0.2em;
+  block-size: 100%;
+}
+
+input[type=range][orient=horizontal]::-moz-range-track {
+  width: 100%;
+  height: 0.2em;
+}
+
 input[type=range][orient=vertical]::-moz-range-track {
-  border-top: none;
-  border-bottom: none;
   width: 0.2em;
   height: 100%;
 }
 
 /**
  * Layout handles positioning of this pseudo-element specially (so that content
  * authors can concentrate on styling this pseudo-element without worrying
  * about the logic to position it). Specifically the 'margin', 'top' and 'left'
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -2756,19 +2756,17 @@ nsNativeThemeCocoa::DrawWidgetBackground
         break;
       }
       // DrawScale requires integer min, max and value. This is purely for
       // drawing, so we normalize to a range 0-1000 here.
       int32_t value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
       int32_t min = 0;
       int32_t max = 1000;
       bool isVertical = !IsRangeHorizontal(aFrame);
-      bool reverseDir =
-        isVertical ||
-        rangeFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+      bool reverseDir = isVertical || rangeFrame->IsRightToLeft();
       DrawScale(cgContext, macRect, eventState, isVertical, reverseDir,
                 value, min, max, aFrame);
       break;
     }
 
     case NS_THEME_SCROLLBAR_SMALL:
     case NS_THEME_SCROLLBAR:
       if (!ScrollbarTrackAndThumbDrawSeparately()) {