123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- #include "nsMenuBarFrame.h"
- #include "nsIServiceManager.h"
- #include "nsIContent.h"
- #include "nsIAtom.h"
- #include "nsPresContext.h"
- #include "nsStyleContext.h"
- #include "nsCSSRendering.h"
- #include "nsNameSpaceManager.h"
- #include "nsIDocument.h"
- #include "nsGkAtoms.h"
- #include "nsMenuFrame.h"
- #include "nsMenuPopupFrame.h"
- #include "nsUnicharUtils.h"
- #include "nsPIDOMWindow.h"
- #include "nsIInterfaceRequestorUtils.h"
- #include "nsCSSFrameConstructor.h"
- #ifdef XP_WIN
- #include "nsISound.h"
- #include "nsWidgetsCID.h"
- #endif
- #include "nsContentUtils.h"
- #include "nsUTF8Utils.h"
- #include "mozilla/TextEvents.h"
- #include "mozilla/dom/Event.h"
- using namespace mozilla;
- //
- // NS_NewMenuBarFrame
- //
- // Wrapper for creating a new menu Bar container
- //
- nsIFrame*
- NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
- {
- return new (aPresShell) nsMenuBarFrame(aContext);
- }
- NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame)
- NS_QUERYFRAME_HEAD(nsMenuBarFrame)
- NS_QUERYFRAME_ENTRY(nsMenuBarFrame)
- NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
- //
- // nsMenuBarFrame cntr
- //
- nsMenuBarFrame::nsMenuBarFrame(nsStyleContext* aContext):
- nsBoxFrame(aContext),
- mStayActive(false),
- mIsActive(false),
- mCurrentMenu(nullptr),
- mTarget(nullptr)
- {
- } // cntr
- void
- nsMenuBarFrame::Init(nsIContent* aContent,
- nsContainerFrame* aParent,
- nsIFrame* aPrevInFlow)
- {
- nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
- // Create the menu bar listener.
- mMenuBarListener = new nsMenuBarListener(this);
- // Hook up the menu bar as a key listener on the whole document. It will see every
- // key press that occurs, but after everyone else does.
- mTarget = aContent->GetComposedDoc();
- // Also hook up the listener to the window listening for focus events. This is so we can keep proper
- // state as the user alt-tabs through processes.
- mTarget->AddSystemEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
- mTarget->AddSystemEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
- mTarget->AddSystemEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
- mTarget->AddSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"), mMenuBarListener, false);
- // mousedown event should be handled in all phase
- mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
- mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
- mTarget->AddEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
- mTarget->AddEventListener(NS_LITERAL_STRING("MozDOMFullscreen:Entered"), mMenuBarListener, false);
- }
- NS_IMETHODIMP
- nsMenuBarFrame::SetActive(bool aActiveFlag)
- {
- // If the activity is not changed, there is nothing to do.
- if (mIsActive == aActiveFlag)
- return NS_OK;
- if (!aActiveFlag) {
- // Don't deactivate when switching between menus on the menubar.
- if (mStayActive)
- return NS_OK;
- // if there is a request to deactivate the menu bar, check to see whether
- // there is a menu popup open for the menu bar. In this case, don't
- // deactivate the menu bar.
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
- if (pm && pm->IsPopupOpenForMenuParent(this))
- return NS_OK;
- }
- mIsActive = aActiveFlag;
- if (mIsActive) {
- InstallKeyboardNavigator();
- }
- else {
- mActiveByKeyboard = false;
- RemoveKeyboardNavigator();
- }
- NS_NAMED_LITERAL_STRING(active, "DOMMenuBarActive");
- NS_NAMED_LITERAL_STRING(inactive, "DOMMenuBarInactive");
-
- FireDOMEvent(mIsActive ? active : inactive, mContent);
- return NS_OK;
- }
- nsMenuFrame*
- nsMenuBarFrame::ToggleMenuActiveState()
- {
- if (mIsActive) {
- // Deactivate the menu bar
- SetActive(false);
- if (mCurrentMenu) {
- nsMenuFrame* closeframe = mCurrentMenu;
- closeframe->SelectMenu(false);
- mCurrentMenu = nullptr;
- return closeframe;
- }
- }
- else {
- // if the menu bar is already selected (eg. mouseover), deselect it
- if (mCurrentMenu)
- mCurrentMenu->SelectMenu(false);
- // Set the active menu to be the top left item (e.g., the File menu).
- // We use an attribute called "menuactive" to track the current
- // active menu.
- nsMenuFrame* firstFrame = nsXULPopupManager::GetNextMenuItem(this, nullptr, false);
- if (firstFrame) {
- // Activate the menu bar
- SetActive(true);
- firstFrame->SelectMenu(true);
-
- // Track this item for keyboard navigation.
- mCurrentMenu = firstFrame;
- }
- }
- return nullptr;
- }
- nsMenuFrame*
- nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent)
- {
- uint32_t charCode;
- aKeyEvent->GetCharCode(&charCode);
- AutoTArray<uint32_t, 10> accessKeys;
- WidgetKeyboardEvent* nativeKeyEvent =
- aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
- if (nativeKeyEvent) {
- nativeKeyEvent->GetAccessKeyCandidates(accessKeys);
- }
- if (accessKeys.IsEmpty() && charCode)
- accessKeys.AppendElement(charCode);
- if (accessKeys.IsEmpty())
- return nullptr; // no character was pressed so just return
- // Enumerate over our list of frames.
- auto insertion = PresContext()->PresShell()->FrameConstructor()->
- GetInsertionPoint(GetContent(), nullptr);
- nsContainerFrame* immediateParent = insertion.mParentFrame;
- if (!immediateParent)
- immediateParent = this;
- // Find a most preferred accesskey which should be returned.
- nsIFrame* foundMenu = nullptr;
- size_t foundIndex = accessKeys.NoIndex;
- nsIFrame* currFrame = immediateParent->PrincipalChildList().FirstChild();
- while (currFrame) {
- nsIContent* current = currFrame->GetContent();
- // See if it's a menu item.
- if (nsXULPopupManager::IsValidMenuItem(current, false)) {
- // Get the shortcut attribute.
- nsAutoString shortcutKey;
- current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, shortcutKey);
- if (!shortcutKey.IsEmpty()) {
- ToLowerCase(shortcutKey);
- const char16_t* start = shortcutKey.BeginReading();
- const char16_t* end = shortcutKey.EndReading();
- uint32_t ch = UTF16CharEnumerator::NextChar(&start, end);
- size_t index = accessKeys.IndexOf(ch);
- if (index != accessKeys.NoIndex &&
- (foundIndex == accessKeys.NoIndex || index < foundIndex)) {
- foundMenu = currFrame;
- foundIndex = index;
- }
- }
- }
- currFrame = currFrame->GetNextSibling();
- }
- if (foundMenu) {
- return do_QueryFrame(foundMenu);
- }
- // didn't find a matching menu item
- #ifdef XP_WIN
- // behavior on Windows - this item is on the menu bar, beep and deactivate the menu bar
- if (mIsActive) {
- nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
- if (soundInterface)
- soundInterface->Beep();
- }
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
- if (pm) {
- nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
- if (popup)
- pm->HidePopup(popup->GetContent(), true, true, true, false);
- }
- SetCurrentMenuItem(nullptr);
- SetActive(false);
- #endif // #ifdef XP_WIN
- return nullptr;
- }
- /* virtual */ nsMenuFrame*
- nsMenuBarFrame::GetCurrentMenuItem()
- {
- return mCurrentMenu;
- }
- NS_IMETHODIMP
- nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem)
- {
- if (mCurrentMenu == aMenuItem)
- return NS_OK;
- if (mCurrentMenu)
- mCurrentMenu->SelectMenu(false);
- if (aMenuItem)
- aMenuItem->SelectMenu(true);
- mCurrentMenu = aMenuItem;
- return NS_OK;
- }
- void
- nsMenuBarFrame::CurrentMenuIsBeingDestroyed()
- {
- mCurrentMenu->SelectMenu(false);
- mCurrentMenu = nullptr;
- }
- class nsMenuBarSwitchMenu : public Runnable
- {
- public:
- nsMenuBarSwitchMenu(nsIContent* aMenuBar,
- nsIContent *aOldMenu,
- nsIContent *aNewMenu,
- bool aSelectFirstItem)
- : mMenuBar(aMenuBar), mOldMenu(aOldMenu), mNewMenu(aNewMenu),
- mSelectFirstItem(aSelectFirstItem)
- {
- }
- NS_IMETHOD Run() override
- {
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
- if (!pm)
- return NS_ERROR_UNEXPECTED;
- // if switching from one menu to another, set a flag so that the call to
- // HidePopup doesn't deactivate the menubar when the first menu closes.
- nsMenuBarFrame* menubar = nullptr;
- if (mOldMenu && mNewMenu) {
- menubar = do_QueryFrame(mMenuBar->GetPrimaryFrame());
- if (menubar)
- menubar->SetStayActive(true);
- }
- if (mOldMenu) {
- nsWeakFrame weakMenuBar(menubar);
- pm->HidePopup(mOldMenu, false, false, false, false);
- // clear the flag again
- if (mNewMenu && weakMenuBar.IsAlive())
- menubar->SetStayActive(false);
- }
- if (mNewMenu)
- pm->ShowMenu(mNewMenu, mSelectFirstItem, false);
- return NS_OK;
- }
- private:
- nsCOMPtr<nsIContent> mMenuBar;
- nsCOMPtr<nsIContent> mOldMenu;
- nsCOMPtr<nsIContent> mNewMenu;
- bool mSelectFirstItem;
- };
- NS_IMETHODIMP
- nsMenuBarFrame::ChangeMenuItem(nsMenuFrame* aMenuItem,
- bool aSelectFirstItem,
- bool aFromKey)
- {
- if (mCurrentMenu == aMenuItem)
- return NS_OK;
- // check if there's an open context menu, we ignore this
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
- if (pm && pm->HasContextMenu(nullptr))
- return NS_OK;
- nsIContent* aOldMenu = nullptr;
- nsIContent* aNewMenu = nullptr;
-
- // Unset the current child.
- bool wasOpen = false;
- if (mCurrentMenu) {
- wasOpen = mCurrentMenu->IsOpen();
- mCurrentMenu->SelectMenu(false);
- if (wasOpen) {
- nsMenuPopupFrame* popupFrame = mCurrentMenu->GetPopup();
- if (popupFrame)
- aOldMenu = popupFrame->GetContent();
- }
- }
- // set to null first in case the IsAlive check below returns false
- mCurrentMenu = nullptr;
- // Set the new child.
- if (aMenuItem) {
- nsCOMPtr<nsIContent> content = aMenuItem->GetContent();
- aMenuItem->SelectMenu(true);
- mCurrentMenu = aMenuItem;
- if (wasOpen && !aMenuItem->IsDisabled())
- aNewMenu = content;
- }
- // use an event so that hiding and showing can be done synchronously, which
- // avoids flickering
- nsCOMPtr<nsIRunnable> event =
- new nsMenuBarSwitchMenu(GetContent(), aOldMenu, aNewMenu, aSelectFirstItem);
- return NS_DispatchToCurrentThread(event);
- }
- nsMenuFrame*
- nsMenuBarFrame::Enter(WidgetGUIEvent* aEvent)
- {
- if (!mCurrentMenu)
- return nullptr;
- if (mCurrentMenu->IsOpen())
- return mCurrentMenu->Enter(aEvent);
- return mCurrentMenu;
- }
- bool
- nsMenuBarFrame::MenuClosed()
- {
- SetActive(false);
- if (!mIsActive && mCurrentMenu) {
- mCurrentMenu->SelectMenu(false);
- mCurrentMenu = nullptr;
- return true;
- }
- return false;
- }
- void
- nsMenuBarFrame::InstallKeyboardNavigator()
- {
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
- if (pm)
- pm->SetActiveMenuBar(this, true);
- }
- void
- nsMenuBarFrame::RemoveKeyboardNavigator()
- {
- if (!mIsActive) {
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
- if (pm)
- pm->SetActiveMenuBar(this, false);
- }
- }
- void
- nsMenuBarFrame::DestroyFrom(nsIFrame* aDestructRoot)
- {
- nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
- if (pm)
- pm->SetActiveMenuBar(this, false);
- mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
- mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
- mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
- mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"), mMenuBarListener, false);
- mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
- mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
- mTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
- mTarget->RemoveEventListener(NS_LITERAL_STRING("MozDOMFullscreen:Entered"), mMenuBarListener, false);
- mMenuBarListener->OnDestroyMenuBarFrame();
- mMenuBarListener = nullptr;
- nsBoxFrame::DestroyFrom(aDestructRoot);
- }
|