1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsMenuBarFrame.h"
7 #include "nsIServiceManager.h"
8 #include "nsIContent.h"
10 #include "nsPresContext.h"
11 #include "nsStyleContext.h"
12 #include "nsCSSRendering.h"
13 #include "nsNameSpaceManager.h"
14 #include "nsIDocument.h"
15 #include "nsGkAtoms.h"
16 #include "nsMenuFrame.h"
17 #include "nsMenuPopupFrame.h"
18 #include "nsUnicharUtils.h"
19 #include "nsPIDOMWindow.h"
20 #include "nsIInterfaceRequestorUtils.h"
21 #include "nsCSSFrameConstructor.h"
24 #include "nsWidgetsCID.h"
26 #include "nsContentUtils.h"
27 #include "nsUTF8Utils.h"
28 #include "mozilla/TextEvents.h"
30 using namespace mozilla
;
35 // Wrapper for creating a new menu Bar container
38 NS_NewMenuBarFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
40 return new (aPresShell
) nsMenuBarFrame (aPresShell
, aContext
);
43 NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame
)
45 NS_QUERYFRAME_HEAD(nsMenuBarFrame
)
46 NS_QUERYFRAME_ENTRY(nsMenuBarFrame
)
47 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame
)
50 // nsMenuBarFrame cntr
52 nsMenuBarFrame::nsMenuBarFrame(nsIPresShell
* aShell
, nsStyleContext
* aContext
):
53 nsBoxFrame(aShell
, aContext
),
54 mMenuBarListener(nullptr),
57 mCurrentMenu(nullptr),
63 nsMenuBarFrame::Init(nsIContent
* aContent
,
64 nsContainerFrame
* aParent
,
65 nsIFrame
* aPrevInFlow
)
67 nsBoxFrame::Init(aContent
, aParent
, aPrevInFlow
);
69 // Create the menu bar listener.
70 mMenuBarListener
= new nsMenuBarListener(this);
71 NS_ADDREF(mMenuBarListener
);
73 // Hook up the menu bar as a key listener on the whole document. It will see every
74 // key press that occurs, but after everyone else does.
75 mTarget
= aContent
->GetComposedDoc();
77 // Also hook up the listener to the window listening for focus events. This is so we can keep proper
78 // state as the user alt-tabs through processes.
80 mTarget
->AddEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener
, false);
81 mTarget
->AddEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener
, false);
82 mTarget
->AddEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener
, false);
84 // mousedown event should be handled in all phase
85 mTarget
->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener
, true);
86 mTarget
->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener
, false);
87 mTarget
->AddEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener
, true);
91 nsMenuBarFrame::SetActive(bool aActiveFlag
)
93 // If the activity is not changed, there is nothing to do.
94 if (mIsActive
== aActiveFlag
)
98 // Don't deactivate when switching between menus on the menubar.
102 // if there is a request to deactivate the menu bar, check to see whether
103 // there is a menu popup open for the menu bar. In this case, don't
104 // deactivate the menu bar.
105 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
106 if (pm
&& pm
->IsPopupOpenForMenuParent(this))
110 mIsActive
= aActiveFlag
;
112 InstallKeyboardNavigator();
115 mActiveByKeyboard
= false;
116 RemoveKeyboardNavigator();
119 NS_NAMED_LITERAL_STRING(active
, "DOMMenuBarActive");
120 NS_NAMED_LITERAL_STRING(inactive
, "DOMMenuBarInactive");
122 FireDOMEvent(mIsActive
? active
: inactive
, mContent
);
128 nsMenuBarFrame::ToggleMenuActiveState()
131 // Deactivate the menu bar
134 nsMenuFrame
* closeframe
= mCurrentMenu
;
135 closeframe
->SelectMenu(false);
136 mCurrentMenu
= nullptr;
141 // if the menu bar is already selected (eg. mouseover), deselect it
143 mCurrentMenu
->SelectMenu(false);
145 // Set the active menu to be the top left item (e.g., the File menu).
146 // We use an attribute called "menuactive" to track the current
148 nsMenuFrame
* firstFrame
= nsXULPopupManager::GetNextMenuItem(this, nullptr, false);
150 // Activate the menu bar
152 firstFrame
->SelectMenu(true);
154 // Track this item for keyboard navigation.
155 mCurrentMenu
= firstFrame
;
163 nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent
* aKeyEvent
)
166 aKeyEvent
->GetCharCode(&charCode
);
168 nsAutoTArray
<uint32_t, 10> accessKeys
;
169 WidgetKeyboardEvent
* nativeKeyEvent
=
170 aKeyEvent
->GetInternalNSEvent()->AsKeyboardEvent();
172 nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent
, accessKeys
);
173 if (accessKeys
.IsEmpty() && charCode
)
174 accessKeys
.AppendElement(charCode
);
176 if (accessKeys
.IsEmpty())
177 return nullptr; // no character was pressed so just return
179 // Enumerate over our list of frames.
180 nsIFrame
* immediateParent
= PresContext()->PresShell()->FrameConstructor()->
181 GetInsertionPoint(GetContent(), nullptr);
182 if (!immediateParent
)
183 immediateParent
= this;
185 // Find a most preferred accesskey which should be returned.
186 nsIFrame
* foundMenu
= nullptr;
187 size_t foundIndex
= accessKeys
.NoIndex
;
188 nsIFrame
* currFrame
= immediateParent
->GetFirstPrincipalChild();
191 nsIContent
* current
= currFrame
->GetContent();
193 // See if it's a menu item.
194 if (nsXULPopupManager::IsValidMenuItem(PresContext(), current
, false)) {
195 // Get the shortcut attribute.
196 nsAutoString shortcutKey
;
197 current
->GetAttr(kNameSpaceID_None
, nsGkAtoms::accesskey
, shortcutKey
);
198 if (!shortcutKey
.IsEmpty()) {
199 ToLowerCase(shortcutKey
);
200 const char16_t
* start
= shortcutKey
.BeginReading();
201 const char16_t
* end
= shortcutKey
.EndReading();
202 uint32_t ch
= UTF16CharEnumerator::NextChar(&start
, end
);
203 size_t index
= accessKeys
.IndexOf(ch
);
204 if (index
!= accessKeys
.NoIndex
&&
205 (foundIndex
== accessKeys
.NoIndex
|| index
< foundIndex
)) {
206 foundMenu
= currFrame
;
211 currFrame
= currFrame
->GetNextSibling();
214 return do_QueryFrame(foundMenu
);
217 // didn't find a matching menu item
219 // behavior on Windows - this item is on the menu bar, beep and deactivate the menu bar
221 nsCOMPtr
<nsISound
> soundInterface
= do_CreateInstance("@mozilla.org/sound;1");
223 soundInterface
->Beep();
226 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
228 nsIFrame
* popup
= pm
->GetTopPopup(ePopupTypeAny
);
230 pm
->HidePopup(popup
->GetContent(), true, true, true, false);
233 SetCurrentMenuItem(nullptr);
236 #endif // #ifdef XP_WIN
241 /* virtual */ nsMenuFrame
*
242 nsMenuBarFrame::GetCurrentMenuItem()
248 nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame
* aMenuItem
)
250 if (mCurrentMenu
== aMenuItem
)
254 mCurrentMenu
->SelectMenu(false);
257 aMenuItem
->SelectMenu(true);
259 mCurrentMenu
= aMenuItem
;
265 nsMenuBarFrame::CurrentMenuIsBeingDestroyed()
267 mCurrentMenu
->SelectMenu(false);
268 mCurrentMenu
= nullptr;
271 class nsMenuBarSwitchMenu
: public nsRunnable
274 nsMenuBarSwitchMenu(nsIContent
* aMenuBar
,
275 nsIContent
*aOldMenu
,
276 nsIContent
*aNewMenu
,
277 bool aSelectFirstItem
)
278 : mMenuBar(aMenuBar
), mOldMenu(aOldMenu
), mNewMenu(aNewMenu
),
279 mSelectFirstItem(aSelectFirstItem
)
283 NS_IMETHOD
Run() MOZ_OVERRIDE
285 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
287 return NS_ERROR_UNEXPECTED
;
289 // if switching from one menu to another, set a flag so that the call to
290 // HidePopup doesn't deactivate the menubar when the first menu closes.
291 nsMenuBarFrame
* menubar
= nullptr;
292 if (mOldMenu
&& mNewMenu
) {
293 menubar
= do_QueryFrame(mMenuBar
->GetPrimaryFrame());
295 menubar
->SetStayActive(true);
299 nsWeakFrame
weakMenuBar(menubar
);
300 pm
->HidePopup(mOldMenu
, false, false, false, false);
301 // clear the flag again
302 if (mNewMenu
&& weakMenuBar
.IsAlive())
303 menubar
->SetStayActive(false);
307 pm
->ShowMenu(mNewMenu
, mSelectFirstItem
, false);
313 nsCOMPtr
<nsIContent
> mMenuBar
;
314 nsCOMPtr
<nsIContent
> mOldMenu
;
315 nsCOMPtr
<nsIContent
> mNewMenu
;
316 bool mSelectFirstItem
;
320 nsMenuBarFrame::ChangeMenuItem(nsMenuFrame
* aMenuItem
,
321 bool aSelectFirstItem
)
323 if (mCurrentMenu
== aMenuItem
)
326 // check if there's an open context menu, we ignore this
327 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
328 if (pm
&& pm
->HasContextMenu(nullptr))
331 nsIContent
* aOldMenu
= nullptr;
332 nsIContent
* aNewMenu
= nullptr;
334 // Unset the current child.
335 bool wasOpen
= false;
337 wasOpen
= mCurrentMenu
->IsOpen();
338 mCurrentMenu
->SelectMenu(false);
340 nsMenuPopupFrame
* popupFrame
= mCurrentMenu
->GetPopup();
342 aOldMenu
= popupFrame
->GetContent();
346 // set to null first in case the IsAlive check below returns false
347 mCurrentMenu
= nullptr;
349 // Set the new child.
351 nsCOMPtr
<nsIContent
> content
= aMenuItem
->GetContent();
352 aMenuItem
->SelectMenu(true);
353 mCurrentMenu
= aMenuItem
;
354 if (wasOpen
&& !aMenuItem
->IsDisabled())
358 // use an event so that hiding and showing can be done synchronously, which
360 nsCOMPtr
<nsIRunnable
> event
=
361 new nsMenuBarSwitchMenu(GetContent(), aOldMenu
, aNewMenu
, aSelectFirstItem
);
362 return NS_DispatchToCurrentThread(event
);
366 nsMenuBarFrame::Enter(WidgetGUIEvent
* aEvent
)
371 if (mCurrentMenu
->IsOpen())
372 return mCurrentMenu
->Enter(aEvent
);
378 nsMenuBarFrame::MenuClosed()
381 if (!mIsActive
&& mCurrentMenu
) {
382 mCurrentMenu
->SelectMenu(false);
383 mCurrentMenu
= nullptr;
390 nsMenuBarFrame::InstallKeyboardNavigator()
392 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
394 pm
->SetActiveMenuBar(this, true);
398 nsMenuBarFrame::RemoveKeyboardNavigator()
401 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
403 pm
->SetActiveMenuBar(this, false);
408 nsMenuBarFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
410 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
412 pm
->SetActiveMenuBar(this, false);
414 mTarget
->RemoveEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener
, false);
415 mTarget
->RemoveEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener
, false);
416 mTarget
->RemoveEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener
, false);
418 mTarget
->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener
, true);
419 mTarget
->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener
, false);
420 mTarget
->RemoveEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener
, true);
422 NS_IF_RELEASE(mMenuBarListener
);
424 nsBoxFrame::DestroyFrom(aDestructRoot
);