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