Bug 1856663 - Add more chunks for Android mochitest-plain. r=jmaher,taskgraph-reviewe...
[gecko.git] / layout / xul / nsScrollbarFrame.cpp
blob76304bf5027343c675ffe7e0f146f62666c74f95
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/. */
7 //
8 // Eric Vaughan
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) {
74 MarkInReflow();
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
78 // axis.
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);
94 }();
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);
117 } else {
118 availSize.BSize(wm) =
119 std::max(0, totalAvailSize.BSize(wm) - nextKidPos.B(wm) * factor);
123 ReflowInput kidRI(aPresContext, aReflowInput, kid, availSize);
124 if (isSlider) {
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));
132 } else {
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);
146 } else {
147 nextKidPos.B(wm) += kidDesiredSize.BSize(wm);
151 aDesiredSize.SetOverflowAreasToDesiredBounds();
154 nsresult nsScrollbarFrame::AttributeChanged(int32_t aNameSpaceID,
155 nsAtom* aAttribute,
156 int32_t aModType) {
157 nsresult rv =
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) {
166 return rv;
169 nsIScrollableFrame* scrollable = do_QueryFrame(GetParent());
170 if (!scrollable) {
171 return rv;
174 nsCOMPtr<nsIContent> content(mContent);
175 scrollable->CurPosAttributeChanged(content);
176 return rv;
179 NS_IMETHODIMP
180 nsScrollbarFrame::HandlePress(nsPresContext* aPresContext,
181 WidgetGUIEvent* aEvent,
182 nsEventStatus* aEventStatus) {
183 return NS_OK;
186 NS_IMETHODIMP
187 nsScrollbarFrame::HandleMultiplePress(nsPresContext* aPresContext,
188 WidgetGUIEvent* aEvent,
189 nsEventStatus* aEventStatus,
190 bool aControlHeld) {
191 return NS_OK;
194 NS_IMETHODIMP
195 nsScrollbarFrame::HandleDrag(nsPresContext* aPresContext,
196 WidgetGUIEvent* aEvent,
197 nsEventStatus* aEventStatus) {
198 return NS_OK;
201 NS_IMETHODIMP
202 nsScrollbarFrame::HandleRelease(nsPresContext* aPresContext,
203 WidgetGUIEvent* aEvent,
204 nsEventStatus* aEventStatus) {
205 return NS_OK;
208 void nsScrollbarFrame::SetScrollbarMediatorContent(nsIContent* aMediator) {
209 mScrollbarMediator = aMediator;
212 nsIScrollbarMediator* nsScrollbarFrame::GetScrollbarMediator() {
213 if (!mScrollbarMediator) {
214 return nullptr;
216 nsIFrame* f = mScrollbarMediator->GetPrimaryFrame();
217 nsIScrollableFrame* scrollFrame = do_QueryFrame(f);
218 nsIScrollbarMediator* sbm;
220 if (scrollFrame) {
221 nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
222 sbm = do_QueryFrame(scrolledFrame);
223 if (sbm) {
224 return sbm;
227 sbm = do_QueryFrame(f);
228 if (f && !sbm) {
229 f = f->PresShell()->GetRootScrollFrame();
230 if (f && f->GetContent() == mScrollbarMediator) {
231 return do_QueryFrame(f);
234 return sbm;
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)
254 ->StyleUIReset()
255 ->ScrollbarWidth();
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
289 // of a page.
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);
298 else
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();
308 MOZ_ASSERT(m);
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
313 // scroll snap flag.
314 m->ScrollByUnit(
315 this, mSmoothScroll ? ScrollMode::Smooth : ScrollMode::Instant,
316 mDirection, mScrollUnit, ScrollSnapFlags::IntendedDirection);
317 return 0;
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);
326 // get the max pos
327 int32_t maxpos = nsSliderFrame::GetMaxPosition(content);
329 // increment the given amount
330 if (mIncrement) {
331 curpos += mIncrement;
334 // make sure the current position is between the current and max positions
335 if (curpos < 0) {
336 curpos = 0;
337 } else if (curpos > maxpos) {
338 curpos = maxpos;
341 // set the current position of the slider.
342 nsAutoString curposStr;
343 curposStr.AppendInt(curpos);
345 AutoWeakFrame weakFrame(this);
346 if (mSmoothScroll) {
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()) {
354 return curpos;
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);
360 if (sliderFrame) {
361 sliderFrame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos,
362 dom::MutationEvent_Binding::MODIFICATION);
363 if (!weakFrame.IsAlive()) {
364 return curpos;
369 content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
370 return curpos;
373 static already_AddRefed<Element> MakeScrollbarButton(
374 dom::NodeInfo* aNodeInfo, bool aVertical, bool aBottom, bool aDown,
375 AnonymousContentKey& aKey) {
376 MOZ_ASSERT(aNodeInfo);
377 MOZ_ASSERT(
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] = {
392 u"decrement"_ns,
393 u"increment"_ns,
396 aKey = AnonymousContentKey::Type_ScrollbarButton;
397 if (aVertical) {
398 aKey |= AnonymousContentKey::Flag_Vertical;
400 if (aBottom) {
401 aKey |= AnonymousContentKey::Flag_ScrollbarButton_Bottom;
403 if (aDown) {
404 aKey |= AnonymousContentKey::Flag_ScrollbarButton_Down;
407 RefPtr<Element> e;
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);
412 return e.forget();
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()) {
423 return NS_OK;
426 nsAutoString orient;
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();
436 if (createButtons) {
437 AnonymousContentKey key;
438 mUpTopButton =
439 MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ false,
440 /* aDown */ false, key);
441 aElements.AppendElement(ContentInfo(mUpTopButton, key));
444 if (createButtons) {
445 AnonymousContentKey key;
446 mDownTopButton =
447 MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ false,
448 /* aDown */ true, key);
449 aElements.AppendElement(ContentInfo(mDownTopButton, key));
453 AnonymousContentKey key = AnonymousContentKey::Type_Slider;
454 if (vertical) {
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());
474 if (createButtons) {
475 AnonymousContentKey key;
476 mUpBottomButton =
477 MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ true,
478 /* aDown */ false, key);
479 aElements.AppendElement(ContentInfo(mUpBottomButton, key));
482 if (createButtons) {
483 AnonymousContentKey key;
484 mDownBottomButton =
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);
505 return NS_OK;
508 void nsScrollbarFrame::UpdateChildrenAttributeValue(nsAtom* aAttribute,
509 bool aNotify) {
510 Element* el = GetContent()->AsElement();
512 nsAutoString value;
513 el->GetAttr(aAttribute, value);
515 if (!el->HasAttr(aAttribute)) {
516 if (mUpTopButton) {
517 mUpTopButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
519 if (mDownTopButton) {
520 mDownTopButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
522 if (mSlider) {
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);
531 return;
534 if (aAttribute == nsGkAtoms::curpos || aAttribute == nsGkAtoms::maxpos) {
535 if (mUpTopButton) {
536 mUpTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
538 if (mDownTopButton) {
539 mDownTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
541 if (mSlider) {
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) {
551 if (mUpTopButton) {
552 mUpTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
554 if (mDownTopButton) {
555 mDownTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
557 if (mSlider) {
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) {
568 if (mSlider) {
569 mSlider->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
574 void nsScrollbarFrame::AppendAnonymousContentTo(
575 nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
576 if (mUpTopButton) {
577 aElements.AppendElement(mUpTopButton);
580 if (mDownTopButton) {
581 aElements.AppendElement(mDownTopButton);
584 if (mSlider) {
585 aElements.AppendElement(mSlider);
588 if (mUpBottomButton) {
589 aElements.AppendElement(mUpBottomButton);
592 if (mDownBottomButton) {
593 aElements.AppendElement(mDownBottomButton);