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