/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ // // Eric Vaughan // Netscape Communications // // See documentation in associated header file // #include "nsScrollbarFrame.h" #include "nsSliderFrame.h" #include "nsScrollbarButtonFrame.h" #include "nsContentCreatorFunctions.h" #include "nsGkAtoms.h" #include "nsIScrollableFrame.h" #include "nsIScrollbarMediator.h" #include "nsStyleConsts.h" #include "nsIContent.h" #include "mozilla/LookAndFeel.h" #include "mozilla/PresShell.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/MutationEventBinding.h" #include "mozilla/StaticPrefs_apz.h" using namespace mozilla; using mozilla::dom::Element; // // NS_NewScrollbarFrame // // Creates a new scrollbar frame and returns it // nsIFrame* NS_NewScrollbarFrame(PresShell* aPresShell, ComputedStyle* aStyle) { return new (aPresShell) nsScrollbarFrame(aStyle, aPresShell->GetPresContext()); } NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarFrame) NS_QUERYFRAME_HEAD(nsScrollbarFrame) NS_QUERYFRAME_ENTRY(nsScrollbarFrame) NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) void nsScrollbarFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { nsBoxFrame::Init(aContent, aParent, aPrevInFlow); // We want to be a reflow root since we use reflows to move the // slider. Any reflow inside the scrollbar frame will be a reflow to // move the slider and will thus not change anything outside of the // scrollbar or change the size of the scrollbar frame. AddStateBits(NS_FRAME_REFLOW_ROOT); } void nsScrollbarFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) { aPostDestroyData.AddAnonymousContent(mUpTopButton.forget()); aPostDestroyData.AddAnonymousContent(mDownTopButton.forget()); aPostDestroyData.AddAnonymousContent(mSlider.forget()); aPostDestroyData.AddAnonymousContent(mUpBottomButton.forget()); aPostDestroyData.AddAnonymousContent(mDownBottomButton.forget()); nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData); } void nsScrollbarFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize, const ReflowInput& aReflowInput, nsReflowStatus& aStatus) { MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); nsBoxFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus); // nsGfxScrollFrame may have told us to shrink to nothing. If so, make sure // our desired size agrees. if (aReflowInput.AvailableWidth() == 0) { aDesiredSize.Width() = 0; } if (aReflowInput.AvailableHeight() == 0) { aDesiredSize.Height() = 0; } } nsresult nsScrollbarFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType) { nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); // Update value in our children UpdateChildrenAttributeValue(aAttribute, true); // if the current position changes, notify any nsGfxScrollFrame // parent we may have if (aAttribute != nsGkAtoms::curpos) return rv; nsIScrollableFrame* scrollable = do_QueryFrame(GetParent()); if (!scrollable) return rv; nsCOMPtr content(mContent); scrollable->CurPosAttributeChanged(content); return rv; } NS_IMETHODIMP nsScrollbarFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent, nsEventStatus* aEventStatus) { return NS_OK; } NS_IMETHODIMP nsScrollbarFrame::HandleMultiplePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent, nsEventStatus* aEventStatus, bool aControlHeld) { return NS_OK; } NS_IMETHODIMP nsScrollbarFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent, nsEventStatus* aEventStatus) { return NS_OK; } NS_IMETHODIMP nsScrollbarFrame::HandleRelease(nsPresContext* aPresContext, WidgetGUIEvent* aEvent, nsEventStatus* aEventStatus) { return NS_OK; } void nsScrollbarFrame::SetScrollbarMediatorContent(nsIContent* aMediator) { mScrollbarMediator = aMediator; } nsIScrollbarMediator* nsScrollbarFrame::GetScrollbarMediator() { if (!mScrollbarMediator) { return nullptr; } nsIFrame* f = mScrollbarMediator->GetPrimaryFrame(); nsIScrollableFrame* scrollFrame = do_QueryFrame(f); nsIScrollbarMediator* sbm; if (scrollFrame) { nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame(); sbm = do_QueryFrame(scrolledFrame); if (sbm) { return sbm; } } sbm = do_QueryFrame(f); if (f && !sbm) { f = f->PresShell()->GetRootScrollFrame(); if (f && f->GetContent() == mScrollbarMediator) { return do_QueryFrame(f); } } return sbm; } nsresult nsScrollbarFrame::GetXULMargin(nsMargin& aMargin) { aMargin.SizeTo(0, 0, 0, 0); const bool overlayScrollbars = PresContext()->UseOverlayScrollbars(); const bool horizontal = IsXULHorizontal(); bool didSetMargin = false; if (overlayScrollbars) { nsSize minSize; bool widthSet = false; bool heightSet = false; AddXULMinSize(this, minSize, widthSet, heightSet); if (horizontal) { if (heightSet) { aMargin.top = -minSize.height; didSetMargin = true; } } else { if (widthSet) { aMargin.left = -minSize.width; didSetMargin = true; } } } if (!didSetMargin) { DebugOnly rv = nsIFrame::GetXULMargin(aMargin); // TODO(emilio): Should probably not be fallible, it's not like anybody // cares about the return value anyway. MOZ_ASSERT(NS_SUCCEEDED(rv), "nsIFrame::GetXULMargin can't really fail"); } if (!horizontal) { nsIScrollbarMediator* scrollFrame = GetScrollbarMediator(); if (scrollFrame && !scrollFrame->IsScrollbarOnRight()) { std::swap(aMargin.left, aMargin.right); } } return NS_OK; } void nsScrollbarFrame::SetIncrementToLine(int32_t aDirection) { mSmoothScroll = true; mDirection = aDirection; mScrollUnit = ScrollUnit::LINES; // get the scrollbar's content node nsIContent* content = GetContent(); mIncrement = aDirection * nsSliderFrame::GetIncrement(content); } void nsScrollbarFrame::SetIncrementToPage(int32_t aDirection) { mSmoothScroll = true; mDirection = aDirection; mScrollUnit = ScrollUnit::PAGES; // get the scrollbar's content node nsIContent* content = GetContent(); mIncrement = aDirection * nsSliderFrame::GetPageIncrement(content); } void nsScrollbarFrame::SetIncrementToWhole(int32_t aDirection) { // Don't repeat or use smooth scrolling if scrolling to beginning or end // of a page. mSmoothScroll = false; mDirection = aDirection; mScrollUnit = ScrollUnit::WHOLE; // get the scrollbar's content node nsIContent* content = GetContent(); if (aDirection == -1) mIncrement = -nsSliderFrame::GetCurrentPosition(content); else mIncrement = nsSliderFrame::GetMaxPosition(content) - nsSliderFrame::GetCurrentPosition(content); } int32_t nsScrollbarFrame::MoveToNewPosition( ImplementsScrollByUnit aImplementsScrollByUnit) { if (aImplementsScrollByUnit == ImplementsScrollByUnit::Yes && StaticPrefs::apz_scrollbarbuttonrepeat_enabled()) { nsIScrollbarMediator* m = GetScrollbarMediator(); MOZ_ASSERT(m); // aImplementsScrollByUnit being Yes indicates the caller doesn't care // about the return value. // Note that this `MoveToNewPosition` is used for scrolling triggered by // repeating scrollbar button press, so we'd use an intended-direction // scroll snap flag. m->ScrollByUnit( this, mSmoothScroll ? ScrollMode::Smooth : ScrollMode::Instant, mDirection, mScrollUnit, ScrollSnapFlags::IntendedDirection); return 0; } // get the scrollbar's content node RefPtr content = GetContent()->AsElement(); // get the current pos int32_t curpos = nsSliderFrame::GetCurrentPosition(content); // get the max pos int32_t maxpos = nsSliderFrame::GetMaxPosition(content); // increment the given amount if (mIncrement) { curpos += mIncrement; } // make sure the current position is between the current and max positions if (curpos < 0) { curpos = 0; } else if (curpos > maxpos) { curpos = maxpos; } // set the current position of the slider. nsAutoString curposStr; curposStr.AppendInt(curpos); AutoWeakFrame weakFrame(this); if (mSmoothScroll) { content->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, u"true"_ns, false); } content->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curposStr, false); // notify the nsScrollbarFrame of the change AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos, dom::MutationEvent_Binding::MODIFICATION); if (!weakFrame.IsAlive()) { return curpos; } // notify all nsSliderFrames of the change for (const auto& childList : ChildLists()) { for (nsIFrame* f : childList.mList) { nsSliderFrame* sliderFrame = do_QueryFrame(f); if (sliderFrame) { sliderFrame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos, dom::MutationEvent_Binding::MODIFICATION); if (!weakFrame.IsAlive()) { return curpos; } } } } content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false); return curpos; } static already_AddRefed MakeScrollbarButton( dom::NodeInfo* aNodeInfo, bool aVertical, bool aBottom, bool aDown, AnonymousContentKey& aKey) { MOZ_ASSERT(aNodeInfo); MOZ_ASSERT( aNodeInfo->Equals(nsGkAtoms::scrollbarbutton, nullptr, kNameSpaceID_XUL)); static constexpr nsLiteralString kSbattrValues[2][2] = { { u"scrollbar-up-top"_ns, u"scrollbar-up-bottom"_ns, }, { u"scrollbar-down-top"_ns, u"scrollbar-down-bottom"_ns, }, }; static constexpr nsLiteralString kTypeValues[2] = { u"decrement"_ns, u"increment"_ns, }; aKey = AnonymousContentKey::Type_ScrollbarButton; if (aVertical) { aKey |= AnonymousContentKey::Flag_Vertical; } if (aBottom) { aKey |= AnonymousContentKey::Flag_ScrollbarButton_Bottom; } if (aDown) { aKey |= AnonymousContentKey::Flag_ScrollbarButton_Down; } RefPtr e; NS_TrustedNewXULElement(getter_AddRefs(e), do_AddRef(aNodeInfo)); e->SetAttr(kNameSpaceID_None, nsGkAtoms::sbattr, kSbattrValues[aDown][aBottom], false); e->SetAttr(kNameSpaceID_None, nsGkAtoms::type, kTypeValues[aDown], false); return e.forget(); } nsresult nsScrollbarFrame::CreateAnonymousContent( nsTArray& aElements) { nsNodeInfoManager* nodeInfoManager = mContent->NodeInfo()->NodeInfoManager(); Element* el(GetContent()->AsElement()); // If there are children already in the node, don't create any anonymous // content (this only apply to crashtests/369038-1.xhtml) if (el->HasChildren()) { return NS_OK; } nsAutoString orient; el->GetAttr(kNameSpaceID_None, nsGkAtoms::orient, orient); bool vertical = orient.EqualsLiteral("vertical"); RefPtr sbbNodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbarbutton, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE); bool createButtons = PresContext()->Theme()->ThemeSupportsScrollbarButtons(); if (createButtons) { AnonymousContentKey key; mUpTopButton = MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ false, /* aDown */ false, key); aElements.AppendElement(ContentInfo(mUpTopButton, key)); } if (createButtons) { AnonymousContentKey key; mDownTopButton = MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ false, /* aDown */ true, key); aElements.AppendElement(ContentInfo(mDownTopButton, key)); } { AnonymousContentKey key = AnonymousContentKey::Type_Slider; if (vertical) { key |= AnonymousContentKey::Flag_Vertical; } NS_TrustedNewXULElement( getter_AddRefs(mSlider), nodeInfoManager->GetNodeInfo(nsGkAtoms::slider, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE)); mSlider->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, orient, false); aElements.AppendElement(ContentInfo(mSlider, key)); NS_TrustedNewXULElement( getter_AddRefs(mThumb), nodeInfoManager->GetNodeInfo(nsGkAtoms::thumb, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE)); mThumb->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, orient, false); mSlider->AppendChildTo(mThumb, false, IgnoreErrors()); } if (createButtons) { AnonymousContentKey key; mUpBottomButton = MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ true, /* aDown */ false, key); aElements.AppendElement(ContentInfo(mUpBottomButton, key)); } if (createButtons) { AnonymousContentKey key; mDownBottomButton = MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ true, /* aDown */ true, key); aElements.AppendElement(ContentInfo(mDownBottomButton, key)); } // Don't cache styles if we are inside a 's attributes. if (GetContent()->GetParent() && GetContent()->GetParent()->IsHTMLElement(nsGkAtoms::select)) { for (auto& info : aElements) { info.mKey = AnonymousContentKey::None; } } UpdateChildrenAttributeValue(nsGkAtoms::curpos, false); UpdateChildrenAttributeValue(nsGkAtoms::maxpos, false); UpdateChildrenAttributeValue(nsGkAtoms::disabled, false); UpdateChildrenAttributeValue(nsGkAtoms::pageincrement, false); UpdateChildrenAttributeValue(nsGkAtoms::increment, false); return NS_OK; } void nsScrollbarFrame::UpdateChildrenAttributeValue(nsAtom* aAttribute, bool aNotify) { Element* el(GetContent()->AsElement()); nsAutoString value; el->GetAttr(kNameSpaceID_None, aAttribute, value); if (!el->HasAttr(kNameSpaceID_None, aAttribute)) { if (mUpTopButton) { mUpTopButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify); } if (mDownTopButton) { mDownTopButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify); } if (mSlider) { mSlider->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify); } if (mThumb && aAttribute == nsGkAtoms::disabled) { mThumb->UnsetAttr(kNameSpaceID_None, nsGkAtoms::collapsed, aNotify); } if (mUpBottomButton) { mUpBottomButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify); } if (mDownBottomButton) { mDownBottomButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify); } return; } if (aAttribute == nsGkAtoms::curpos || aAttribute == nsGkAtoms::maxpos) { if (mUpTopButton) { mUpTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify); } if (mDownTopButton) { mDownTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify); } if (mSlider) { mSlider->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify); } if (mUpBottomButton) { mUpBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify); } if (mDownBottomButton) { mDownBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify); } } else if (aAttribute == nsGkAtoms::disabled) { if (mUpTopButton) { mUpTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify); } if (mDownTopButton) { mDownTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify); } if (mSlider) { mSlider->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify); } // Set the value on "collapsed" attribute. if (mThumb) { mThumb->SetAttr(kNameSpaceID_None, nsGkAtoms::collapsed, value, aNotify); } if (mUpBottomButton) { mUpBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify); } if (mDownBottomButton) { mDownBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify); } } else if (aAttribute == nsGkAtoms::pageincrement || aAttribute == nsGkAtoms::increment) { if (mSlider) { mSlider->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify); } } } void nsScrollbarFrame::AppendAnonymousContentTo( nsTArray& aElements, uint32_t aFilter) { if (mUpTopButton) { aElements.AppendElement(mUpTopButton); } if (mDownTopButton) { aElements.AppendElement(mDownTopButton); } if (mSlider) { aElements.AppendElement(mSlider); } if (mUpBottomButton) { aElements.AppendElement(mUpBottomButton); } if (mDownBottomButton) { aElements.AppendElement(mDownBottomButton); } }