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