tor-browser

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

nsScrollbarFrame.cpp (15958B)


      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 //
      8 // Eric Vaughan
      9 // Netscape Communications
     10 //
     11 // See documentation in associated header file
     12 //
     13 
     14 #include "nsScrollbarFrame.h"
     15 
     16 #include "mozilla/LookAndFeel.h"
     17 #include "mozilla/PresShell.h"
     18 #include "mozilla/ScrollContainerFrame.h"
     19 #include "mozilla/dom/Element.h"
     20 #include "nsContentCreatorFunctions.h"
     21 #include "nsGkAtoms.h"
     22 #include "nsIContent.h"
     23 #include "nsIScrollbarMediator.h"
     24 #include "nsLayoutUtils.h"
     25 #include "nsScrollbarButtonFrame.h"
     26 #include "nsSliderFrame.h"
     27 #include "nsStyleConsts.h"
     28 
     29 using namespace mozilla;
     30 using mozilla::dom::Element;
     31 
     32 //
     33 // NS_NewScrollbarFrame
     34 //
     35 // Creates a new scrollbar frame and returns it
     36 //
     37 nsIFrame* NS_NewScrollbarFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
     38  return new (aPresShell)
     39      nsScrollbarFrame(aStyle, aPresShell->GetPresContext());
     40 }
     41 
     42 NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarFrame)
     43 
     44 NS_QUERYFRAME_HEAD(nsScrollbarFrame)
     45  NS_QUERYFRAME_ENTRY(nsScrollbarFrame)
     46  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
     47 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
     48 
     49 void nsScrollbarFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
     50                            nsIFrame* aPrevInFlow) {
     51  nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
     52 
     53  // We want to be a reflow root since we use reflows to move the
     54  // slider.  Any reflow inside the scrollbar frame will be a reflow to
     55  // move the slider and will thus not change anything outside of the
     56  // scrollbar or change the size of the scrollbar frame.
     57  AddStateBits(NS_FRAME_REFLOW_ROOT);
     58 }
     59 
     60 nsScrollbarFrame* nsScrollbarFrame::GetOppositeScrollbar() const {
     61  ScrollContainerFrame* sc = do_QueryFrame(GetParent());
     62  if (!sc) {
     63    return nullptr;
     64  }
     65  auto* vScrollbar = sc->GetScrollbarBox(/* aVertical= */ true);
     66  if (vScrollbar == this) {
     67    return sc->GetScrollbarBox(/* aVertical= */ false);
     68  }
     69  MOZ_ASSERT(sc->GetScrollbarBox(/* aVertical= */ false) == this,
     70             "Which scrollbar are we?");
     71  return vScrollbar;
     72 }
     73 
     74 void nsScrollbarFrame::InvalidateForHoverChange(bool aIsNowHovered) {
     75  // Hover state on the scrollbar changes both the scrollbar and potentially
     76  // descendants too, so invalidate when it changes.
     77  InvalidateFrameSubtree();
     78  if (!aIsNowHovered) {
     79    return;
     80  }
     81  mHasBeenHovered = true;
     82  // When hovering over one scrollbar, remove the sticky hover effect from the
     83  // opposite scrollbar, if needed.
     84  if (auto* opposite = GetOppositeScrollbar();
     85      opposite && opposite->mHasBeenHovered) {
     86    opposite->mHasBeenHovered = false;
     87    opposite->InvalidateFrameSubtree();
     88  }
     89 }
     90 
     91 void nsScrollbarFrame::ActivityChanged(bool aIsNowActive) {
     92  if (ScrollContainerFrame* sc = do_QueryFrame(GetParent())) {
     93    if (aIsNowActive) {
     94      sc->ScrollbarActivityStarted();
     95    } else {
     96      sc->ScrollbarActivityStopped();
     97    }
     98  }
     99 }
    100 
    101 void nsScrollbarFrame::ElementStateChanged(dom::ElementState aStates) {
    102  if (aStates.HasState(dom::ElementState::HOVER)) {
    103    const bool hovered =
    104        mContent->AsElement()->State().HasState(dom::ElementState::HOVER);
    105    InvalidateForHoverChange(hovered);
    106    ActivityChanged(hovered);
    107  }
    108 }
    109 
    110 void nsScrollbarFrame::WillBecomeActive() {
    111  // Reset our sticky hover state before becoming active.
    112  mHasBeenHovered = false;
    113 }
    114 
    115 void nsScrollbarFrame::Destroy(DestroyContext& aContext) {
    116  aContext.AddAnonymousContent(mUpTopButton.forget());
    117  aContext.AddAnonymousContent(mDownTopButton.forget());
    118  aContext.AddAnonymousContent(mSlider.forget());
    119  aContext.AddAnonymousContent(mUpBottomButton.forget());
    120  aContext.AddAnonymousContent(mDownBottomButton.forget());
    121  nsContainerFrame::Destroy(aContext);
    122 }
    123 
    124 void nsScrollbarFrame::Reflow(nsPresContext* aPresContext,
    125                              ReflowOutput& aDesiredSize,
    126                              const ReflowInput& aReflowInput,
    127                              nsReflowStatus& aStatus) {
    128  MarkInReflow();
    129  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
    130 
    131  // We always take all the space we're given, and our track size in the other
    132  // axis.
    133  const bool horizontal = IsHorizontal();
    134  const auto wm = GetWritingMode();
    135  const auto minSize = aReflowInput.ComputedMinSize();
    136 
    137  aDesiredSize.ISize(wm) = aReflowInput.ComputedISize();
    138  aDesiredSize.BSize(wm) = [&] {
    139    if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
    140      return aReflowInput.ComputedBSize();
    141    }
    142    // We don't want to change our size during incremental reflow, see the
    143    // reflow root comment in init.
    144    if (!aReflowInput.mParentReflowInput) {
    145      return GetLogicalSize(wm).BSize(wm);
    146    }
    147    return minSize.BSize(wm);
    148  }();
    149 
    150  const nsSize containerSize = aDesiredSize.PhysicalSize();
    151  const LogicalSize totalAvailSize = aDesiredSize.Size(wm);
    152  LogicalPoint nextKidPos(wm);
    153 
    154  MOZ_ASSERT(!wm.IsVertical());
    155  const bool movesInInlineDirection = horizontal;
    156 
    157  // Layout our kids left to right / top to bottom.
    158  for (nsIFrame* kid : mFrames) {
    159    MOZ_ASSERT(!kid->GetWritingMode().IsOrthogonalTo(wm),
    160               "We don't expect orthogonal scrollbar parts");
    161    const bool isSlider = kid->GetContent() == mSlider;
    162    LogicalSize availSize = totalAvailSize;
    163    {
    164      // Assume we'll consume the same size before and after the slider. This is
    165      // not a technically correct assumption if we have weird scrollbar button
    166      // setups, but those will be going away, see bug 1824254.
    167      const int32_t factor = isSlider ? 2 : 1;
    168      if (movesInInlineDirection) {
    169        availSize.ISize(wm) =
    170            std::max(0, totalAvailSize.ISize(wm) - nextKidPos.I(wm) * factor);
    171      } else {
    172        availSize.BSize(wm) =
    173            std::max(0, totalAvailSize.BSize(wm) - nextKidPos.B(wm) * factor);
    174      }
    175    }
    176 
    177    ReflowInput kidRI(aPresContext, aReflowInput, kid, availSize);
    178    if (isSlider) {
    179      // We want for the slider to take all the remaining available space.
    180      kidRI.SetComputedISize(availSize.ISize(wm));
    181      kidRI.SetComputedBSize(availSize.BSize(wm));
    182    } else if (movesInInlineDirection) {
    183      // Otherwise we want all the space in the axis we're not advancing in, and
    184      // the default / minimum size on the other axis.
    185      kidRI.SetComputedBSize(availSize.BSize(wm));
    186    } else {
    187      kidRI.SetComputedISize(availSize.ISize(wm));
    188    }
    189 
    190    ReflowOutput kidDesiredSize(wm);
    191    nsReflowStatus status;
    192    const auto flags = ReflowChildFlags::Default;
    193    ReflowChild(kid, aPresContext, kidDesiredSize, kidRI, wm, nextKidPos,
    194                containerSize, flags, status);
    195    // We haven't seen the slider yet, we can advance
    196    FinishReflowChild(kid, aPresContext, kidDesiredSize, &kidRI, wm, nextKidPos,
    197                      containerSize, flags);
    198    if (movesInInlineDirection) {
    199      nextKidPos.I(wm) += kidDesiredSize.ISize(wm);
    200    } else {
    201      nextKidPos.B(wm) += kidDesiredSize.BSize(wm);
    202    }
    203  }
    204 
    205  aDesiredSize.SetOverflowAreasToDesiredBounds();
    206 }
    207 
    208 bool nsScrollbarFrame::SetCurPos(CSSIntCoord aCurPos) {
    209  if (mCurPos == aCurPos) {
    210    return false;
    211  }
    212  mCurPos = aCurPos;
    213  if (ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(GetParent())) {
    214    scrollContainerFrame->ScrollbarCurPosChanged();
    215  }
    216  if (nsSliderFrame* slider = do_QueryFrame(mSlider->GetPrimaryFrame())) {
    217    slider->CurrentPositionChanged();
    218  }
    219  return true;
    220 }
    221 
    222 void nsScrollbarFrame::RequestSliderReflow() {
    223  // These affect the slider.
    224  if (nsSliderFrame* slider = do_QueryFrame(mSlider->GetPrimaryFrame())) {
    225    PresShell()->FrameNeedsReflow(slider, IntrinsicDirty::None,
    226                                  NS_FRAME_IS_DIRTY);
    227  }
    228 }
    229 
    230 bool nsScrollbarFrame::SetMaxPos(CSSIntCoord aMaxPos) {
    231  if (mMaxPos == aMaxPos) {
    232    return false;
    233  }
    234  RequestSliderReflow();
    235  mMaxPos = aMaxPos;
    236  return true;
    237 }
    238 
    239 bool nsScrollbarFrame::SetPageIncrement(CSSIntCoord aPageIncrement) {
    240  if (mPageIncrement == aPageIncrement) {
    241    return false;
    242  }
    243  RequestSliderReflow();
    244  mPageIncrement = aPageIncrement;
    245  return true;
    246 }
    247 
    248 bool nsScrollbarFrame::IsEnabled() const {
    249  return !mContent->AsElement()->State().HasState(dom::ElementState::DISABLED);
    250 }
    251 
    252 bool nsScrollbarFrame::SetEnabled(bool aEnabled) {
    253  if (IsEnabled() == aEnabled) {
    254    return false;
    255  }
    256  mContent->AsElement()->SetStates(dom::ElementState::DISABLED, !aEnabled);
    257  return true;
    258 }
    259 
    260 NS_IMETHODIMP
    261 nsScrollbarFrame::HandlePress(nsPresContext* aPresContext,
    262                              WidgetGUIEvent* aEvent,
    263                              nsEventStatus* aEventStatus) {
    264  return NS_OK;
    265 }
    266 
    267 NS_IMETHODIMP
    268 nsScrollbarFrame::HandleMultiplePress(nsPresContext* aPresContext,
    269                                      WidgetGUIEvent* aEvent,
    270                                      nsEventStatus* aEventStatus,
    271                                      bool aControlHeld) {
    272  return NS_OK;
    273 }
    274 
    275 NS_IMETHODIMP
    276 nsScrollbarFrame::HandleDrag(nsPresContext* aPresContext,
    277                             WidgetGUIEvent* aEvent,
    278                             nsEventStatus* aEventStatus) {
    279  return NS_OK;
    280 }
    281 
    282 NS_IMETHODIMP
    283 nsScrollbarFrame::HandleRelease(nsPresContext* aPresContext,
    284                                WidgetGUIEvent* aEvent,
    285                                nsEventStatus* aEventStatus) {
    286  return NS_OK;
    287 }
    288 
    289 void nsScrollbarFrame::SetOverrideScrollbarMediator(
    290    nsIScrollbarMediator* aMediator) {
    291  mOverriddenScrollbarMediator = do_QueryFrame(aMediator);
    292 }
    293 
    294 nsIScrollbarMediator* nsScrollbarFrame::GetScrollbarMediator() {
    295  if (auto* override = mOverriddenScrollbarMediator.GetFrame()) {
    296    return do_QueryFrame(override);
    297  }
    298  return do_QueryFrame(GetParent());
    299 }
    300 
    301 bool nsScrollbarFrame::IsHorizontal() const {
    302  auto appearance = StyleDisplay()->EffectiveAppearance();
    303  MOZ_ASSERT(appearance == StyleAppearance::ScrollbarHorizontal ||
    304             appearance == StyleAppearance::ScrollbarVertical);
    305  return appearance == StyleAppearance::ScrollbarHorizontal;
    306 }
    307 
    308 nsSize nsScrollbarFrame::ScrollbarMinSize() const {
    309  nsPresContext* pc = PresContext();
    310  const LayoutDeviceIntSize widget =
    311      pc->Theme()->GetMinimumWidgetSize(pc, const_cast<nsScrollbarFrame*>(this),
    312                                        StyleDisplay()->EffectiveAppearance());
    313  return LayoutDeviceIntSize::ToAppUnits(widget, pc->AppUnitsPerDevPixel());
    314 }
    315 
    316 StyleScrollbarWidth nsScrollbarFrame::ScrollbarWidth() const {
    317  return nsLayoutUtils::StyleForScrollbar(this)
    318      ->StyleUIReset()
    319      ->ScrollbarWidth();
    320 }
    321 
    322 nscoord nsScrollbarFrame::ScrollbarTrackSize() const {
    323  nsPresContext* pc = PresContext();
    324  auto overlay = pc->UseOverlayScrollbars() ? nsITheme::Overlay::Yes
    325                                            : nsITheme::Overlay::No;
    326  return LayoutDevicePixel::ToAppUnits(
    327      pc->Theme()->GetScrollbarSize(pc, ScrollbarWidth(), overlay),
    328      pc->AppUnitsPerDevPixel());
    329 }
    330 
    331 void nsScrollbarFrame::MoveToNewPosition() {
    332  nsIScrollbarMediator* m = GetScrollbarMediator();
    333  if (!m) {
    334    return;
    335  }
    336  // Note that this `MoveToNewPosition` is used for scrolling triggered by
    337  // repeating scrollbar button press, so we'd use an intended-direction
    338  // scroll snap flag.
    339  m->ScrollByUnit(this, ScrollMode::Smooth, mButtonScrollDirection,
    340                  mButtonScrollUnit, ScrollSnapFlags::IntendedDirection);
    341 }
    342 
    343 static already_AddRefed<Element> MakeScrollbarButton(
    344    dom::NodeInfo* aNodeInfo, bool aVertical, bool aBottom, bool aDown,
    345    AnonymousContentKey& aKey) {
    346  MOZ_ASSERT(aNodeInfo);
    347  MOZ_ASSERT(
    348      aNodeInfo->Equals(nsGkAtoms::scrollbarbutton, nullptr, kNameSpaceID_XUL));
    349 
    350  static constexpr nsLiteralString kSbattrValues[2][2] = {
    351      {
    352          u"scrollbar-up-top"_ns,
    353          u"scrollbar-up-bottom"_ns,
    354      },
    355      {
    356          u"scrollbar-down-top"_ns,
    357          u"scrollbar-down-bottom"_ns,
    358      },
    359  };
    360 
    361  static constexpr nsLiteralString kTypeValues[2] = {
    362      u"decrement"_ns,
    363      u"increment"_ns,
    364  };
    365 
    366  aKey = AnonymousContentKey::Type_ScrollbarButton;
    367  if (aVertical) {
    368    aKey |= AnonymousContentKey::Flag_Vertical;
    369  }
    370  if (aBottom) {
    371    aKey |= AnonymousContentKey::Flag_ScrollbarButton_Bottom;
    372  }
    373  if (aDown) {
    374    aKey |= AnonymousContentKey::Flag_ScrollbarButton_Down;
    375  }
    376 
    377  RefPtr<Element> e;
    378  NS_TrustedNewXULElement(getter_AddRefs(e), do_AddRef(aNodeInfo));
    379  e->SetAttr(kNameSpaceID_None, nsGkAtoms::sbattr,
    380             kSbattrValues[aDown][aBottom], false);
    381  e->SetAttr(kNameSpaceID_None, nsGkAtoms::type, kTypeValues[aDown], false);
    382  return e.forget();
    383 }
    384 
    385 nsresult nsScrollbarFrame::CreateAnonymousContent(
    386    nsTArray<ContentInfo>& aElements) {
    387  nsNodeInfoManager* nodeInfoManager = mContent->NodeInfo()->NodeInfoManager();
    388  Element* el = GetContent()->AsElement();
    389 
    390  // If there are children already in the node, don't create any anonymous
    391  // content (this only apply to crashtests/369038-1.xhtml)
    392  if (el->HasChildren()) {
    393    return NS_OK;
    394  }
    395 
    396  const bool vertical = el->HasAttr(nsGkAtoms::vertical);
    397  RefPtr<dom::NodeInfo> sbbNodeInfo =
    398      nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbarbutton, nullptr,
    399                                   kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
    400 
    401  const int32_t buttons =
    402      PresContext()->Theme()->ThemeSupportsScrollbarButtons()
    403          ? LookAndFeel::GetInt(LookAndFeel::IntID::ScrollArrowStyle)
    404          : 0;
    405 
    406  if (buttons & LookAndFeel::eScrollArrow_StartBackward) {
    407    AnonymousContentKey key;
    408    mUpTopButton =
    409        MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ false,
    410                            /* aDown */ false, key);
    411    aElements.AppendElement(ContentInfo(mUpTopButton, key));
    412  }
    413 
    414  if (buttons & LookAndFeel::eScrollArrow_StartForward) {
    415    AnonymousContentKey key;
    416    mDownTopButton =
    417        MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ false,
    418                            /* aDown */ true, key);
    419    aElements.AppendElement(ContentInfo(mDownTopButton, key));
    420  }
    421 
    422  {
    423    AnonymousContentKey key = AnonymousContentKey::Type_Slider;
    424    if (vertical) {
    425      key |= AnonymousContentKey::Flag_Vertical;
    426    }
    427 
    428    NS_TrustedNewXULElement(
    429        getter_AddRefs(mSlider),
    430        nodeInfoManager->GetNodeInfo(nsGkAtoms::slider, nullptr,
    431                                     kNameSpaceID_XUL, nsINode::ELEMENT_NODE));
    432 
    433    aElements.AppendElement(ContentInfo(mSlider, key));
    434 
    435    NS_TrustedNewXULElement(
    436        getter_AddRefs(mThumb),
    437        nodeInfoManager->GetNodeInfo(nsGkAtoms::thumb, nullptr,
    438                                     kNameSpaceID_XUL, nsINode::ELEMENT_NODE));
    439    mSlider->AppendChildTo(mThumb, false, IgnoreErrors());
    440  }
    441 
    442  if (buttons & LookAndFeel::eScrollArrow_EndBackward) {
    443    AnonymousContentKey key;
    444    mUpBottomButton =
    445        MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ true,
    446                            /* aDown */ false, key);
    447    aElements.AppendElement(ContentInfo(mUpBottomButton, key));
    448  }
    449 
    450  if (buttons & LookAndFeel::eScrollArrow_EndForward) {
    451    AnonymousContentKey key;
    452    mDownBottomButton =
    453        MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ true,
    454                            /* aDown */ true, key);
    455    aElements.AppendElement(ContentInfo(mDownBottomButton, key));
    456  }
    457 
    458  return NS_OK;
    459 }
    460 
    461 void nsScrollbarFrame::AppendAnonymousContentTo(
    462    nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
    463  if (mUpTopButton) {
    464    aElements.AppendElement(mUpTopButton);
    465  }
    466 
    467  if (mDownTopButton) {
    468    aElements.AppendElement(mDownTopButton);
    469  }
    470 
    471  if (mSlider) {
    472    aElements.AppendElement(mSlider);
    473  }
    474 
    475  if (mUpBottomButton) {
    476    aElements.AppendElement(mUpBottomButton);
    477  }
    478 
    479  if (mDownBottomButton) {
    480    aElements.AppendElement(mDownBottomButton);
    481  }
    482 }