Bug 1133732. Make button box frames listen for blurs so they de-activate on blur. r=smaug
authorTimothy Nikkel <tnikkel@gmail.com>
Sun, 29 Mar 2015 18:05:00 -0500
changeset 266755 4bd0338c09a4420f0a3e652fcd2581b07676a0f6
parent 266754 d7b8787283ce592cabf188d7fdf8ffc1f1d08dbb
child 266756 14c1e88ba55b136d2f34cb69debe17196e9bb1d9
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1133732
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1133732. Make button box frames listen for blurs so they de-activate on blur. r=smaug
layout/xul/nsButtonBoxFrame.cpp
layout/xul/nsButtonBoxFrame.h
--- a/layout/xul/nsButtonBoxFrame.cpp
+++ b/layout/xul/nsButtonBoxFrame.cpp
@@ -17,29 +17,83 @@
 #include "mozilla/dom/Element.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextEvents.h"
 
 using namespace mozilla;
 
+
+NS_IMPL_ISUPPORTS(nsButtonBoxFrame::nsButtonBoxListener, nsIDOMEventListener)
+
+nsresult
+nsButtonBoxFrame::nsButtonBoxListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+  if (!mButtonBoxFrame) {
+    return NS_OK;
+  }
+
+  nsAutoString eventType;
+  aEvent->GetType(eventType);
+
+  if (eventType.EqualsLiteral("blur")) {
+    mButtonBoxFrame->Blurred();
+    return NS_OK;
+  }
+
+  NS_ABORT();
+
+  return NS_OK;
+}
+
 //
 // NS_NewXULButtonFrame
 //
 // Creates a new Button frame and returns it
 //
 nsIFrame*
 NS_NewButtonBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsButtonBoxFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsButtonBoxFrame)
 
+nsButtonBoxFrame::nsButtonBoxFrame(nsStyleContext* aContext) :
+  nsBoxFrame(aContext, false),
+  mButtonBoxListener(nullptr),
+  mIsHandlingKeyEvent(false)
+{
+  UpdateMouseThrough();
+}
+
+void
+nsButtonBoxFrame::Init(nsIContent*       aContent,
+                       nsContainerFrame* aParent,
+                       nsIFrame*         aPrevInFlow)
+{
+  nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
+
+  mButtonBoxListener = new nsButtonBoxListener(this);
+
+  mContent->AddSystemEventListener(NS_LITERAL_STRING("blur"), mButtonBoxListener, false);
+}
+
+void
+nsButtonBoxFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+  mContent->RemoveSystemEventListener(NS_LITERAL_STRING("blur"), mButtonBoxListener, false);
+
+  mButtonBoxListener->mButtonBoxFrame = nullptr;
+  mButtonBoxListener = nullptr;
+
+  nsBoxFrame::DestroyFrom(aDestructRoot);
+}
+
 void
 nsButtonBoxFrame::BuildDisplayListForChildren(nsDisplayListBuilder*   aBuilder,
                                               const nsRect&           aDirtyRect,
                                               const nsDisplayListSet& aLists)
 {
   // override, since we don't want children to get events
   if (aBuilder->IsForEventDelivery())
     return;
@@ -62,16 +116,17 @@ nsButtonBoxFrame::HandleEvent(nsPresCont
       if (!keyEvent) {
         break;
       }
       if (NS_VK_SPACE == keyEvent->keyCode) {
         EventStateManager* esm = aPresContext->EventStateManager();
         // :hover:active state
         esm->SetContentState(mContent, NS_EVENT_STATE_HOVER);
         esm->SetContentState(mContent, NS_EVENT_STATE_ACTIVE);
+        mIsHandlingKeyEvent = true;
       }
       break;
     }
 
 // On mac, Return fires the default button, not the focused one.
 #ifndef XP_MACOSX
     case NS_KEY_PRESS: {
       WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
@@ -90,16 +145,17 @@ nsButtonBoxFrame::HandleEvent(nsPresCont
 #endif
 
     case NS_KEY_UP: {
       WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
       if (!keyEvent) {
         break;
       }
       if (NS_VK_SPACE == keyEvent->keyCode) {
+        mIsHandlingKeyEvent = false;
         // only activate on keyup if we're already in the :hover:active state
         NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?");
         EventStates buttonState = mContent->AsElement()->State();
         if (buttonState.HasAllStates(NS_EVENT_STATE_ACTIVE |
                                      NS_EVENT_STATE_HOVER)) {
           // return to normal state
           EventStateManager* esm = aPresContext->EventStateManager();
           esm->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE);
@@ -117,17 +173,33 @@ nsButtonBoxFrame::HandleEvent(nsPresCont
       }
       break;
     }
   }
 
   return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
 }
 
-void 
+void
+nsButtonBoxFrame::Blurred()
+{
+  NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?");
+  EventStates buttonState = mContent->AsElement()->State();
+  if (mIsHandlingKeyEvent &&
+      buttonState.HasAllStates(NS_EVENT_STATE_ACTIVE |
+                               NS_EVENT_STATE_HOVER)) {
+    // return to normal state
+    EventStateManager* esm = PresContext()->EventStateManager();
+    esm->SetContentState(nullptr, NS_EVENT_STATE_ACTIVE);
+    esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER);
+  }
+  mIsHandlingKeyEvent = false;
+}
+
+void
 nsButtonBoxFrame::DoMouseClick(WidgetGUIEvent* aEvent, bool aTrustEvent)
 {
   // Don't execute if we're disabled.
   if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
                             nsGkAtoms::_true, eCaseMatters))
     return;
 
   // Execute the oncommand event handler.
