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 #include "nsCOMPtr.h"
7 #include "nsButtonBoxFrame.h"
8 #include "nsIContent.h"
9 #include "nsIDOMXULButtonElement.h"
10 #include "nsGkAtoms.h"
11 #include "nsNameSpaceManager.h"
12 #include "nsPresContext.h"
13 #include "nsDisplayList.h"
14 #include "nsContentUtils.h"
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/dom/Event.h"
17 #include "mozilla/dom/MouseEventBinding.h"
18 #include "mozilla/EventStateManager.h"
19 #include "mozilla/EventStates.h"
20 #include "mozilla/MouseEvents.h"
21 #include "mozilla/PresShell.h"
22 #include "mozilla/TextEvents.h"
23 
24 using namespace mozilla;
25 using namespace mozilla::dom;
26 
NS_IMPL_ISUPPORTS(nsButtonBoxFrame::nsButtonBoxListener,nsIDOMEventListener)27 NS_IMPL_ISUPPORTS(nsButtonBoxFrame::nsButtonBoxListener, nsIDOMEventListener)
28 
29 nsresult nsButtonBoxFrame::nsButtonBoxListener::HandleEvent(
30     dom::Event* aEvent) {
31   if (!mButtonBoxFrame) {
32     return NS_OK;
33   }
34 
35   nsAutoString eventType;
36   aEvent->GetType(eventType);
37 
38   if (eventType.EqualsLiteral("blur")) {
39     mButtonBoxFrame->Blurred();
40     return NS_OK;
41   }
42 
43   MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
44   return NS_OK;
45 }
46 
47 //
48 // NS_NewXULButtonFrame
49 //
50 // Creates a new Button frame and returns it
51 //
NS_NewButtonBoxFrame(PresShell * aPresShell,ComputedStyle * aStyle)52 nsIFrame* NS_NewButtonBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
53   return new (aPresShell)
54       nsButtonBoxFrame(aStyle, aPresShell->GetPresContext());
55 }
56 
NS_IMPL_FRAMEARENA_HELPERS(nsButtonBoxFrame)57 NS_IMPL_FRAMEARENA_HELPERS(nsButtonBoxFrame)
58 
59 nsButtonBoxFrame::nsButtonBoxFrame(ComputedStyle* aStyle,
60                                    nsPresContext* aPresContext, ClassID aID)
61     : nsBoxFrame(aStyle, aPresContext, aID, false),
62       mButtonBoxListener(nullptr),
63       mIsHandlingKeyEvent(false) {}
64 
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)65 void nsButtonBoxFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
66                             nsIFrame* aPrevInFlow) {
67   nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
68 
69   mButtonBoxListener = new nsButtonBoxListener(this);
70 
71   mContent->AddSystemEventListener(u"blur"_ns, mButtonBoxListener, false);
72 }
73 
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)74 void nsButtonBoxFrame::DestroyFrom(nsIFrame* aDestructRoot,
75                                    PostDestroyData& aPostDestroyData) {
76   mContent->RemoveSystemEventListener(u"blur"_ns, mButtonBoxListener, false);
77 
78   mButtonBoxListener->mButtonBoxFrame = nullptr;
79   mButtonBoxListener = nullptr;
80 
81   nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
82 }
83 
BuildDisplayListForChildren(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)84 void nsButtonBoxFrame::BuildDisplayListForChildren(
85     nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
86   // override, since we don't want children to get events
87   if (aBuilder->IsForEventDelivery()) return;
88   nsBoxFrame::BuildDisplayListForChildren(aBuilder, aLists);
89 }
90 
HandleEvent(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)91 nsresult nsButtonBoxFrame::HandleEvent(nsPresContext* aPresContext,
92                                        WidgetGUIEvent* aEvent,
93                                        nsEventStatus* aEventStatus) {
94   NS_ENSURE_ARG_POINTER(aEventStatus);
95   if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
96     return NS_OK;
97   }
98 
99   switch (aEvent->mMessage) {
100     case eKeyDown: {
101       WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
102       if (!keyEvent) {
103         break;
104       }
105       if (NS_VK_SPACE == keyEvent->mKeyCode) {
106         EventStateManager* esm = aPresContext->EventStateManager();
107         // :hover:active state
108         esm->SetContentState(mContent, NS_EVENT_STATE_HOVER);
109         esm->SetContentState(mContent, NS_EVENT_STATE_ACTIVE);
110         mIsHandlingKeyEvent = true;
111       }
112       break;
113     }
114 
115 // On mac, Return fires the default button, not the focused one.
116 #ifndef XP_MACOSX
117     case eKeyPress: {
118       WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
119       if (!keyEvent) {
120         break;
121       }
122       if (NS_VK_RETURN == keyEvent->mKeyCode) {
123         RefPtr<nsIDOMXULButtonElement> button =
124             mContent->AsElement()->AsXULButton();
125         if (button) {
126           MouseClicked(aEvent);
127           *aEventStatus = nsEventStatus_eConsumeNoDefault;
128         }
129       }
130       break;
131     }
132 #endif
133 
134     case eKeyUp: {
135       WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
136       if (!keyEvent) {
137         break;
138       }
139       if (NS_VK_SPACE == keyEvent->mKeyCode) {
140         mIsHandlingKeyEvent = false;
141         // only activate on keyup if we're already in the :hover:active state
142         NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?");
143         EventStates buttonState = mContent->AsElement()->State();
144         if (buttonState.HasAllStates(NS_EVENT_STATE_ACTIVE |
145                                      NS_EVENT_STATE_HOVER)) {
146           // return to normal state
147           EventStateManager* esm = aPresContext->EventStateManager();
148           esm->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE);
149           esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER);
150           MouseClicked(aEvent);
151         }
152       }
153       break;
154     }
155 
156     case eMouseClick: {
157       WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
158       if (mouseEvent->IsLeftClickEvent()) {
159         MouseClicked(mouseEvent);
160       }
161       break;
162     }
163 
164     default:
165       break;
166   }
167 
168   return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
169 }
170 
Blurred()171 void nsButtonBoxFrame::Blurred() {
172   NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?");
173   EventStates buttonState = mContent->AsElement()->State();
174   if (mIsHandlingKeyEvent &&
175       buttonState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) {
176     // return to normal state
177     EventStateManager* esm = PresContext()->EventStateManager();
178     esm->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE);
179     esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER);
180   }
181   mIsHandlingKeyEvent = false;
182 }
183 
MouseClicked(WidgetGUIEvent * aEvent)184 void nsButtonBoxFrame::MouseClicked(WidgetGUIEvent* aEvent) {
185   // Don't execute if we're disabled.
186   if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
187                                          nsGkAtoms::_true, eCaseMatters))
188     return;
189 
190   // Have the content handle the event, propagating it according to normal DOM
191   // rules.
192   RefPtr<mozilla::PresShell> presShell = PresContext()->GetPresShell();
193   if (!presShell) {
194     return;
195   }
196 
197   // Execute the oncommand event handler.
198   nsCOMPtr<nsIContent> content = mContent;
199   WidgetInputEvent* inputEvent = aEvent->AsInputEvent();
200   WidgetMouseEventBase* mouseEvent = aEvent->AsMouseEventBase();
201   nsContentUtils::DispatchXULCommand(
202       content, aEvent->IsTrusted(), nullptr, presShell, inputEvent->IsControl(),
203       inputEvent->IsAlt(), inputEvent->IsShift(), inputEvent->IsMeta(),
204       mouseEvent ? mouseEvent->mInputSource
205                  : MouseEvent_Binding::MOZ_SOURCE_UNKNOWN,
206       mouseEvent ? mouseEvent->mButton : 0);
207 }
208