nsMenuBarFrame.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  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/. */
  5. #include "nsMenuBarFrame.h"
  6. #include "nsIServiceManager.h"
  7. #include "nsIContent.h"
  8. #include "nsIAtom.h"
  9. #include "nsPresContext.h"
  10. #include "nsStyleContext.h"
  11. #include "nsCSSRendering.h"
  12. #include "nsNameSpaceManager.h"
  13. #include "nsIDocument.h"
  14. #include "nsGkAtoms.h"
  15. #include "nsMenuFrame.h"
  16. #include "nsMenuPopupFrame.h"
  17. #include "nsUnicharUtils.h"
  18. #include "nsPIDOMWindow.h"
  19. #include "nsIInterfaceRequestorUtils.h"
  20. #include "nsCSSFrameConstructor.h"
  21. #ifdef XP_WIN
  22. #include "nsISound.h"
  23. #include "nsWidgetsCID.h"
  24. #endif
  25. #include "nsContentUtils.h"
  26. #include "nsUTF8Utils.h"
  27. #include "mozilla/TextEvents.h"
  28. #include "mozilla/dom/Event.h"
  29. using namespace mozilla;
  30. //
  31. // NS_NewMenuBarFrame
  32. //
  33. // Wrapper for creating a new menu Bar container
  34. //
  35. nsIFrame*
  36. NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
  37. {
  38. return new (aPresShell) nsMenuBarFrame(aContext);
  39. }
  40. NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame)
  41. NS_QUERYFRAME_HEAD(nsMenuBarFrame)
  42. NS_QUERYFRAME_ENTRY(nsMenuBarFrame)
  43. NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
  44. //
  45. // nsMenuBarFrame cntr
  46. //
  47. nsMenuBarFrame::nsMenuBarFrame(nsStyleContext* aContext):
  48. nsBoxFrame(aContext),
  49. mStayActive(false),
  50. mIsActive(false),
  51. mCurrentMenu(nullptr),
  52. mTarget(nullptr)
  53. {
  54. } // cntr
  55. void
  56. nsMenuBarFrame::Init(nsIContent* aContent,
  57. nsContainerFrame* aParent,
  58. nsIFrame* aPrevInFlow)
  59. {
  60. nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
  61. // Create the menu bar listener.
  62. mMenuBarListener = new nsMenuBarListener(this);
  63. // Hook up the menu bar as a key listener on the whole document. It will see every
  64. // key press that occurs, but after everyone else does.
  65. mTarget = aContent->GetComposedDoc();
  66. // Also hook up the listener to the window listening for focus events. This is so we can keep proper
  67. // state as the user alt-tabs through processes.
  68. mTarget->AddSystemEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
  69. mTarget->AddSystemEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
  70. mTarget->AddSystemEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
  71. mTarget->AddSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"), mMenuBarListener, false);
  72. // mousedown event should be handled in all phase
  73. mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
  74. mTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
  75. mTarget->AddEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
  76. mTarget->AddEventListener(NS_LITERAL_STRING("MozDOMFullscreen:Entered"), mMenuBarListener, false);
  77. }
  78. NS_IMETHODIMP
  79. nsMenuBarFrame::SetActive(bool aActiveFlag)
  80. {
  81. // If the activity is not changed, there is nothing to do.
  82. if (mIsActive == aActiveFlag)
  83. return NS_OK;
  84. if (!aActiveFlag) {
  85. // Don't deactivate when switching between menus on the menubar.
  86. if (mStayActive)
  87. return NS_OK;
  88. // if there is a request to deactivate the menu bar, check to see whether
  89. // there is a menu popup open for the menu bar. In this case, don't
  90. // deactivate the menu bar.
  91. nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  92. if (pm && pm->IsPopupOpenForMenuParent(this))
  93. return NS_OK;
  94. }
  95. mIsActive = aActiveFlag;
  96. if (mIsActive) {
  97. InstallKeyboardNavigator();
  98. }
  99. else {
  100. mActiveByKeyboard = false;
  101. RemoveKeyboardNavigator();
  102. }
  103. NS_NAMED_LITERAL_STRING(active, "DOMMenuBarActive");
  104. NS_NAMED_LITERAL_STRING(inactive, "DOMMenuBarInactive");
  105. FireDOMEvent(mIsActive ? active : inactive, mContent);
  106. return NS_OK;
  107. }
  108. nsMenuFrame*
  109. nsMenuBarFrame::ToggleMenuActiveState()
  110. {
  111. if (mIsActive) {
  112. // Deactivate the menu bar
  113. SetActive(false);
  114. if (mCurrentMenu) {
  115. nsMenuFrame* closeframe = mCurrentMenu;
  116. closeframe->SelectMenu(false);
  117. mCurrentMenu = nullptr;
  118. return closeframe;
  119. }
  120. }
  121. else {
  122. // if the menu bar is already selected (eg. mouseover), deselect it
  123. if (mCurrentMenu)
  124. mCurrentMenu->SelectMenu(false);
  125. // Set the active menu to be the top left item (e.g., the File menu).
  126. // We use an attribute called "menuactive" to track the current
  127. // active menu.
  128. nsMenuFrame* firstFrame = nsXULPopupManager::GetNextMenuItem(this, nullptr, false);
  129. if (firstFrame) {
  130. // Activate the menu bar
  131. SetActive(true);
  132. firstFrame->SelectMenu(true);
  133. // Track this item for keyboard navigation.
  134. mCurrentMenu = firstFrame;
  135. }
  136. }
  137. return nullptr;
  138. }
  139. nsMenuFrame*
  140. nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent)
  141. {
  142. uint32_t charCode;
  143. aKeyEvent->GetCharCode(&charCode);
  144. AutoTArray<uint32_t, 10> accessKeys;
  145. WidgetKeyboardEvent* nativeKeyEvent =
  146. aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
  147. if (nativeKeyEvent) {
  148. nativeKeyEvent->GetAccessKeyCandidates(accessKeys);
  149. }
  150. if (accessKeys.IsEmpty() && charCode)
  151. accessKeys.AppendElement(charCode);
  152. if (accessKeys.IsEmpty())
  153. return nullptr; // no character was pressed so just return
  154. // Enumerate over our list of frames.
  155. auto insertion = PresContext()->PresShell()->FrameConstructor()->
  156. GetInsertionPoint(GetContent(), nullptr);
  157. nsContainerFrame* immediateParent = insertion.mParentFrame;
  158. if (!immediateParent)
  159. immediateParent = this;
  160. // Find a most preferred accesskey which should be returned.
  161. nsIFrame* foundMenu = nullptr;
  162. size_t foundIndex = accessKeys.NoIndex;
  163. nsIFrame* currFrame = immediateParent->PrincipalChildList().FirstChild();
  164. while (currFrame) {
  165. nsIContent* current = currFrame->GetContent();
  166. // See if it's a menu item.
  167. if (nsXULPopupManager::IsValidMenuItem(current, false)) {
  168. // Get the shortcut attribute.
  169. nsAutoString shortcutKey;
  170. current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, shortcutKey);
  171. if (!shortcutKey.IsEmpty()) {
  172. ToLowerCase(shortcutKey);
  173. const char16_t* start = shortcutKey.BeginReading();
  174. const char16_t* end = shortcutKey.EndReading();
  175. uint32_t ch = UTF16CharEnumerator::NextChar(&start, end);
  176. size_t index = accessKeys.IndexOf(ch);
  177. if (index != accessKeys.NoIndex &&
  178. (foundIndex == accessKeys.NoIndex || index < foundIndex)) {
  179. foundMenu = currFrame;
  180. foundIndex = index;
  181. }
  182. }
  183. }
  184. currFrame = currFrame->GetNextSibling();
  185. }
  186. if (foundMenu) {
  187. return do_QueryFrame(foundMenu);
  188. }
  189. // didn't find a matching menu item
  190. #ifdef XP_WIN
  191. // behavior on Windows - this item is on the menu bar, beep and deactivate the menu bar
  192. if (mIsActive) {
  193. nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
  194. if (soundInterface)
  195. soundInterface->Beep();
  196. }
  197. nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  198. if (pm) {
  199. nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
  200. if (popup)
  201. pm->HidePopup(popup->GetContent(), true, true, true, false);
  202. }
  203. SetCurrentMenuItem(nullptr);
  204. SetActive(false);
  205. #endif // #ifdef XP_WIN
  206. return nullptr;
  207. }
  208. /* virtual */ nsMenuFrame*
  209. nsMenuBarFrame::GetCurrentMenuItem()
  210. {
  211. return mCurrentMenu;
  212. }
  213. NS_IMETHODIMP
  214. nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem)
  215. {
  216. if (mCurrentMenu == aMenuItem)
  217. return NS_OK;
  218. if (mCurrentMenu)
  219. mCurrentMenu->SelectMenu(false);
  220. if (aMenuItem)
  221. aMenuItem->SelectMenu(true);
  222. mCurrentMenu = aMenuItem;
  223. return NS_OK;
  224. }
  225. void
  226. nsMenuBarFrame::CurrentMenuIsBeingDestroyed()
  227. {
  228. mCurrentMenu->SelectMenu(false);
  229. mCurrentMenu = nullptr;
  230. }
  231. class nsMenuBarSwitchMenu : public Runnable
  232. {
  233. public:
  234. nsMenuBarSwitchMenu(nsIContent* aMenuBar,
  235. nsIContent *aOldMenu,
  236. nsIContent *aNewMenu,
  237. bool aSelectFirstItem)
  238. : mMenuBar(aMenuBar), mOldMenu(aOldMenu), mNewMenu(aNewMenu),
  239. mSelectFirstItem(aSelectFirstItem)
  240. {
  241. }
  242. NS_IMETHOD Run() override
  243. {
  244. nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  245. if (!pm)
  246. return NS_ERROR_UNEXPECTED;
  247. // if switching from one menu to another, set a flag so that the call to
  248. // HidePopup doesn't deactivate the menubar when the first menu closes.
  249. nsMenuBarFrame* menubar = nullptr;
  250. if (mOldMenu && mNewMenu) {
  251. menubar = do_QueryFrame(mMenuBar->GetPrimaryFrame());
  252. if (menubar)
  253. menubar->SetStayActive(true);
  254. }
  255. if (mOldMenu) {
  256. nsWeakFrame weakMenuBar(menubar);
  257. pm->HidePopup(mOldMenu, false, false, false, false);
  258. // clear the flag again
  259. if (mNewMenu && weakMenuBar.IsAlive())
  260. menubar->SetStayActive(false);
  261. }
  262. if (mNewMenu)
  263. pm->ShowMenu(mNewMenu, mSelectFirstItem, false);
  264. return NS_OK;
  265. }
  266. private:
  267. nsCOMPtr<nsIContent> mMenuBar;
  268. nsCOMPtr<nsIContent> mOldMenu;
  269. nsCOMPtr<nsIContent> mNewMenu;
  270. bool mSelectFirstItem;
  271. };
  272. NS_IMETHODIMP
  273. nsMenuBarFrame::ChangeMenuItem(nsMenuFrame* aMenuItem,
  274. bool aSelectFirstItem,
  275. bool aFromKey)
  276. {
  277. if (mCurrentMenu == aMenuItem)
  278. return NS_OK;
  279. // check if there's an open context menu, we ignore this
  280. nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  281. if (pm && pm->HasContextMenu(nullptr))
  282. return NS_OK;
  283. nsIContent* aOldMenu = nullptr;
  284. nsIContent* aNewMenu = nullptr;
  285. // Unset the current child.
  286. bool wasOpen = false;
  287. if (mCurrentMenu) {
  288. wasOpen = mCurrentMenu->IsOpen();
  289. mCurrentMenu->SelectMenu(false);
  290. if (wasOpen) {
  291. nsMenuPopupFrame* popupFrame = mCurrentMenu->GetPopup();
  292. if (popupFrame)
  293. aOldMenu = popupFrame->GetContent();
  294. }
  295. }
  296. // set to null first in case the IsAlive check below returns false
  297. mCurrentMenu = nullptr;
  298. // Set the new child.
  299. if (aMenuItem) {
  300. nsCOMPtr<nsIContent> content = aMenuItem->GetContent();
  301. aMenuItem->SelectMenu(true);
  302. mCurrentMenu = aMenuItem;
  303. if (wasOpen && !aMenuItem->IsDisabled())
  304. aNewMenu = content;
  305. }
  306. // use an event so that hiding and showing can be done synchronously, which
  307. // avoids flickering
  308. nsCOMPtr<nsIRunnable> event =
  309. new nsMenuBarSwitchMenu(GetContent(), aOldMenu, aNewMenu, aSelectFirstItem);
  310. return NS_DispatchToCurrentThread(event);
  311. }
  312. nsMenuFrame*
  313. nsMenuBarFrame::Enter(WidgetGUIEvent* aEvent)
  314. {
  315. if (!mCurrentMenu)
  316. return nullptr;
  317. if (mCurrentMenu->IsOpen())
  318. return mCurrentMenu->Enter(aEvent);
  319. return mCurrentMenu;
  320. }
  321. bool
  322. nsMenuBarFrame::MenuClosed()
  323. {
  324. SetActive(false);
  325. if (!mIsActive && mCurrentMenu) {
  326. mCurrentMenu->SelectMenu(false);
  327. mCurrentMenu = nullptr;
  328. return true;
  329. }
  330. return false;
  331. }
  332. void
  333. nsMenuBarFrame::InstallKeyboardNavigator()
  334. {
  335. nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  336. if (pm)
  337. pm->SetActiveMenuBar(this, true);
  338. }
  339. void
  340. nsMenuBarFrame::RemoveKeyboardNavigator()
  341. {
  342. if (!mIsActive) {
  343. nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  344. if (pm)
  345. pm->SetActiveMenuBar(this, false);
  346. }
  347. }
  348. void
  349. nsMenuBarFrame::DestroyFrom(nsIFrame* aDestructRoot)
  350. {
  351. nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
  352. if (pm)
  353. pm->SetActiveMenuBar(this, false);
  354. mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
  355. mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
  356. mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
  357. mTarget->RemoveSystemEventListener(NS_LITERAL_STRING("mozaccesskeynotfound"), mMenuBarListener, false);
  358. mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
  359. mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
  360. mTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
  361. mTarget->RemoveEventListener(NS_LITERAL_STRING("MozDOMFullscreen:Entered"), mMenuBarListener, false);
  362. mMenuBarListener->OnDestroyMenuBarFrame();
  363. mMenuBarListener = nullptr;
  364. nsBoxFrame::DestroyFrom(aDestructRoot);
  365. }