tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsRangeFrame.cpp (26294B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "nsRangeFrame.h"
      8 
      9 #include "ListMutationObserver.h"
     10 #include "gfxContext.h"
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/PresShell.h"
     13 #include "mozilla/ServoStyleSet.h"
     14 #include "mozilla/TouchEvents.h"
     15 #include "mozilla/dom/Document.h"
     16 #include "mozilla/dom/Element.h"
     17 #include "mozilla/dom/HTMLDataListElement.h"
     18 #include "mozilla/dom/HTMLInputElement.h"
     19 #include "mozilla/dom/HTMLOptionElement.h"
     20 #include "nsCSSRendering.h"
     21 #include "nsContentCreatorFunctions.h"
     22 #include "nsDisplayList.h"
     23 #include "nsGkAtoms.h"
     24 #include "nsIContent.h"
     25 #include "nsIMutationObserver.h"
     26 #include "nsLayoutUtils.h"
     27 #include "nsNodeInfoManager.h"
     28 #include "nsPresContext.h"
     29 #include "nsTArray.h"
     30 
     31 #ifdef ACCESSIBILITY
     32 #  include "nsAccessibilityService.h"
     33 #endif
     34 
     35 // Our intrinsic size is 12em in the main-axis and 1.3em in the cross-axis.
     36 #define MAIN_AXIS_EM_SIZE 12
     37 #define CROSS_AXIS_EM_SIZE 1.3f
     38 
     39 using namespace mozilla;
     40 using namespace mozilla::dom;
     41 using namespace mozilla::image;
     42 
     43 nsIFrame* NS_NewRangeFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
     44  return new (aPresShell) nsRangeFrame(aStyle, aPresShell->GetPresContext());
     45 }
     46 
     47 nsRangeFrame::nsRangeFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
     48    : nsContainerFrame(aStyle, aPresContext, kClassID) {}
     49 
     50 void nsRangeFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
     51                        nsIFrame* aPrevInFlow) {
     52  nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
     53  if (InputElement().HasAttr(nsGkAtoms::list)) {
     54    mListMutationObserver = new ListMutationObserver(*this);
     55  }
     56 }
     57 
     58 nsRangeFrame::~nsRangeFrame() = default;
     59 
     60 NS_IMPL_FRAMEARENA_HELPERS(nsRangeFrame)
     61 
     62 NS_QUERYFRAME_HEAD(nsRangeFrame)
     63  NS_QUERYFRAME_ENTRY(nsRangeFrame)
     64  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
     65 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
     66 
     67 void nsRangeFrame::Destroy(DestroyContext& aContext) {
     68  NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
     69               "nsRangeFrame should not have continuations; if it does we "
     70               "need to call RegUnregAccessKey only for the first.");
     71 
     72  if (mListMutationObserver) {
     73    mListMutationObserver->Detach();
     74  }
     75  aContext.AddAnonymousContent(mTrackDiv.forget());
     76  aContext.AddAnonymousContent(mProgressDiv.forget());
     77  aContext.AddAnonymousContent(mThumbDiv.forget());
     78  nsContainerFrame::Destroy(aContext);
     79 }
     80 
     81 static already_AddRefed<Element> MakeAnonymousDiv(
     82    Document& aDoc, PseudoStyleType aOldPseudoType,
     83    PseudoStyleType aModernPseudoType,
     84    nsTArray<nsIAnonymousContentCreator::ContentInfo>& aElements) {
     85  RefPtr<Element> result = aDoc.CreateHTMLElement(nsGkAtoms::div);
     86 
     87  // Associate the pseudo-element with the anonymous child.
     88  if (StaticPrefs::layout_css_modern_range_pseudos_enabled()) {
     89    result->SetPseudoElementType(aModernPseudoType);
     90  } else {
     91    result->SetPseudoElementType(aOldPseudoType);
     92  }
     93 
     94  // XXX(Bug 1631371) Check if this should use a fallible operation as it
     95  // pretended earlier, or change the return type to void.
     96  aElements.AppendElement(result);
     97 
     98  return result.forget();
     99 }
    100 
    101 nsresult nsRangeFrame::CreateAnonymousContent(
    102    nsTArray<ContentInfo>& aElements) {
    103  Document* doc = mContent->OwnerDoc();
    104  // Create the ::-moz-range-track pseudo-element (a div):
    105  mTrackDiv = MakeAnonymousDiv(*doc, PseudoStyleType::mozRangeTrack,
    106                               PseudoStyleType::sliderTrack, aElements);
    107  // Create the ::-moz-range-progress pseudo-element (a div):
    108  mProgressDiv = MakeAnonymousDiv(*doc, PseudoStyleType::mozRangeProgress,
    109                                  PseudoStyleType::sliderFill, aElements);
    110  // Create the ::-moz-range-thumb pseudo-element (a div):
    111  mThumbDiv = MakeAnonymousDiv(*doc, PseudoStyleType::mozRangeThumb,
    112                               PseudoStyleType::sliderThumb, aElements);
    113  return NS_OK;
    114 }
    115 
    116 void nsRangeFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
    117                                            uint32_t aFilter) {
    118  if (mTrackDiv) {
    119    aElements.AppendElement(mTrackDiv);
    120  }
    121 
    122  if (mProgressDiv) {
    123    aElements.AppendElement(mProgressDiv);
    124  }
    125 
    126  if (mThumbDiv) {
    127    aElements.AppendElement(mThumbDiv);
    128  }
    129 }
    130 
    131 void nsRangeFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
    132                                    const nsDisplayListSet& aLists) {
    133  const nsStyleDisplay* disp = StyleDisplay();
    134  if (IsThemed(disp)) {
    135    DisplayBorderBackgroundOutline(aBuilder, aLists);
    136    // Don't paint our children, but let the thumb be hittable for events.
    137    if (auto* thumb = mThumbDiv->GetPrimaryFrame();
    138        thumb && aBuilder->IsForEventDelivery() && !HidesContent()) {
    139      nsDisplayListSet set(aLists, aLists.Content());
    140      BuildDisplayListForChild(aBuilder, thumb, set, DisplayChildFlag::Inline);
    141    }
    142  } else {
    143    BuildDisplayListForInline(aBuilder, aLists);
    144  }
    145 }
    146 
    147 void nsRangeFrame::Reflow(nsPresContext* aPresContext,
    148                          ReflowOutput& aDesiredSize,
    149                          const ReflowInput& aReflowInput,
    150                          nsReflowStatus& aStatus) {
    151  MarkInReflow();
    152  DO_GLOBAL_REFLOW_COUNT("nsRangeFrame");
    153  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
    154 
    155  NS_ASSERTION(mTrackDiv, "::-moz-range-track div must exist!");
    156  NS_ASSERTION(mProgressDiv, "::-moz-range-progress div must exist!");
    157  NS_ASSERTION(mThumbDiv, "::-moz-range-thumb div must exist!");
    158  NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
    159               "nsRangeFrame should not have continuations; if it does we "
    160               "need to call RegUnregAccessKey only for the first.");
    161 
    162  WritingMode wm = aReflowInput.GetWritingMode();
    163  const auto contentBoxSize = aReflowInput.ComputedSizeWithBSizeFallback([&] {
    164    return IsInlineOriented() ? AutoCrossSize()
    165                              : OneEmInAppUnits() * MAIN_AXIS_EM_SIZE;
    166  });
    167  aDesiredSize.SetSize(
    168      wm,
    169      contentBoxSize + aReflowInput.ComputedLogicalBorderPadding(wm).Size(wm));
    170  aDesiredSize.SetOverflowAreasToDesiredBounds();
    171 
    172  ReflowAnonymousContent(aPresContext, aDesiredSize, contentBoxSize,
    173                         aReflowInput);
    174  FinishAndStoreOverflow(&aDesiredSize);
    175 
    176  MOZ_ASSERT(aStatus.IsEmpty(), "This type of frame can't be split.");
    177 }
    178 
    179 void nsRangeFrame::ReflowAnonymousContent(nsPresContext* aPresContext,
    180                                          ReflowOutput& aDesiredSize,
    181                                          const LogicalSize& aContentBoxSize,
    182                                          const ReflowInput& aReflowInput) {
    183  const auto parentWM = aReflowInput.GetWritingMode();
    184  // The width/height of our content box, which is the available width/height
    185  // for our anonymous content.
    186  const nsSize rangeFrameContentBoxSize =
    187      aContentBoxSize.GetPhysicalSize(parentWM);
    188  for (auto* div : {mTrackDiv.get(), mThumbDiv.get(), mProgressDiv.get()}) {
    189    nsIFrame* child = div->GetPrimaryFrame();
    190    if (!child) {
    191      continue;
    192    }
    193    const WritingMode wm = child->GetWritingMode();
    194    const LogicalSize parentSizeInChildWM =
    195        aContentBoxSize.ConvertTo(wm, parentWM);
    196    LogicalSize availSize = parentSizeInChildWM;
    197    availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
    198    ReflowInput childReflowInput(aPresContext, aReflowInput, child, availSize,
    199                                 Some(parentSizeInChildWM));
    200 
    201    const nsPoint pos = [&] {
    202      if (div != mTrackDiv) {
    203        // Where we position the thumb and range-progress depends on its size,
    204        // so we first reflow them at {0,0} to obtain the size, then position
    205        // them afterwards.
    206        return nsPoint();
    207      }
    208      // Find the x/y position of the track. The idea here is that we allow
    209      // content authors to style the width, height, border and padding of the
    210      // track, but we ignore margin and positioning properties and do the
    211      // positioning ourself to keep the center of the track's border box on the
    212      // center of the nsRangeFrame's content. These coordinates are with
    213      // respect to the nsRangeFrame's border-box.
    214      nscoord trackX = rangeFrameContentBoxSize.Width() / 2;
    215      nscoord trackY = rangeFrameContentBoxSize.Height() / 2;
    216 
    217      // Account for the track's border and padding (we ignore its margin):
    218      // FIXME(emilio): Assumes the track height is constrained, which might not
    219      // be true if authors override it.
    220      trackX -= childReflowInput.ComputedPhysicalBorderPadding().left +
    221                childReflowInput.ComputedWidth() / 2;
    222      trackY -= childReflowInput.ComputedPhysicalBorderPadding().top +
    223                childReflowInput.ComputedHeight() / 2;
    224 
    225      // Make relative to our border box instead of our content box:
    226      trackX += aReflowInput.ComputedPhysicalBorderPadding().left;
    227      trackY += aReflowInput.ComputedPhysicalBorderPadding().top;
    228      return nsPoint(trackX, trackY);
    229    }();
    230 
    231    nsReflowStatus frameStatus;
    232    ReflowOutput childDesiredSize(aReflowInput);
    233    ReflowChild(child, aPresContext, childDesiredSize, childReflowInput, pos.x,
    234                pos.y, ReflowChildFlags::Default, frameStatus);
    235    MOZ_ASSERT(
    236        frameStatus.IsFullyComplete(),
    237        "We gave our child unconstrained height, so it should be complete");
    238    FinishReflowChild(child, aPresContext, childDesiredSize, &childReflowInput,
    239                      pos.x, pos.y, ReflowChildFlags::Default);
    240    if (div == mThumbDiv) {
    241      DoUpdateThumbPosition(child, rangeFrameContentBoxSize);
    242    } else if (div == mProgressDiv) {
    243      DoUpdateRangeProgressFrame(child, rangeFrameContentBoxSize);
    244    }
    245    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, child);
    246  }
    247 }
    248 
    249 #ifdef ACCESSIBILITY
    250 a11y::AccType nsRangeFrame::AccessibleType() { return a11y::eHTMLRangeType; }
    251 #endif
    252 
    253 double nsRangeFrame::GetValueAsFractionOfRange() {
    254  const auto& input = InputElement();
    255  if (MOZ_UNLIKELY(!input.IsDoneCreating())) {
    256    // Our element isn't done being created, so its values haven't yet been
    257    // sanitized! (It's rare that we'd be reflowed when our element is in this
    258    // state, but it can happen if the parser decides to yield while processing
    259    // its tasks to build the element.)  We can't trust that any of our numeric
    260    // values will make sense until they've been sanitized; so for now, just
    261    // use 0.0 as a fallback fraction-of-range value here (i.e. behave as if
    262    // we're at our minimum, which is how the spec handles some edge cases).
    263    return 0.0;
    264  }
    265  return GetDoubleAsFractionOfRange(input.GetValueAsDecimal());
    266 }
    267 
    268 double nsRangeFrame::GetDoubleAsFractionOfRange(const Decimal& aValue) {
    269  auto& input = InputElement();
    270 
    271  Decimal minimum = input.GetMinimum();
    272  Decimal maximum = input.GetMaximum();
    273 
    274  MOZ_ASSERT(aValue.isFinite() && minimum.isFinite() && maximum.isFinite(),
    275             "type=range should have a default maximum/minimum");
    276 
    277  if (maximum <= minimum) {
    278    // Avoid rounding triggering the assert by checking against an epsilon.
    279    MOZ_ASSERT((aValue - minimum).abs().toDouble() <
    280                   std::numeric_limits<float>::epsilon(),
    281               "Unsanitized value");
    282    return 0.0;
    283  }
    284 
    285  MOZ_ASSERT(aValue >= minimum && aValue <= maximum, "Unsanitized value");
    286 
    287  return ((aValue - minimum) / (maximum - minimum)).toDouble();
    288 }
    289 
    290 Decimal nsRangeFrame::GetValueAtEventPoint(WidgetGUIEvent* aEvent) {
    291  MOZ_ASSERT(
    292      aEvent->mClass == eMouseEventClass || aEvent->mClass == eTouchEventClass,
    293      "Unexpected event type - aEvent->mRefPoint may be meaningless");
    294 
    295  MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
    296  dom::HTMLInputElement* input =
    297      static_cast<dom::HTMLInputElement*>(GetContent());
    298 
    299  MOZ_ASSERT(input->ControlType() == FormControlType::InputRange);
    300 
    301  Decimal minimum = input->GetMinimum();
    302  Decimal maximum = input->GetMaximum();
    303  MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
    304             "type=range should have a default maximum/minimum");
    305  if (maximum <= minimum) {
    306    return minimum;
    307  }
    308  Decimal range = maximum - minimum;
    309 
    310  LayoutDeviceIntPoint absPoint;
    311  if (aEvent->mClass == eTouchEventClass) {
    312    MOZ_ASSERT(aEvent->AsTouchEvent()->mTouches.Length() == 1,
    313               "Unexpected number of mTouches");
    314    absPoint = aEvent->AsTouchEvent()->mTouches[0]->mRefPoint;
    315  } else {
    316    absPoint = aEvent->mRefPoint;
    317  }
    318  nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
    319      aEvent, absPoint, RelativeTo{this});
    320 
    321  if (point == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
    322    // We don't want to change the current value for this error state.
    323    return static_cast<dom::HTMLInputElement*>(GetContent())
    324        ->GetValueAsDecimal();
    325  }
    326 
    327  nsRect rangeRect;
    328  nsSize thumbSize;
    329  if (IsThemed()) {
    330    // Themed ranges draw on the border-box rect.
    331    rangeRect = GetRectRelativeToSelf();
    332    // We need to get the size of the thumb from the theme.
    333    nscoord min = CSSPixel::ToAppUnits(
    334        PresContext()->Theme()->GetMinimumRangeThumbSize());
    335    MOZ_ASSERT(min, "The thumb is expected to take up some slider space");
    336    thumbSize = nsSize(min, min);
    337  } else {
    338    rangeRect = GetContentRectRelativeToSelf();
    339    nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
    340    if (thumbFrame) {  // diplay:none?
    341      thumbSize = thumbFrame->GetSize();
    342    }
    343  }
    344 
    345  Decimal fraction;
    346  if (IsHorizontal()) {
    347    nscoord traversableDistance = rangeRect.width - thumbSize.width;
    348    if (traversableDistance <= 0) {
    349      return minimum;
    350    }
    351    nscoord posAtStart = rangeRect.x + thumbSize.width / 2;
    352    nscoord posAtEnd = posAtStart + traversableDistance;
    353    nscoord posOfPoint = std::clamp(point.x, posAtStart, posAtEnd);
    354    fraction = Decimal(posOfPoint - posAtStart) / Decimal(traversableDistance);
    355    if (IsRightToLeft()) {
    356      fraction = Decimal(1) - fraction;
    357    }
    358  } else {
    359    nscoord traversableDistance = rangeRect.height - thumbSize.height;
    360    if (traversableDistance <= 0) {
    361      return minimum;
    362    }
    363    nscoord posAtStart = rangeRect.y + thumbSize.height / 2;
    364    nscoord posAtEnd = posAtStart + traversableDistance;
    365    nscoord posOfPoint = std::clamp(point.y, posAtStart, posAtEnd);
    366    // For a vertical range, the top (posAtStart) is the highest value, so we
    367    // subtract the fraction from 1.0 to get that polarity correct.
    368    fraction = Decimal(posOfPoint - posAtStart) / Decimal(traversableDistance);
    369    if (IsUpwards()) {
    370      fraction = Decimal(1) - fraction;
    371    }
    372  }
    373 
    374  MOZ_ASSERT(fraction >= Decimal(0) && fraction <= Decimal(1));
    375  return minimum + fraction * range;
    376 }
    377 
    378 void nsRangeFrame::UpdateForValueChange() {
    379  if (IsSubtreeDirty()) {
    380    return;  // we're going to be updated when we reflow
    381  }
    382  nsIFrame* rangeProgressFrame = mProgressDiv->GetPrimaryFrame();
    383  nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
    384  if (!rangeProgressFrame && !thumbFrame) {
    385    return;  // diplay:none?
    386  }
    387  const nsSize contentBoxSize = GetContentRect().Size();
    388  if (rangeProgressFrame) {
    389    DoUpdateRangeProgressFrame(rangeProgressFrame, contentBoxSize);
    390  }
    391  if (thumbFrame) {
    392    DoUpdateThumbPosition(thumbFrame, contentBoxSize);
    393  }
    394  if (IsThemed()) {
    395    // We don't know the exact dimensions or location of the thumb when native
    396    // theming is applied, so we just repaint the entire range.
    397    InvalidateFrame();
    398  }
    399 
    400 #ifdef ACCESSIBILITY
    401  if (nsAccessibilityService* accService = GetAccService()) {
    402    accService->RangeValueChanged(PresShell(), mContent);
    403  }
    404 #endif
    405 
    406  SchedulePaint();
    407 }
    408 
    409 nsTArray<Decimal> nsRangeFrame::TickMarks() {
    410  nsTArray<Decimal> tickMarks;
    411  auto& input = InputElement();
    412  auto* list = input.GetList();
    413  if (!list) {
    414    return tickMarks;
    415  }
    416  auto min = input.GetMinimum();
    417  auto max = input.GetMaximum();
    418  auto* options = list->Options();
    419  nsAutoString label;
    420  for (uint32_t i = 0; i < options->Length(); ++i) {
    421    auto* item = options->Item(i);
    422    auto* option = HTMLOptionElement::FromNode(item);
    423    MOZ_ASSERT(option);
    424    if (option->Disabled()) {
    425      continue;
    426    }
    427    nsAutoString str;
    428    option->GetValue(str);
    429    auto tickMark = HTMLInputElement::StringToDecimal(str);
    430    if (tickMark.isNaN() || tickMark < min || tickMark > max ||
    431        input.ValueIsStepMismatch(tickMark)) {
    432      continue;
    433    }
    434    tickMarks.AppendElement(tickMark);
    435  }
    436  tickMarks.Sort();
    437  return tickMarks;
    438 }
    439 
    440 Decimal nsRangeFrame::NearestTickMark(const Decimal& aValue) {
    441  auto tickMarks = TickMarks();
    442  if (tickMarks.IsEmpty() || aValue.isNaN()) {
    443    return Decimal::nan();
    444  }
    445  size_t index;
    446  if (BinarySearch(tickMarks, 0, tickMarks.Length(), aValue, &index)) {
    447    return tickMarks[index];
    448  }
    449  if (index == tickMarks.Length()) {
    450    return tickMarks.LastElement();
    451  }
    452  if (index == 0) {
    453    return tickMarks[0];
    454  }
    455  const auto& smallerTickMark = tickMarks[index - 1];
    456  const auto& largerTickMark = tickMarks[index];
    457  MOZ_ASSERT(smallerTickMark < aValue);
    458  MOZ_ASSERT(largerTickMark > aValue);
    459  return (aValue - smallerTickMark).abs() < (aValue - largerTickMark).abs()
    460             ? smallerTickMark
    461             : largerTickMark;
    462 }
    463 
    464 mozilla::dom::HTMLInputElement& nsRangeFrame::InputElement() const {
    465  MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
    466  auto& input = *static_cast<dom::HTMLInputElement*>(GetContent());
    467  MOZ_ASSERT(input.ControlType() == FormControlType::InputRange);
    468  return input;
    469 }
    470 
    471 void nsRangeFrame::DoUpdateThumbPosition(nsIFrame* aThumbFrame,
    472                                         const nsSize& aRangeContentBoxSize) {
    473  MOZ_ASSERT(aThumbFrame);
    474 
    475  // The idea here is that we want to position the thumb so that the center
    476  // of the thumb is on an imaginary line drawn from the middle of one edge
    477  // of the range frame's content box to the middle of the opposite edge of
    478  // its content box (the opposite edges being the left/right edge if the
    479  // range is horizontal, or else the top/bottom edges if the range is
    480  // vertical). How far along this line the center of the thumb is placed
    481  // depends on the value of the range.
    482 
    483  nsMargin borderAndPadding = GetUsedBorderAndPadding();
    484  nsPoint newPosition(borderAndPadding.left, borderAndPadding.top);
    485 
    486  nsSize thumbSize = aThumbFrame->GetSize();
    487  double fraction = GetValueAsFractionOfRange();
    488  MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
    489 
    490  if (IsHorizontal()) {
    491    if (thumbSize.width < aRangeContentBoxSize.width) {
    492      nscoord traversableDistance =
    493          aRangeContentBoxSize.width - thumbSize.width;
    494      if (IsRightToLeft()) {
    495        newPosition.x += NSToCoordRound((1.0 - fraction) * traversableDistance);
    496      } else {
    497        newPosition.x += NSToCoordRound(fraction * traversableDistance);
    498      }
    499      newPosition.y += (aRangeContentBoxSize.height - thumbSize.height) / 2;
    500    }
    501  } else {
    502    if (thumbSize.height < aRangeContentBoxSize.height) {
    503      nscoord traversableDistance =
    504          aRangeContentBoxSize.height - thumbSize.height;
    505      newPosition.x += (aRangeContentBoxSize.width - thumbSize.width) / 2;
    506      if (IsUpwards()) {
    507        newPosition.y += NSToCoordRound((1.0 - fraction) * traversableDistance);
    508      } else {
    509        newPosition.y += NSToCoordRound(fraction * traversableDistance);
    510      }
    511    }
    512  }
    513  aThumbFrame->SetPosition(newPosition);
    514 }
    515 
    516 void nsRangeFrame::DoUpdateRangeProgressFrame(
    517    nsIFrame* aProgressFrame, const nsSize& aRangeContentBoxSize) {
    518  MOZ_ASSERT(aProgressFrame);
    519 
    520  // The idea here is that we want to position the ::-moz-range-progress
    521  // pseudo-element so that the center line running along its length is on the
    522  // corresponding center line of the nsRangeFrame's content box. In the other
    523  // dimension, we align the "start" edge of the ::-moz-range-progress
    524  // pseudo-element's border-box with the corresponding edge of the
    525  // nsRangeFrame's content box, and we size the progress element's border-box
    526  // to have a length of GetValueAsFractionOfRange() times the nsRangeFrame's
    527  // content-box size.
    528  nsMargin borderAndPadding = GetUsedBorderAndPadding();
    529  nsSize progSize = aProgressFrame->GetSize();
    530  nsRect progRect(borderAndPadding.left, borderAndPadding.top, progSize.width,
    531                  progSize.height);
    532 
    533  double fraction = GetValueAsFractionOfRange();
    534  MOZ_ASSERT(fraction >= 0.0 && fraction <= 1.0);
    535 
    536  if (IsHorizontal()) {
    537    nscoord progLength = NSToCoordRound(fraction * aRangeContentBoxSize.width);
    538    if (IsRightToLeft()) {
    539      progRect.x += aRangeContentBoxSize.width - progLength;
    540    }
    541    progRect.y += (aRangeContentBoxSize.height - progSize.height) / 2;
    542    progRect.width = progLength;
    543  } else {
    544    nscoord progLength = NSToCoordRound(fraction * aRangeContentBoxSize.height);
    545    progRect.x += (aRangeContentBoxSize.width - progSize.width) / 2;
    546    if (IsUpwards()) {
    547      progRect.y += aRangeContentBoxSize.height - progLength;
    548    }
    549    progRect.height = progLength;
    550  }
    551  aProgressFrame->SetRect(progRect);
    552 }
    553 
    554 nsresult nsRangeFrame::AttributeChanged(int32_t aNameSpaceID,
    555                                        nsAtom* aAttribute,
    556                                        AttrModType aModType) {
    557  NS_ASSERTION(mTrackDiv, "The track div must exist!");
    558  NS_ASSERTION(mThumbDiv, "The thumb div must exist!");
    559 
    560  if (aNameSpaceID == kNameSpaceID_None) {
    561    if (aAttribute == nsGkAtoms::value || aAttribute == nsGkAtoms::min ||
    562        aAttribute == nsGkAtoms::max || aAttribute == nsGkAtoms::step) {
    563      // We want to update the position of the thumb, except in one special
    564      // case: If the value attribute is being set, it is possible that we are
    565      // in the middle of a type change away from type=range, under the
    566      // SetAttr(..., nsGkAtoms::value, ...) call in HTMLInputElement::
    567      // HandleTypeChange. In that case the HTMLInputElement's type will
    568      // already have changed, and if we call UpdateForValueChange()
    569      // we'll fail the asserts under that call that check the type of our
    570      // HTMLInputElement. Given that we're changing away from being a range
    571      // and this frame will shortly be destroyed, there's no point in calling
    572      // UpdateForValueChange() anyway.
    573      MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
    574      bool typeIsRange =
    575          static_cast<dom::HTMLInputElement*>(GetContent())->ControlType() ==
    576          FormControlType::InputRange;
    577      // If script changed the <input>'s type before setting these attributes
    578      // then we don't need to do anything since we are going to be reframed.
    579      if (typeIsRange) {
    580        UpdateForValueChange();
    581      }
    582    } else if (aAttribute == nsGkAtoms::orient) {
    583      PresShell()->FrameNeedsReflow(this, IntrinsicDirty::None,
    584                                    NS_FRAME_IS_DIRTY);
    585    } else if (aAttribute == nsGkAtoms::list) {
    586      const bool isRemoval = aModType == AttrModType::Removal;
    587      if (mListMutationObserver) {
    588        mListMutationObserver->Detach();
    589        if (isRemoval) {
    590          mListMutationObserver = nullptr;
    591        } else {
    592          mListMutationObserver->Attach();
    593        }
    594      } else if (!isRemoval) {
    595        mListMutationObserver = new ListMutationObserver(*this, true);
    596      }
    597    }
    598  }
    599 
    600  return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
    601 }
    602 
    603 nscoord nsRangeFrame::AutoCrossSize() {
    604  nscoord minCrossSize =
    605      IsThemed() ? CSSPixel::ToAppUnits(
    606                       PresContext()->Theme()->GetMinimumRangeThumbSize())
    607                 : 0;
    608  return std::max(minCrossSize,
    609                  NSToCoordRound(OneEmInAppUnits() * CROSS_AXIS_EM_SIZE));
    610 }
    611 
    612 nscoord nsRangeFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
    613                                     IntrinsicISizeType aType) {
    614  if (aType == IntrinsicISizeType::MinISize) {
    615    const auto* pos = StylePosition();
    616    auto wm = GetWritingMode();
    617    const auto iSize = pos->ISize(wm, AnchorPosResolutionParams::From(this));
    618    if (iSize->HasPercent()) {
    619      // https://drafts.csswg.org/css-sizing-3/#percentage-sizing
    620      // https://drafts.csswg.org/css-sizing-3/#min-content-zero
    621      return nsLayoutUtils::ResolveToLength<true>(iSize->AsLengthPercentage(),
    622                                                  nscoord(0));
    623    }
    624  }
    625  if (IsInlineOriented()) {
    626    return OneEmInAppUnits() * MAIN_AXIS_EM_SIZE;
    627  }
    628  return AutoCrossSize();
    629 }
    630 
    631 bool nsRangeFrame::IsHorizontal() const {
    632  dom::HTMLInputElement* element =
    633      static_cast<dom::HTMLInputElement*>(GetContent());
    634  return element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
    635                              nsGkAtoms::horizontal, eCaseMatters) ||
    636         (!element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
    637                                nsGkAtoms::vertical, eCaseMatters) &&
    638          GetWritingMode().IsVertical() ==
    639              element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
    640                                   nsGkAtoms::block, eCaseMatters));
    641 }
    642 
    643 double nsRangeFrame::GetMin() const {
    644  return static_cast<dom::HTMLInputElement*>(GetContent())
    645      ->GetMinimum()
    646      .toDouble();
    647 }
    648 
    649 double nsRangeFrame::GetMax() const {
    650  return static_cast<dom::HTMLInputElement*>(GetContent())
    651      ->GetMaximum()
    652      .toDouble();
    653 }
    654 
    655 double nsRangeFrame::GetValue() const {
    656  return static_cast<dom::HTMLInputElement*>(GetContent())
    657      ->GetValueAsDecimal()
    658      .toDouble();
    659 }
    660 
    661 bool nsRangeFrame::ShouldUseNativeStyle() const {
    662  nsIFrame* trackFrame = mTrackDiv->GetPrimaryFrame();
    663  nsIFrame* progressFrame = mProgressDiv->GetPrimaryFrame();
    664  nsIFrame* thumbFrame = mThumbDiv->GetPrimaryFrame();
    665 
    666  return StyleDisplay()->EffectiveAppearance() == StyleAppearance::Range &&
    667         trackFrame &&
    668         !trackFrame->Style()->HasAuthorSpecifiedBorderOrBackground() &&
    669         progressFrame &&
    670         !progressFrame->Style()->HasAuthorSpecifiedBorderOrBackground() &&
    671         thumbFrame &&
    672         !thumbFrame->Style()->HasAuthorSpecifiedBorderOrBackground();
    673 }