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 #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 "mozilla/LookAndFeel.h"
24 #include "mozilla/PresShell.h"
25 #include "mozilla/dom/MutationEventBinding.h"
26 
27 using namespace mozilla;
28 using mozilla::dom::Element;
29 
30 //
31 // NS_NewScrollbarFrame
32 //
33 // Creates a new scrollbar frame and returns it
34 //
NS_NewScrollbarFrame(PresShell * aPresShell,ComputedStyle * aStyle)35 nsIFrame* NS_NewScrollbarFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
36   return new (aPresShell)
37       nsScrollbarFrame(aStyle, aPresShell->GetPresContext());
38 }
39 
40 NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarFrame)
41 
NS_QUERYFRAME_HEAD(nsScrollbarFrame)42 NS_QUERYFRAME_HEAD(nsScrollbarFrame)
43   NS_QUERYFRAME_ENTRY(nsScrollbarFrame)
44   NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
45 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
46 
47 void nsScrollbarFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
48                             nsIFrame* aPrevInFlow) {
49   nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
50 
51   // We want to be a reflow root since we use reflows to move the
52   // slider.  Any reflow inside the scrollbar frame will be a reflow to
53   // move the slider and will thus not change anything outside of the
54   // scrollbar or change the size of the scrollbar frame.
55   AddStateBits(NS_FRAME_REFLOW_ROOT);
56 }
57 
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)58 void nsScrollbarFrame::DestroyFrom(nsIFrame* aDestructRoot,
59                                    PostDestroyData& aPostDestroyData) {
60   aPostDestroyData.AddAnonymousContent(mUpTopButton.forget());
61   aPostDestroyData.AddAnonymousContent(mDownTopButton.forget());
62   aPostDestroyData.AddAnonymousContent(mSlider.forget());
63   aPostDestroyData.AddAnonymousContent(mUpBottomButton.forget());
64   aPostDestroyData.AddAnonymousContent(mDownBottomButton.forget());
65   nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
66 }
67 
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)68 void nsScrollbarFrame::Reflow(nsPresContext* aPresContext,
69                               ReflowOutput& aDesiredSize,
70                               const ReflowInput& aReflowInput,
71                               nsReflowStatus& aStatus) {
72   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
73 
74   nsBoxFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
75 
76   // nsGfxScrollFrame may have told us to shrink to nothing. If so, make sure
77   // our desired size agrees.
78   if (aReflowInput.AvailableWidth() == 0) {
79     aDesiredSize.Width() = 0;
80   }
81   if (aReflowInput.AvailableHeight() == 0) {
82     aDesiredSize.Height() = 0;
83   }
84 }
85 
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)86 nsresult nsScrollbarFrame::AttributeChanged(int32_t aNameSpaceID,
87                                             nsAtom* aAttribute,
88                                             int32_t aModType) {
89   nsresult rv =
90       nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
91 
92   // Update value in our children
93   UpdateChildrenAttributeValue(aAttribute, true);
94 
95   // if the current position changes, notify any nsGfxScrollFrame
96   // parent we may have
97   if (aAttribute != nsGkAtoms::curpos) return rv;
98 
99   nsIScrollableFrame* scrollable = do_QueryFrame(GetParent());
100   if (!scrollable) return rv;
101 
102   nsCOMPtr<nsIContent> content(mContent);
103   scrollable->CurPosAttributeChanged(content);
104   return rv;
105 }
106 
107 NS_IMETHODIMP
HandlePress(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)108 nsScrollbarFrame::HandlePress(nsPresContext* aPresContext,
109                               WidgetGUIEvent* aEvent,
110                               nsEventStatus* aEventStatus) {
111   return NS_OK;
112 }
113 
114 NS_IMETHODIMP
HandleMultiplePress(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus,bool aControlHeld)115 nsScrollbarFrame::HandleMultiplePress(nsPresContext* aPresContext,
116                                       WidgetGUIEvent* aEvent,
117                                       nsEventStatus* aEventStatus,
118                                       bool aControlHeld) {
119   return NS_OK;
120 }
121 
122 NS_IMETHODIMP
HandleDrag(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)123 nsScrollbarFrame::HandleDrag(nsPresContext* aPresContext,
124                              WidgetGUIEvent* aEvent,
125                              nsEventStatus* aEventStatus) {
126   return NS_OK;
127 }
128 
129 NS_IMETHODIMP
HandleRelease(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)130 nsScrollbarFrame::HandleRelease(nsPresContext* aPresContext,
131                                 WidgetGUIEvent* aEvent,
132                                 nsEventStatus* aEventStatus) {
133   return NS_OK;
134 }
135 
SetScrollbarMediatorContent(nsIContent * aMediator)136 void nsScrollbarFrame::SetScrollbarMediatorContent(nsIContent* aMediator) {
137   mScrollbarMediator = aMediator;
138 }
139 
GetScrollbarMediator()140 nsIScrollbarMediator* nsScrollbarFrame::GetScrollbarMediator() {
141   if (!mScrollbarMediator) {
142     return nullptr;
143   }
144   nsIFrame* f = mScrollbarMediator->GetPrimaryFrame();
145   nsIScrollableFrame* scrollFrame = do_QueryFrame(f);
146   nsIScrollbarMediator* sbm;
147 
148   if (scrollFrame) {
149     nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
150     sbm = do_QueryFrame(scrolledFrame);
151     if (sbm) {
152       return sbm;
153     }
154   }
155   sbm = do_QueryFrame(f);
156   if (f && !sbm) {
157     f = f->PresShell()->GetRootScrollFrame();
158     if (f && f->GetContent() == mScrollbarMediator) {
159       return do_QueryFrame(f);
160     }
161   }
162   return sbm;
163 }
164 
GetXULMargin(nsMargin & aMargin)165 nsresult nsScrollbarFrame::GetXULMargin(nsMargin& aMargin) {
166   aMargin.SizeTo(0, 0, 0, 0);
167 
168   const bool overlayScrollbars =
169       !!LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars);
170 
171   const bool horizontal = IsXULHorizontal();
172   bool didSetMargin = false;
173 
174   if (overlayScrollbars) {
175     nsSize minSize;
176     bool widthSet = false;
177     bool heightSet = false;
178     AddXULMinSize(this, minSize, widthSet, heightSet);
179     if (horizontal) {
180       if (heightSet) {
181         aMargin.top = -minSize.height;
182         didSetMargin = true;
183       }
184     } else {
185       if (widthSet) {
186         aMargin.left = -minSize.width;
187         didSetMargin = true;
188       }
189     }
190   }
191 
192   if (!didSetMargin) {
193     DebugOnly<nsresult> rv = nsIFrame::GetXULMargin(aMargin);
194     // TODO(emilio): Should probably not be fallible, it's not like anybody
195     // cares about the return value anyway.
196     MOZ_ASSERT(NS_SUCCEEDED(rv), "nsIFrame::GetXULMargin can't really fail");
197   }
198 
199   if (!horizontal) {
200     nsIScrollbarMediator* scrollFrame = GetScrollbarMediator();
201     if (scrollFrame && !scrollFrame->IsScrollbarOnRight()) {
202       std::swap(aMargin.left, aMargin.right);
203     }
204   }
205 
206   return NS_OK;
207 }
208 
SetIncrementToLine(int32_t aDirection)209 void nsScrollbarFrame::SetIncrementToLine(int32_t aDirection) {
210   // get the scrollbar's content node
211   nsIContent* content = GetContent();
212   mSmoothScroll = true;
213   mIncrement = aDirection * nsSliderFrame::GetIncrement(content);
214 }
215 
SetIncrementToPage(int32_t aDirection)216 void nsScrollbarFrame::SetIncrementToPage(int32_t aDirection) {
217   // get the scrollbar's content node
218   nsIContent* content = GetContent();
219   mSmoothScroll = true;
220   mIncrement = aDirection * nsSliderFrame::GetPageIncrement(content);
221 }
222 
SetIncrementToWhole(int32_t aDirection)223 void nsScrollbarFrame::SetIncrementToWhole(int32_t aDirection) {
224   // get the scrollbar's content node
225   nsIContent* content = GetContent();
226   if (aDirection == -1)
227     mIncrement = -nsSliderFrame::GetCurrentPosition(content);
228   else
229     mIncrement = nsSliderFrame::GetMaxPosition(content) -
230                  nsSliderFrame::GetCurrentPosition(content);
231   // Don't repeat or use smooth scrolling if scrolling to beginning or end
232   // of a page.
233   mSmoothScroll = false;
234 }
235 
MoveToNewPosition()236 int32_t nsScrollbarFrame::MoveToNewPosition() {
237   // get the scrollbar's content node
238   RefPtr<Element> content = GetContent()->AsElement();
239 
240   // get the current pos
241   int32_t curpos = nsSliderFrame::GetCurrentPosition(content);
242 
243   // get the max pos
244   int32_t maxpos = nsSliderFrame::GetMaxPosition(content);
245 
246   // increment the given amount
247   if (mIncrement) {
248     curpos += mIncrement;
249   }
250 
251   // make sure the current position is between the current and max positions
252   if (curpos < 0) {
253     curpos = 0;
254   } else if (curpos > maxpos) {
255     curpos = maxpos;
256   }
257 
258   // set the current position of the slider.
259   nsAutoString curposStr;
260   curposStr.AppendInt(curpos);
261 
262   AutoWeakFrame weakFrame(this);
263   if (mSmoothScroll) {
264     content->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth,
265                      NS_LITERAL_STRING("true"), false);
266   }
267   content->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curposStr, false);
268   // notify the nsScrollbarFrame of the change
269   AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos,
270                    dom::MutationEvent_Binding::MODIFICATION);
271   if (!weakFrame.IsAlive()) {
272     return curpos;
273   }
274   // notify all nsSliderFrames of the change
275   for (const auto& childList : ChildLists()) {
276     for (nsIFrame* f : childList.mList) {
277       nsSliderFrame* sliderFrame = do_QueryFrame(f);
278       if (sliderFrame) {
279         sliderFrame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::curpos,
280                                       dom::MutationEvent_Binding::MODIFICATION);
281         if (!weakFrame.IsAlive()) {
282           return curpos;
283         }
284       }
285     }
286   }
287   content->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
288   return curpos;
289 }
290 
MakeScrollbarButton(dom::NodeInfo * aNodeInfo,bool aVertical,bool aBottom,bool aDown,AnonymousContentKey & aKey)291 static already_AddRefed<Element> MakeScrollbarButton(
292     dom::NodeInfo* aNodeInfo, bool aVertical, bool aBottom, bool aDown,
293     AnonymousContentKey& aKey) {
294   MOZ_ASSERT(aNodeInfo);
295   MOZ_ASSERT(
296       aNodeInfo->Equals(nsGkAtoms::scrollbarbutton, nullptr, kNameSpaceID_XUL));
297 
298   static constexpr nsLiteralString kSbattrValues[2][2] = {
299       {
300           NS_LITERAL_STRING("scrollbar-up-top"),
301           NS_LITERAL_STRING("scrollbar-up-bottom"),
302       },
303       {
304           NS_LITERAL_STRING("scrollbar-down-top"),
305           NS_LITERAL_STRING("scrollbar-down-bottom"),
306       },
307   };
308 
309   static constexpr nsLiteralString kTypeValues[2] = {
310       NS_LITERAL_STRING("decrement"),
311       NS_LITERAL_STRING("increment"),
312   };
313 
314   aKey = AnonymousContentKey::Type_ScrollbarButton;
315   if (aVertical) {
316     aKey |= AnonymousContentKey::Flag_Vertical;
317   }
318   if (aBottom) {
319     aKey |= AnonymousContentKey::Flag_ScrollbarButton_Bottom;
320   }
321   if (aDown) {
322     aKey |= AnonymousContentKey::Flag_ScrollbarButton_Down;
323   }
324 
325   RefPtr<Element> e;
326   NS_TrustedNewXULElement(getter_AddRefs(e), do_AddRef(aNodeInfo));
327   e->SetAttr(kNameSpaceID_None, nsGkAtoms::sbattr,
328              kSbattrValues[aDown][aBottom], false);
329   e->SetAttr(kNameSpaceID_None, nsGkAtoms::type, kTypeValues[aDown], false);
330   return e.forget();
331 }
332 
CreateAnonymousContent(nsTArray<ContentInfo> & aElements)333 nsresult nsScrollbarFrame::CreateAnonymousContent(
334     nsTArray<ContentInfo>& aElements) {
335   nsNodeInfoManager* nodeInfoManager = mContent->NodeInfo()->NodeInfoManager();
336 
337   Element* el(GetContent()->AsElement());
338 
339   // If there are children already in the node, don't create any anonymous
340   // content (this only apply to crashtests/369038-1.xhtml)
341   if (el->HasChildren()) {
342     return NS_OK;
343   }
344 
345   nsAutoString orient;
346   el->GetAttr(kNameSpaceID_None, nsGkAtoms::orient, orient);
347   bool vertical = orient.EqualsLiteral("vertical");
348 
349   RefPtr<dom::NodeInfo> sbbNodeInfo =
350       nodeInfoManager->GetNodeInfo(nsGkAtoms::scrollbarbutton, nullptr,
351                                    kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
352 
353   {
354     AnonymousContentKey key;
355     mUpTopButton =
356         MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ false,
357                             /* aDown */ false, key);
358     aElements.AppendElement(ContentInfo(mUpTopButton, key));
359   }
360 
361   {
362     AnonymousContentKey key;
363     mDownTopButton =
364         MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ false,
365                             /* aDown */ true, key);
366     aElements.AppendElement(ContentInfo(mDownTopButton, key));
367   }
368 
369   {
370     AnonymousContentKey key = AnonymousContentKey::Type_Slider;
371     if (vertical) {
372       key |= AnonymousContentKey::Flag_Vertical;
373     }
374 
375     NS_TrustedNewXULElement(
376         getter_AddRefs(mSlider),
377         nodeInfoManager->GetNodeInfo(nsGkAtoms::slider, nullptr,
378                                      kNameSpaceID_XUL, nsINode::ELEMENT_NODE));
379     mSlider->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, orient, false);
380     mSlider->SetAttr(kNameSpaceID_None, nsGkAtoms::flex, NS_LITERAL_STRING("1"),
381                      false);
382 
383     aElements.AppendElement(ContentInfo(mSlider, key));
384 
385     NS_TrustedNewXULElement(
386         getter_AddRefs(mThumb),
387         nodeInfoManager->GetNodeInfo(nsGkAtoms::thumb, nullptr,
388                                      kNameSpaceID_XUL, nsINode::ELEMENT_NODE));
389     mThumb->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, orient, false);
390     mSlider->AppendChildTo(mThumb, false);
391   }
392 
393   {
394     AnonymousContentKey key;
395     mUpBottomButton =
396         MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ true,
397                             /* aDown */ false, key);
398     aElements.AppendElement(ContentInfo(mUpBottomButton, key));
399   }
400 
401   {
402     AnonymousContentKey key;
403     mDownBottomButton =
404         MakeScrollbarButton(sbbNodeInfo, vertical, /* aBottom */ true,
405                             /* aDown */ true, key);
406     aElements.AppendElement(ContentInfo(mDownBottomButton, key));
407   }
408 
409   // Don't cache styles if we are inside a <select> element, since we have
410   // some UA style sheet rules that depend on the <select>'s attributes.
411   if (GetContent()->GetParent() &&
412       GetContent()->GetParent()->IsHTMLElement(nsGkAtoms::select)) {
413     for (auto& info : aElements) {
414       info.mKey = AnonymousContentKey::None;
415     }
416   }
417 
418   UpdateChildrenAttributeValue(nsGkAtoms::curpos, false);
419   UpdateChildrenAttributeValue(nsGkAtoms::maxpos, false);
420   UpdateChildrenAttributeValue(nsGkAtoms::disabled, false);
421   UpdateChildrenAttributeValue(nsGkAtoms::pageincrement, false);
422   UpdateChildrenAttributeValue(nsGkAtoms::increment, false);
423 
424   return NS_OK;
425 }
426 
UpdateChildrenAttributeValue(nsAtom * aAttribute,bool aNotify)427 void nsScrollbarFrame::UpdateChildrenAttributeValue(nsAtom* aAttribute,
428                                                     bool aNotify) {
429   Element* el(GetContent()->AsElement());
430 
431   nsAutoString value;
432   el->GetAttr(kNameSpaceID_None, aAttribute, value);
433 
434   if (!el->HasAttr(kNameSpaceID_None, aAttribute)) {
435     if (mUpTopButton) {
436       mUpTopButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
437     }
438     if (mDownTopButton) {
439       mDownTopButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
440     }
441     if (mSlider) {
442       mSlider->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
443     }
444     if (mThumb && aAttribute == nsGkAtoms::disabled) {
445       mThumb->UnsetAttr(kNameSpaceID_None, nsGkAtoms::collapsed, aNotify);
446     }
447     if (mUpBottomButton) {
448       mUpBottomButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
449     }
450     if (mDownBottomButton) {
451       mDownBottomButton->UnsetAttr(kNameSpaceID_None, aAttribute, aNotify);
452     }
453     return;
454   }
455 
456   if (aAttribute == nsGkAtoms::curpos || aAttribute == nsGkAtoms::maxpos) {
457     if (mUpTopButton) {
458       mUpTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
459     }
460     if (mDownTopButton) {
461       mDownTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
462     }
463     if (mSlider) {
464       mSlider->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
465     }
466     if (mUpBottomButton) {
467       mUpBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
468     }
469     if (mDownBottomButton) {
470       mDownBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
471     }
472   } else if (aAttribute == nsGkAtoms::disabled) {
473     if (mUpTopButton) {
474       mUpTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
475     }
476     if (mDownTopButton) {
477       mDownTopButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
478     }
479     if (mSlider) {
480       mSlider->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
481     }
482     // Set the value on "collapsed" attribute.
483     if (mThumb) {
484       mThumb->SetAttr(kNameSpaceID_None, nsGkAtoms::collapsed, value, aNotify);
485     }
486     if (mUpBottomButton) {
487       mUpBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
488     }
489     if (mDownBottomButton) {
490       mDownBottomButton->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
491     }
492   } else if (aAttribute == nsGkAtoms::pageincrement ||
493              aAttribute == nsGkAtoms::increment) {
494     if (mSlider) {
495       mSlider->SetAttr(kNameSpaceID_None, aAttribute, value, aNotify);
496     }
497   }
498 }
499 
AppendAnonymousContentTo(nsTArray<nsIContent * > & aElements,uint32_t aFilter)500 void nsScrollbarFrame::AppendAnonymousContentTo(
501     nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
502   if (mUpTopButton) {
503     aElements.AppendElement(mUpTopButton);
504   }
505 
506   if (mDownTopButton) {
507     aElements.AppendElement(mDownTopButton);
508   }
509 
510   if (mSlider) {
511     aElements.AppendElement(mSlider);
512   }
513 
514   if (mUpBottomButton) {
515     aElements.AppendElement(mUpBottomButton);
516   }
517 
518   if (mDownBottomButton) {
519     aElements.AppendElement(mDownBottomButton);
520   }
521 }
522