--- a/layout/xul/nsButtonBoxFrame.h
+++ b/layout/xul/nsButtonBoxFrame.h
@@ -10,40 +10,66 @@
 
 class nsButtonBoxFrame : public nsBoxFrame
 {
 public:
   NS_DECL_FRAMEARENA_HELPERS
 
   friend nsIFrame* NS_NewButtonBoxFrame(nsIPresShell* aPresShell);
 
-  explicit nsButtonBoxFrame(nsStyleContext* aContext)
-    :nsBoxFrame(aContext, false) {
-    UpdateMouseThrough();
-  }
+  explicit nsButtonBoxFrame(nsStyleContext* aContext);
+
+  virtual void Init(nsIContent*       aContent,
+                    nsContainerFrame* aParent,
+                    nsIFrame*         aPrevInFlow) override;
 
   virtual void BuildDisplayListForChildren(nsDisplayListBuilder*   aBuilder,
                                            const nsRect&           aDirtyRect,
                                            const nsDisplayListSet& aLists) override;
 
+  virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
   virtual nsresult HandleEvent(nsPresContext* aPresContext, 
                                mozilla::WidgetGUIEvent* aEvent,
                                nsEventStatus* aEventStatus) override;
 
   virtual void MouseClicked(nsPresContext* aPresContext,
                             mozilla::WidgetGUIEvent* aEvent)
   { DoMouseClick(aEvent, false); }
 
+  void Blurred();
+
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override {
     return MakeFrameName(NS_LITERAL_STRING("ButtonBoxFrame"), aResult);
   }
 #endif
 
   /**
    * Our implementation of MouseClicked. 
    * @param aTrustEvent if true and aEvent as null, then assume the event was trusted
    */
   void DoMouseClick(mozilla::WidgetGUIEvent* aEvent, bool aTrustEvent);
   void UpdateMouseThrough() override { AddStateBits(NS_FRAME_MOUSE_THROUGH_NEVER); }
+
+private:
+  class nsButtonBoxListener final : public nsIDOMEventListener
+  {
+  public:
+    explicit nsButtonBoxListener(nsButtonBoxFrame* aButtonBoxFrame) :
+      mButtonBoxFrame(aButtonBoxFrame)
+      { }
+
+    NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override;
+
+    NS_DECL_ISUPPORTS
+
+  private:
+    friend class nsButtonBoxFrame;
+    virtual ~nsButtonBoxListener() { }
+    nsButtonBoxFrame* mButtonBoxFrame;
+  };
+
+  nsRefPtr<nsButtonBoxListener> mButtonBoxListener;
+  bool mIsHandlingKeyEvent;
 }; // class nsButtonBoxFrame
 
 #endif /* nsButtonBoxFrame_h___ */