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 "nsScrollbarButtonFrame.h"
15 #include "nsPresContext.h"
16 #include "nsIContent.h"
17 #include "nsCOMPtr.h"
18 #include "nsNameSpaceManager.h"
19 #include "nsGkAtoms.h"
20 #include "nsLayoutUtils.h"
21 #include "nsSliderFrame.h"
22 #include "nsScrollbarFrame.h"
23 #include "nsIScrollbarMediator.h"
24 #include "nsRepeatService.h"
25 #include "mozilla/LookAndFeel.h"
26 #include "mozilla/MouseEvents.h"
27 #include "mozilla/PresShell.h"
28 #include "mozilla/Telemetry.h"
29 
30 using namespace mozilla;
31 
32 //
33 // NS_NewToolbarFrame
34 //
35 // Creates a new Toolbar frame and returns it
36 //
NS_NewScrollbarButtonFrame(PresShell * aPresShell,ComputedStyle * aStyle)37 nsIFrame* NS_NewScrollbarButtonFrame(PresShell* aPresShell,
38                                      ComputedStyle* aStyle) {
39   return new (aPresShell)
40       nsScrollbarButtonFrame(aStyle, aPresShell->GetPresContext());
41 }
42 
NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarButtonFrame)43 NS_IMPL_FRAMEARENA_HELPERS(nsScrollbarButtonFrame)
44 
45 nsresult nsScrollbarButtonFrame::HandleEvent(nsPresContext* aPresContext,
46                                              WidgetGUIEvent* aEvent,
47                                              nsEventStatus* aEventStatus) {
48   NS_ENSURE_ARG_POINTER(aEventStatus);
49 
50   // If a web page calls event.preventDefault() we still want to
51   // scroll when scroll arrow is clicked. See bug 511075.
52   if (!mContent->IsInNativeAnonymousSubtree() &&
53       nsEventStatus_eConsumeNoDefault == *aEventStatus) {
54     return NS_OK;
55   }
56 
57   switch (aEvent->mMessage) {
58     case eMouseDown:
59       mCursorOnThis = true;
60       // if we didn't handle the press ourselves, pass it on to the superclass
61       if (HandleButtonPress(aPresContext, aEvent, aEventStatus)) {
62         return NS_OK;
63       }
64       break;
65     case eMouseUp:
66       HandleRelease(aPresContext, aEvent, aEventStatus);
67       break;
68     case eMouseOut:
69       mCursorOnThis = false;
70       break;
71     case eMouseMove: {
72       nsPoint cursor = nsLayoutUtils::GetEventCoordinatesRelativeTo(
73           aEvent, RelativeTo{this});
74       nsRect frameRect(nsPoint(0, 0), GetSize());
75       mCursorOnThis = frameRect.Contains(cursor);
76       break;
77     }
78     default:
79       break;
80   }
81 
82   return nsButtonBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
83 }
84 
HandleButtonPress(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)85 bool nsScrollbarButtonFrame::HandleButtonPress(nsPresContext* aPresContext,
86                                                WidgetGUIEvent* aEvent,
87                                                nsEventStatus* aEventStatus) {
88   // Get the desired action for the scrollbar button.
89   LookAndFeel::IntID tmpAction;
90   uint16_t button = aEvent->AsMouseEvent()->mButton;
91   if (button == MouseButton::ePrimary) {
92     tmpAction = LookAndFeel::IntID::ScrollButtonLeftMouseButtonAction;
93   } else if (button == MouseButton::eMiddle) {
94     tmpAction = LookAndFeel::IntID::ScrollButtonMiddleMouseButtonAction;
95   } else if (button == MouseButton::eSecondary) {
96     tmpAction = LookAndFeel::IntID::ScrollButtonRightMouseButtonAction;
97   } else {
98     return false;
99   }
100 
101   // Get the button action metric from the pres. shell.
102   int32_t pressedButtonAction;
103   if (NS_FAILED(LookAndFeel::GetInt(tmpAction, &pressedButtonAction))) {
104     return false;
105   }
106 
107   // get the scrollbar control
108   nsIFrame* scrollbar;
109   GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
110 
111   if (scrollbar == nullptr) return false;
112 
113   static dom::Element::AttrValuesArray strings[] = {
114       nsGkAtoms::increment, nsGkAtoms::decrement, nullptr};
115   int32_t index = mContent->AsElement()->FindAttrValueIn(
116       kNameSpaceID_None, nsGkAtoms::type, strings, eCaseMatters);
117   int32_t direction;
118   if (index == 0)
119     direction = 1;
120   else if (index == 1)
121     direction = -1;
122   else
123     return false;
124 
125   bool repeat = pressedButtonAction != 2;
126   // set this attribute so we can style it later
127   AutoWeakFrame weakFrame(this);
128   mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::active,
129                                  u"true"_ns, true);
130 
131   PresShell::SetCapturingContent(mContent, CaptureFlags::IgnoreAllowedState);
132 
133   if (!weakFrame.IsAlive()) {
134     return false;
135   }
136 
137   nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
138   if (sb) {
139     nsIScrollbarMediator* m = sb->GetScrollbarMediator();
140     switch (pressedButtonAction) {
141       case 0:
142         sb->SetIncrementToLine(direction);
143         if (m) {
144           m->ScrollByLine(sb, direction, nsIScrollbarMediator::ENABLE_SNAP);
145         }
146         break;
147       case 1:
148         sb->SetIncrementToPage(direction);
149         if (m) {
150           m->ScrollByPage(sb, direction, nsIScrollbarMediator::ENABLE_SNAP);
151         }
152         break;
153       case 2:
154         sb->SetIncrementToWhole(direction);
155         if (m) {
156           m->ScrollByWhole(sb, direction, nsIScrollbarMediator::ENABLE_SNAP);
157         }
158         break;
159       case 3:
160       default:
161         // We were told to ignore this click, or someone assigned a non-standard
162         // value to the button's action.
163         return false;
164     }
165     if (!weakFrame.IsAlive()) {
166       return false;
167     }
168 
169     if (!m) {
170       sb->MoveToNewPosition(nsScrollbarFrame::ImplementsScrollByUnit::No);
171       if (!weakFrame.IsAlive()) {
172         return false;
173       }
174     }
175   }
176   if (repeat) {
177     StartRepeat();
178   }
179   return true;
180 }
181 
182 NS_IMETHODIMP
HandleRelease(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)183 nsScrollbarButtonFrame::HandleRelease(nsPresContext* aPresContext,
184                                       WidgetGUIEvent* aEvent,
185                                       nsEventStatus* aEventStatus) {
186   PresShell::ReleaseCapturingContent();
187   // we're not active anymore
188   mContent->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::active, true);
189   StopRepeat();
190   nsIFrame* scrollbar;
191   GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
192   nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
193   if (sb) {
194     nsIScrollbarMediator* m = sb->GetScrollbarMediator();
195     if (m) {
196       m->ScrollbarReleased(sb);
197     }
198   }
199   return NS_OK;
200 }
201 
Notify()202 void nsScrollbarButtonFrame::Notify() {
203   if (mCursorOnThis ||
204       LookAndFeel::GetInt(LookAndFeel::IntID::ScrollbarButtonAutoRepeatBehavior,
205                           0)) {
206     // get the scrollbar control
207     nsIFrame* scrollbar;
208     GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
209     nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
210     if (sb) {
211       nsIScrollbarMediator* m = sb->GetScrollbarMediator();
212       if (m) {
213         m->RepeatButtonScroll(sb);
214       } else {
215         sb->MoveToNewPosition(nsScrollbarFrame::ImplementsScrollByUnit::No);
216       }
217     }
218   }
219 }
220 
GetChildWithTag(nsAtom * atom,nsIFrame * start,nsIFrame * & result)221 nsresult nsScrollbarButtonFrame::GetChildWithTag(nsAtom* atom, nsIFrame* start,
222                                                  nsIFrame*& result) {
223   // recursively search our children
224   for (nsIFrame* childFrame : start->PrincipalChildList()) {
225     // get the content node
226     nsIContent* child = childFrame->GetContent();
227 
228     if (child) {
229       // see if it is the child
230       if (child->IsXULElement(atom)) {
231         result = childFrame;
232 
233         return NS_OK;
234       }
235     }
236 
237     // recursive search the child
238     GetChildWithTag(atom, childFrame, result);
239     if (result != nullptr) return NS_OK;
240   }
241 
242   result = nullptr;
243   return NS_OK;
244 }
245 
GetParentWithTag(nsAtom * toFind,nsIFrame * start,nsIFrame * & result)246 nsresult nsScrollbarButtonFrame::GetParentWithTag(nsAtom* toFind,
247                                                   nsIFrame* start,
248                                                   nsIFrame*& result) {
249   while (start) {
250     start = start->GetParent();
251 
252     if (start) {
253       // get the content node
254       nsIContent* child = start->GetContent();
255 
256       if (child && child->IsXULElement(toFind)) {
257         result = start;
258         return NS_OK;
259       }
260     }
261   }
262 
263   result = nullptr;
264   return NS_OK;
265 }
266 
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)267 void nsScrollbarButtonFrame::DestroyFrom(nsIFrame* aDestructRoot,
268                                          PostDestroyData& aPostDestroyData) {
269   // Ensure our repeat service isn't going... it's possible that a scrollbar can
270   // disappear out from under you while you're in the process of scrolling.
271   StopRepeat();
272   nsButtonBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
273 }
274