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/. */
9 // Netscape Communications
11 // See documentation in associated header file
14 #include "nsScrollbarFrame.h"
15 #include "nsSliderFrame.h"
16 #include "nsScrollbarButtonFrame.h"
17 #include "nsContentCreatorFunctions.h"
18 #include "nsGkAtoms.h"
19 #include "nsIScrollableFrame.h"
20 #include "nsIScrollbarMediator.h"
21 #include "nsStyleConsts.h"
22 #include "nsIContent.h"
23 #include "nsLayoutUtils.h"
24 #include "mozilla/LookAndFeel.h"
25 #include "mozilla/PresShell.h"
26 #include "mozilla/dom/Element.h"
27 #include "mozilla/dom/MutationEventBinding.h"
28 #include "mozilla/StaticPrefs_apz.h"
30 using namespace mozilla
;
31 using mozilla::dom::Element
;
34 // NS_NewScrollbarFrame
36 // Creates a new scrollbar frame and returns it
38 nsIFrame
* NS_NewScrollbarFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
39 return new (aPresShell
)
40 nsScrollbarFrame(aStyle
, aPresShell
->GetPresContext());
43 NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarFrame
)
45 NS_QUERYFRAME_HEAD(nsScrollbarFrame
)
46 NS_QUERYFRAME_ENTRY(nsScrollbarFrame
)
47 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator
)
48 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
50 void nsScrollbarFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
51 nsIFrame
* aPrevInFlow
) {
52 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
54 // We want to be a reflow root since we use reflows to move the
55 // slider. Any reflow inside the scrollbar frame will be a reflow to
56 // move the slider and will thus not change anything outside of the
57 // scrollbar or change the size of the scrollbar frame.
58 AddStateBits(NS_FRAME_REFLOW_ROOT
);
61 void nsScrollbarFrame::Destroy(DestroyContext
& aContext
) {
62 aContext
.AddAnonymousContent(mUpTopButton
.forget());
63 aContext
.AddAnonymousContent(mDownTopButton
.forget());
64 aContext
.AddAnonymousContent(mSlider
.forget());
65 aContext
.AddAnonymousContent(mUpBottomButton
.forget());
66 aContext
.AddAnonymousContent(mDownBottomButton
.forget());
67 nsContainerFrame::Destroy(aContext
);
70 void nsScrollbarFrame::Reflow(nsPresContext
* aPresContext
,
71 ReflowOutput
& aDesiredSize
,
72 const ReflowInput
& aReflowInput
,
73 nsReflowStatus
& aStatus
) {
75 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
77 // We always take all the space we're given, and our track size in the other
79 const bool horizontal
= IsHorizontal();
80 const auto wm
= GetWritingMode();
81 const auto minSize
= aReflowInput
.ComputedMinSize();
83 aDesiredSize
.ISize(wm
) = aReflowInput
.ComputedISize();
84 aDesiredSize
.BSize(wm
) = [&] {
85 if (aReflowInput
.ComputedBSize() != NS_UNCONSTRAINEDSIZE
) {
86 return aReflowInput
.ComputedBSize();
88 // We don't want to change our size during incremental reflow, see the
89 // reflow root comment in init.
90 if (!aReflowInput
.mParentReflowInput
) {
91 return GetLogicalSize(wm
).BSize(wm
);
93 return minSize
.BSize(wm
);
96 const nsSize containerSize
= aDesiredSize
.PhysicalSize();
97 const LogicalSize totalAvailSize
= aDesiredSize
.Size(wm
);
98 LogicalPoint
nextKidPos(wm
);
100 MOZ_ASSERT(!wm
.IsVertical());
101 const bool movesInInlineDirection
= horizontal
;
103 // Layout our kids left to right / top to bottom.
104 for (nsIFrame
* kid
: mFrames
) {
105 MOZ_ASSERT(!kid
->GetWritingMode().IsOrthogonalTo(wm
),
106 "We don't expect orthogonal scrollbar parts");
107 const bool isSlider
= kid
->GetContent() == mSlider
;
108 LogicalSize availSize
= totalAvailSize
;
110 // Assume we'll consume the same size before and after the slider. This is
111 // not a technically correct assumption if we have weird scrollbar button
112 // setups, but those will be going away, see bug 1824254.
113 const int32_t factor
= isSlider
? 2 : 1;
114 if (movesInInlineDirection
) {
115 availSize
.ISize(wm
) =
116 std::max(0, totalAvailSize
.ISize(wm
) - nextKidPos
.I(wm
) * factor
);
118 availSize
.BSize(wm
) =
119 std::max(0, totalAvailSize
.BSize(wm
) - nextKidPos
.B(wm
) * factor
);
123 ReflowInput
kidRI(aPresContext
, aReflowInput
, kid
, availSize
);
125 // We want for the slider to take all the remaining available space.
126 kidRI
.SetComputedISize(availSize
.ISize(wm
));
127 kidRI
.SetComputedBSize(availSize
.BSize(wm
));
128 } else if (movesInInlineDirection
) {
129 // Otherwise we want all the space in the axis we're not advancing in, and
130 // the default / minimum size on the other axis.
131 kidRI
.SetComputedBSize(availSize
.BSize(wm
));
133 kidRI
.SetComputedISize(availSize
.ISize(wm
));
136 ReflowOutput
kidDesiredSize(wm
);
137 nsReflowStatus status
;
138 const auto flags
= ReflowChildFlags::Default
;
139 ReflowChild(kid
, aPresContext
, kidDesiredSize
, kidRI
, wm
, nextKidPos
,
140 containerSize
, flags
, status
);
141 // We haven't seen the slider yet, we can advance
142 FinishReflowChild(kid
, aPresContext
, kidDesiredSize
, &kidRI
, wm
, nextKidPos
,
143 containerSize
, flags
);
144 if (movesInInlineDirection
) {
145 nextKidPos
.I(wm
) += kidDesiredSize
.ISize(wm
);
147 nextKidPos
.B(wm
) += kidDesiredSize
.BSize(wm
);
151 aDesiredSize
.SetOverflowAreasToDesiredBounds();
154 nsresult
nsScrollbarFrame::AttributeChanged(int32_t aNameSpaceID
,
158 nsContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
160 // Update value in our children
161 UpdateChildrenAttributeValue(aAttribute
, true);
163 // if the current position changes, notify any nsGfxScrollFrame
164 // parent we may have
165 if (aAttribute
!= nsGkAtoms::curpos
) {
169 nsIScrollableFrame
* scrollable
= do_QueryFrame(GetParent());
174 nsCOMPtr
<nsIContent
> content(mContent
);
175 scrollable
->CurPosAttributeChanged(content
);
180 nsScrollbarFrame::HandlePress(nsPresContext
* aPresContext
,
181 WidgetGUIEvent
* aEvent
,
182 nsEventStatus
* aEventStatus
) {
187 nsScrollbarFrame::HandleMultiplePress(nsPresContext
* aPresContext
,
188 WidgetGUIEvent
* aEvent
,
189 nsEventStatus
* aEventStatus
,
195 nsScrollbarFrame::HandleDrag(nsPresContext
* aPresContext
,
196 WidgetGUIEvent
* aEvent
,
197 nsEventStatus
* aEventStatus
) {
202 nsScrollbarFrame::HandleRelease(nsPresContext
* aPresContext
,
203 WidgetGUIEvent
* aEvent
,
204 nsEventStatus
* aEventStatus
) {
208 void nsScrollbarFrame::SetScrollbarMediatorContent(nsIContent
* aMediator
) {
209 mScrollbarMediator
= aMediator
;
212 nsIScrollbarMediator
* nsScrollbarFrame::GetScrollbarMediator() {
213 if (!mScrollbarMediator
) {
216 nsIFrame
* f
= mScrollbarMediator
->GetPrimaryFrame();
217 nsIScrollableFrame
* scrollFrame
= do_QueryFrame(f
);
218 nsIScrollbarMediator
* sbm
;
221 nsIFrame
* scrolledFrame
= scrollFrame
->GetScrolledFrame();
222 sbm
= do_QueryFrame(scrolledFrame
);
227 sbm
= do_QueryFrame(f
);
229 f
= f
->PresShell()->GetRootScrollFrame();
230 if (f
&& f
->GetContent() == mScrollbarMediator
) {
231 return do_QueryFrame(f
);
237 bool nsScrollbarFrame::IsHorizontal() const {
238 auto appearance
= StyleDisplay()->EffectiveAppearance();
239 MOZ_ASSERT(appearance
== StyleAppearance::ScrollbarHorizontal
||
240 appearance
== StyleAppearance::ScrollbarVertical
);
241 return appearance
== StyleAppearance::ScrollbarHorizontal
;
244 nsSize
nsScrollbarFrame::ScrollbarMinSize() const {
245 nsPresContext
* pc
= PresContext();
246 const LayoutDeviceIntSize widget
=
247 pc
->Theme()->GetMinimumWidgetSize(pc
, const_cast<nsScrollbarFrame
*>(this),
248 StyleDisplay()->EffectiveAppearance());
249 return LayoutDeviceIntSize::ToAppUnits(widget
, pc
->AppUnitsPerDevPixel());
252 StyleScrollbarWidth
nsScrollbarFrame::ScrollbarWidth() const {
253 return nsLayoutUtils::StyleForScrollbar(this)
258 nscoord
nsScrollbarFrame::ScrollbarTrackSize() const {
259 nsPresContext
* pc
= PresContext();
260 auto overlay
= pc
->UseOverlayScrollbars() ? nsITheme::Overlay::Yes
261 : nsITheme::Overlay::No
;
262 return LayoutDevicePixel::ToAppUnits(
263 pc
->Theme()->GetScrollbarSize(pc
, ScrollbarWidth(), overlay
),
264 pc
->AppUnitsPerDevPixel());
267 void nsScrollbarFrame::SetIncrementToLine(int32_t aDirection
) {
268 mSmoothScroll
= true;
269 mDirection
= aDirection
;
270 mScrollUnit
= ScrollUnit::LINES
;
272 // get the scrollbar's content node
273 nsIContent
* content
= GetContent();
274 mIncrement
= aDirection
* nsSliderFrame::GetIncrement(content
);
277 void nsScrollbarFrame::SetIncrementToPage(int32_t aDirection
) {
278 mSmoothScroll
= true;
279 mDirection
= aDirection
;
280 mScrollUnit
= ScrollUnit::PAGES
;
282 // get the scrollbar's content node
283 nsIContent
* content
= GetContent();
284 mIncrement
= aDirection
* nsSliderFrame::GetPageIncrement(content
);
287 void nsScrollbarFrame::SetIncrementToWhole(int32_t aDirection
) {
288 // Don't repeat or use smooth scrolling if scrolling to beginning or end
290 mSmoothScroll
= false;
291 mDirection
= aDirection
;
292 mScrollUnit
= ScrollUnit::WHOLE
;
294 // get the scrollbar's content node
295 nsIContent
* content
= GetContent();
296 if (aDirection
== -1)
297 mIncrement
= -nsSliderFrame::GetCurrentPosition(content
);
299 mIncrement
= nsSliderFrame::GetMaxPosition(content
) -
300 nsSliderFrame::GetCurrentPosition(content
);
303 int32_t nsScrollbarFrame::MoveToNewPosition(
304 ImplementsScrollByUnit aImplementsScrollByUnit
) {
305 if (aImplementsScrollByUnit
== ImplementsScrollByUnit::Yes
&&
306 StaticPrefs::apz_scrollbarbuttonrepeat_enabled()) {
307 nsIScrollbarMediator
* m
= GetScrollbarMediator();
309 // aImplementsScrollByUnit being Yes indicates the caller doesn't care
310 // about the return value.
311 // Note that this `MoveToNewPosition` is used for scrolling triggered by
312 // repeating scrollbar button press, so we'd use an intended-direction
315 this, mSmoothScroll
? ScrollMode::Smooth
: ScrollMode::Instant
,
316 mDirection
, mScrollUnit
, ScrollSnapFlags::IntendedDirection
);
320 // get the scrollbar's content node
321 RefPtr
<Element
> content
= GetContent()->AsElement();
323 // get the current pos
324 int32_t curpos
= nsSliderFrame::GetCurrentPosition(content
);
327 int32_t maxpos
= nsSliderFrame::GetMaxPosition(content
);
329 // increment the given amount
331 curpos
+= mIncrement
;
334 // make sure the current position is between the current and max positions
337 } else if (curpos
> maxpos
) {
341 // set the current position of the slider.
342 nsAutoString curposStr
;
343 curposStr
.AppendInt(curpos
);
345 AutoWeakFrame
weakFrame(this);
347 content
->SetAttr(kNameSpaceID_None
, nsGkAtoms::smooth
, u
"true"_ns
, false);
349 content
->SetAttr(kNameSpaceID_None
, nsGkAtoms::curpos
, curposStr
, false);
350 // notify the nsScrollbarFrame of the change
351 AttributeChanged(kNameSpaceID_None
, nsGkAtoms::curpos
,
352 dom::MutationEvent_Binding::MODIFICATION
);
353 if (!weakFrame
.IsAlive()) {
356 // notify all nsSliderFrames of the change
357 for (const auto& childList
: ChildLists()) {
358 for (nsIFrame
* f
: childList
.mList
) {
359 nsSliderFrame
* sliderFrame
= do_QueryFrame(f
);
361 sliderFrame
->AttributeChanged(kNameSpaceID_None
, nsGkAtoms::curpos
,
362 dom::MutationEvent_Binding::MODIFICATION
);
363 if (!weakFrame
.IsAlive()) {
369 content
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::smooth
, false);
373 static already_AddRefed
<Element
> MakeScrollbarButton(
374 dom::NodeInfo
* aNodeInfo
, bool aVertical
, bool aBottom
, bool aDown
,
375 AnonymousContentKey
& aKey
) {
376 MOZ_ASSERT(aNodeInfo
);
378 aNodeInfo
->Equals(nsGkAtoms::scrollbarbutton
, nullptr, kNameSpaceID_XUL
));
380 static constexpr nsLiteralString kSbattrValues
[2][2] = {
382 u
"scrollbar-up-top"_ns
,
383 u
"scrollbar-up-bottom"_ns
,
386 u
"scrollbar-down-top"_ns
,
387 u
"scrollbar-down-bottom"_ns
,
391 static constexpr nsLiteralString kTypeValues
[2] = {
396 aKey
= AnonymousContentKey::Type_ScrollbarButton
;
398 aKey
|= AnonymousContentKey::Flag_Vertical
;
401 aKey
|= AnonymousContentKey::Flag_ScrollbarButton_Bottom
;
404 aKey
|= AnonymousContentKey::Flag_ScrollbarButton_Down
;
408 NS_TrustedNewXULElement(getter_AddRefs(e
), do_AddRef(aNodeInfo
));
409 e
->SetAttr(kNameSpaceID_None
, nsGkAtoms::sbattr
,
410 kSbattrValues
[aDown
][aBottom
], false);
411 e
->SetAttr(kNameSpaceID_None
, nsGkAtoms::type
, kTypeValues
[aDown
], false);
415 nsresult
nsScrollbarFrame::CreateAnonymousContent(
416 nsTArray
<ContentInfo
>& aElements
) {
417 nsNodeInfoManager
* nodeInfoManager
= mContent
->NodeInfo()->NodeInfoManager();
418 Element
* el
= GetContent()->AsElement();
420 // If there are children already in the node, don't create any anonymous
421 // content (this only apply to crashtests/369038-1.xhtml)
422 if (el
->HasChildren()) {
427 el
->GetAttr(nsGkAtoms::orient
, orient
);
428 bool vertical
= orient
.EqualsLiteral("vertical");
430 RefPtr
<dom::NodeInfo
> sbbNodeInfo
=
431 nodeInfoManager
->GetNodeInfo(nsGkAtoms::scrollbarbutton
, nullptr,
432 kNameSpaceID_XUL
, nsINode::ELEMENT_NODE
);
434 bool createButtons
= PresContext()->Theme()->ThemeSupportsScrollbarButtons();
437 AnonymousContentKey key
;
439 MakeScrollbarButton(sbbNodeInfo
, vertical
, /* aBottom */ false,
440 /* aDown */ false, key
);
441 aElements
.AppendElement(ContentInfo(mUpTopButton
, key
));
445 AnonymousContentKey key
;
447 MakeScrollbarButton(sbbNodeInfo
, vertical
, /* aBottom */ false,
448 /* aDown */ true, key
);
449 aElements
.AppendElement(ContentInfo(mDownTopButton
, key
));
453 AnonymousContentKey key
= AnonymousContentKey::Type_Slider
;
455 key
|= AnonymousContentKey::Flag_Vertical
;
458 NS_TrustedNewXULElement(
459 getter_AddRefs(mSlider
),
460 nodeInfoManager
->GetNodeInfo(nsGkAtoms::slider
, nullptr,
461 kNameSpaceID_XUL
, nsINode::ELEMENT_NODE
));
462 mSlider
->SetAttr(kNameSpaceID_None
, nsGkAtoms::orient
, orient
, false);
464 aElements
.AppendElement(ContentInfo(mSlider
, key
));
466 NS_TrustedNewXULElement(
467 getter_AddRefs(mThumb
),
468 nodeInfoManager
->GetNodeInfo(nsGkAtoms::thumb
, nullptr,
469 kNameSpaceID_XUL
, nsINode::ELEMENT_NODE
));
470 mThumb
->SetAttr(kNameSpaceID_None
, nsGkAtoms::orient
, orient
, false);
471 mSlider
->AppendChildTo(mThumb
, false, IgnoreErrors());
475 AnonymousContentKey key
;
477 MakeScrollbarButton(sbbNodeInfo
, vertical
, /* aBottom */ true,
478 /* aDown */ false, key
);
479 aElements
.AppendElement(ContentInfo(mUpBottomButton
, key
));
483 AnonymousContentKey key
;
485 MakeScrollbarButton(sbbNodeInfo
, vertical
, /* aBottom */ true,
486 /* aDown */ true, key
);
487 aElements
.AppendElement(ContentInfo(mDownBottomButton
, key
));
490 // Don't cache styles if we are inside a <select> element, since we have
491 // some UA style sheet rules that depend on the <select>'s attributes.
492 if (GetContent()->GetParent() &&
493 GetContent()->GetParent()->IsHTMLElement(nsGkAtoms::select
)) {
494 for (auto& info
: aElements
) {
495 info
.mKey
= AnonymousContentKey::None
;
499 UpdateChildrenAttributeValue(nsGkAtoms::curpos
, false);
500 UpdateChildrenAttributeValue(nsGkAtoms::maxpos
, false);
501 UpdateChildrenAttributeValue(nsGkAtoms::disabled
, false);
502 UpdateChildrenAttributeValue(nsGkAtoms::pageincrement
, false);
503 UpdateChildrenAttributeValue(nsGkAtoms::increment
, false);
508 void nsScrollbarFrame::UpdateChildrenAttributeValue(nsAtom
* aAttribute
,
510 Element
* el
= GetContent()->AsElement();
513 el
->GetAttr(aAttribute
, value
);
515 if (!el
->HasAttr(aAttribute
)) {
517 mUpTopButton
->UnsetAttr(kNameSpaceID_None
, aAttribute
, aNotify
);
519 if (mDownTopButton
) {
520 mDownTopButton
->UnsetAttr(kNameSpaceID_None
, aAttribute
, aNotify
);
523 mSlider
->UnsetAttr(kNameSpaceID_None
, aAttribute
, aNotify
);
525 if (mUpBottomButton
) {
526 mUpBottomButton
->UnsetAttr(kNameSpaceID_None
, aAttribute
, aNotify
);
528 if (mDownBottomButton
) {
529 mDownBottomButton
->UnsetAttr(kNameSpaceID_None
, aAttribute
, aNotify
);
534 if (aAttribute
== nsGkAtoms::curpos
|| aAttribute
== nsGkAtoms::maxpos
) {
536 mUpTopButton
->SetAttr(kNameSpaceID_None
, aAttribute
, value
, aNotify
);
538 if (mDownTopButton
) {
539 mDownTopButton
->SetAttr(kNameSpaceID_None
, aAttribute
, value
, aNotify
);
542 mSlider
->SetAttr(kNameSpaceID_None
, aAttribute
, value
, aNotify
);
544 if (mUpBottomButton
) {
545 mUpBottomButton
->SetAttr(kNameSpaceID_None
, aAttribute
, value
, aNotify
);
547 if (mDownBottomButton
) {
548 mDownBottomButton
->SetAttr(kNameSpaceID_None
, aAttribute
, value
, aNotify
);
550 } else if (aAttribute
== nsGkAtoms::disabled
) {
552 mUpTopButton
->SetAttr(kNameSpaceID_None
, aAttribute
, value
, aNotify
);
554 if (mDownTopButton
) {
555 mDownTopButton
->SetAttr(kNameSpaceID_None
, aAttribute
, value
, aNotify
);
558 mSlider
->SetAttr(kNameSpaceID_None
, aAttribute
, value
, aNotify
);
560 if (mUpBottomButton
) {
561 mUpBottomButton
->SetAttr(kNameSpaceID_None
, aAttribute
, value
, aNotify
);
563 if (mDownBottomButton
) {
564 mDownBottomButton
->SetAttr(kNameSpaceID_None
, aAttribute
, value
, aNotify
);
566 } else if (aAttribute
== nsGkAtoms::pageincrement
||
567 aAttribute
== nsGkAtoms::increment
) {
569 mSlider
->SetAttr(kNameSpaceID_None
, aAttribute
, value
, aNotify
);
574 void nsScrollbarFrame::AppendAnonymousContentTo(
575 nsTArray
<nsIContent
*>& aElements
, uint32_t aFilter
) {
577 aElements
.AppendElement(mUpTopButton
);
580 if (mDownTopButton
) {
581 aElements
.AppendElement(mDownTopButton
);
585 aElements
.AppendElement(mSlider
);
588 if (mUpBottomButton
) {
589 aElements
.AppendElement(mUpBottomButton
);
592 if (mDownBottomButton
) {
593 aElements
.AppendElement(mDownBottomButton
);