/src/mozilla-central/layout/xul/nsMenuFrame.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "nsGkAtoms.h" |
8 | | #include "nsHTMLParts.h" |
9 | | #include "nsMenuFrame.h" |
10 | | #include "nsBoxFrame.h" |
11 | | #include "nsIContent.h" |
12 | | #include "nsAtom.h" |
13 | | #include "nsPresContext.h" |
14 | | #include "nsIPresShell.h" |
15 | | #include "mozilla/ComputedStyle.h" |
16 | | #include "nsCSSRendering.h" |
17 | | #include "nsNameSpaceManager.h" |
18 | | #include "nsMenuPopupFrame.h" |
19 | | #include "nsMenuBarFrame.h" |
20 | | #include "nsIDocument.h" |
21 | | #include "nsIComponentManager.h" |
22 | | #include "nsBoxLayoutState.h" |
23 | | #include "nsIScrollableFrame.h" |
24 | | #include "nsBindingManager.h" |
25 | | #include "nsIServiceManager.h" |
26 | | #include "nsCSSFrameConstructor.h" |
27 | | #include "nsString.h" |
28 | | #include "nsReadableUtils.h" |
29 | | #include "nsUnicharUtils.h" |
30 | | #include "nsIStringBundle.h" |
31 | | #include "nsContentUtils.h" |
32 | | #include "nsDisplayList.h" |
33 | | #include "nsIReflowCallback.h" |
34 | | #include "nsISound.h" |
35 | | #include "nsIDOMXULMenuListElement.h" |
36 | | #include "mozilla/Attributes.h" |
37 | | #include "mozilla/EventDispatcher.h" |
38 | | #include "mozilla/EventStateManager.h" |
39 | | #include "mozilla/Likely.h" |
40 | | #include "mozilla/LookAndFeel.h" |
41 | | #include "mozilla/MouseEvents.h" |
42 | | #include "mozilla/Preferences.h" |
43 | | #include "mozilla/Services.h" |
44 | | #include "mozilla/TextEvents.h" |
45 | | #include "mozilla/dom/Element.h" |
46 | | #include "mozilla/dom/Event.h" |
47 | | #include <algorithm> |
48 | | |
49 | | using namespace mozilla; |
50 | | |
51 | | #define NS_MENU_POPUP_LIST_INDEX 0 |
52 | | |
53 | | #if defined(XP_WIN) |
54 | | #define NSCONTEXTMENUISMOUSEUP 1 |
55 | | #endif |
56 | | |
57 | | NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PopupListProperty) |
58 | | |
59 | | // This global flag indicates that a menu just opened or closed and is used |
60 | | // to ignore the mousemove and mouseup events that would fire on the menu after |
61 | | // the mousedown occurred. |
62 | | static int32_t gMenuJustOpenedOrClosed = false; |
63 | | |
64 | | const int32_t kBlinkDelay = 67; // milliseconds |
65 | | |
66 | | // this class is used for dispatching menu activation events asynchronously. |
67 | | class nsMenuActivateEvent : public Runnable |
68 | | { |
69 | | public: |
70 | | nsMenuActivateEvent(Element* aMenu, |
71 | | nsPresContext* aPresContext, |
72 | | bool aIsActivate) |
73 | | : mozilla::Runnable("nsMenuActivateEvent") |
74 | | , mMenu(aMenu) |
75 | | , mPresContext(aPresContext) |
76 | | , mIsActivate(aIsActivate) |
77 | 0 | { |
78 | 0 | } |
79 | | |
80 | | NS_IMETHOD Run() override |
81 | 0 | { |
82 | 0 | nsAutoString domEventToFire; |
83 | 0 |
|
84 | 0 | if (mIsActivate) { |
85 | 0 | // Highlight the menu. |
86 | 0 | mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, |
87 | 0 | NS_LITERAL_STRING("true"), true); |
88 | 0 | // The menuactivated event is used by accessibility to track the user's |
89 | 0 | // movements through menus |
90 | 0 | domEventToFire.AssignLiteral("DOMMenuItemActive"); |
91 | 0 | } |
92 | 0 | else { |
93 | 0 | // Unhighlight the menu. |
94 | 0 | mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true); |
95 | 0 | domEventToFire.AssignLiteral("DOMMenuItemInactive"); |
96 | 0 | } |
97 | 0 |
|
98 | 0 | RefPtr<dom::Event> event = NS_NewDOMEvent(mMenu, mPresContext, nullptr); |
99 | 0 | event->InitEvent(domEventToFire, true, true); |
100 | 0 |
|
101 | 0 | event->SetTrusted(true); |
102 | 0 |
|
103 | 0 | EventDispatcher::DispatchDOMEvent(mMenu, nullptr, event, |
104 | 0 | mPresContext, nullptr); |
105 | 0 |
|
106 | 0 | return NS_OK; |
107 | 0 | } |
108 | | |
109 | | private: |
110 | | RefPtr<Element> mMenu; |
111 | | RefPtr<nsPresContext> mPresContext; |
112 | | bool mIsActivate; |
113 | | }; |
114 | | |
115 | | class nsMenuAttributeChangedEvent : public Runnable |
116 | | { |
117 | | public: |
118 | | nsMenuAttributeChangedEvent(nsIFrame* aFrame, nsAtom* aAttr) |
119 | | : mozilla::Runnable("nsMenuAttributeChangedEvent") |
120 | | , mFrame(aFrame) |
121 | | , mAttr(aAttr) |
122 | 0 | { |
123 | 0 | } |
124 | | |
125 | | NS_IMETHOD Run() override |
126 | 0 | { |
127 | 0 | nsMenuFrame* frame = static_cast<nsMenuFrame*>(mFrame.GetFrame()); |
128 | 0 | NS_ENSURE_STATE(frame); |
129 | 0 | if (mAttr == nsGkAtoms::checked) { |
130 | 0 | frame->UpdateMenuSpecialState(); |
131 | 0 | } else if (mAttr == nsGkAtoms::acceltext) { |
132 | 0 | // someone reset the accelText attribute, |
133 | 0 | // so clear the bit that says *we* set it |
134 | 0 | frame->RemoveStateBits(NS_STATE_ACCELTEXT_IS_DERIVED); |
135 | 0 | frame->BuildAcceleratorText(true); |
136 | 0 | } |
137 | 0 | else if (mAttr == nsGkAtoms::key) { |
138 | 0 | frame->BuildAcceleratorText(true); |
139 | 0 | } else if (mAttr == nsGkAtoms::type || mAttr == nsGkAtoms::name) { |
140 | 0 | frame->UpdateMenuType(); |
141 | 0 | } |
142 | 0 | return NS_OK; |
143 | 0 | } |
144 | | protected: |
145 | | WeakFrame mFrame; |
146 | | RefPtr<nsAtom> mAttr; |
147 | | }; |
148 | | |
149 | | // |
150 | | // NS_NewMenuFrame and NS_NewMenuItemFrame |
151 | | // |
152 | | // Wrappers for creating a new menu popup container |
153 | | // |
154 | | nsIFrame* |
155 | | NS_NewMenuFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
156 | 0 | { |
157 | 0 | nsMenuFrame* it = new (aPresShell) nsMenuFrame(aStyle); |
158 | 0 | it->SetIsMenu(true); |
159 | 0 | return it; |
160 | 0 | } |
161 | | |
162 | | nsIFrame* |
163 | | NS_NewMenuItemFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
164 | 0 | { |
165 | 0 | nsMenuFrame* it = new (aPresShell) nsMenuFrame(aStyle); |
166 | 0 | it->SetIsMenu(false); |
167 | 0 | return it; |
168 | 0 | } |
169 | | |
170 | | NS_IMPL_FRAMEARENA_HELPERS(nsMenuFrame) |
171 | | |
172 | 0 | NS_QUERYFRAME_HEAD(nsMenuFrame) |
173 | 0 | NS_QUERYFRAME_ENTRY(nsMenuFrame) |
174 | 0 | NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) |
175 | | |
176 | | nsMenuFrame::nsMenuFrame(ComputedStyle* aStyle) |
177 | | : nsBoxFrame(aStyle, kClassID) |
178 | | , mIsMenu(false) |
179 | | , mChecked(false) |
180 | | , mIgnoreAccelTextChange(false) |
181 | | , mReflowCallbackPosted(false) |
182 | | , mType(eMenuType_Normal) |
183 | | , mBlinkState(0) |
184 | 0 | { |
185 | 0 | } |
186 | | |
187 | | nsMenuParent* |
188 | | nsMenuFrame::GetMenuParent() const |
189 | 0 | { |
190 | 0 | nsContainerFrame* parent = GetParent(); |
191 | 0 | for (; parent; parent = parent->GetParent()) { |
192 | 0 | nsMenuPopupFrame* popup = do_QueryFrame(parent); |
193 | 0 | if (popup) { |
194 | 0 | return popup; |
195 | 0 | } |
196 | 0 | nsMenuBarFrame* menubar = do_QueryFrame(parent); |
197 | 0 | if (menubar) { |
198 | 0 | return menubar; |
199 | 0 | } |
200 | 0 | } |
201 | 0 | return nullptr; |
202 | 0 | } |
203 | | |
204 | | bool |
205 | | nsMenuFrame::ReflowFinished() |
206 | 0 | { |
207 | 0 | mReflowCallbackPosted = false; |
208 | 0 |
|
209 | 0 | UpdateMenuType(); |
210 | 0 | return true; |
211 | 0 | } |
212 | | |
213 | | void |
214 | | nsMenuFrame::ReflowCallbackCanceled() |
215 | 0 | { |
216 | 0 | mReflowCallbackPosted = false; |
217 | 0 | } |
218 | | |
219 | | void |
220 | | nsMenuFrame::Init(nsIContent* aContent, |
221 | | nsContainerFrame* aParent, |
222 | | nsIFrame* aPrevInFlow) |
223 | 0 | { |
224 | 0 | nsBoxFrame::Init(aContent, aParent, aPrevInFlow); |
225 | 0 |
|
226 | 0 | // Set up a mediator which can be used for callbacks on this frame. |
227 | 0 | mTimerMediator = new nsMenuTimerMediator(this); |
228 | 0 |
|
229 | 0 | BuildAcceleratorText(false); |
230 | 0 | if (!mReflowCallbackPosted) { |
231 | 0 | mReflowCallbackPosted = true; |
232 | 0 | PresShell()->PostReflowCallback(this); |
233 | 0 | } |
234 | 0 | } |
235 | | |
236 | | const nsFrameList& |
237 | | nsMenuFrame::GetChildList(ChildListID aListID) const |
238 | 0 | { |
239 | 0 | if (kPopupList == aListID) { |
240 | 0 | nsFrameList* list = GetPopupList(); |
241 | 0 | return list ? *list : nsFrameList::EmptyList(); |
242 | 0 | } |
243 | 0 | return nsBoxFrame::GetChildList(aListID); |
244 | 0 | } |
245 | | |
246 | | void |
247 | | nsMenuFrame::GetChildLists(nsTArray<ChildList>* aLists) const |
248 | 0 | { |
249 | 0 | nsBoxFrame::GetChildLists(aLists); |
250 | 0 | nsFrameList* list = GetPopupList(); |
251 | 0 | if (list) { |
252 | 0 | list->AppendIfNonempty(aLists, kPopupList); |
253 | 0 | } |
254 | 0 | } |
255 | | |
256 | | nsMenuPopupFrame* |
257 | | nsMenuFrame::GetPopup() |
258 | 0 | { |
259 | 0 | nsFrameList* popupList = GetPopupList(); |
260 | 0 | return popupList ? static_cast<nsMenuPopupFrame*>(popupList->FirstChild()) : |
261 | 0 | nullptr; |
262 | 0 | } |
263 | | |
264 | | nsFrameList* |
265 | | nsMenuFrame::GetPopupList() const |
266 | 0 | { |
267 | 0 | if (!HasPopup()) { |
268 | 0 | return nullptr; |
269 | 0 | } |
270 | 0 | nsFrameList* prop = GetProperty(PopupListProperty()); |
271 | 0 | NS_ASSERTION(prop && prop->GetLength() == 1 && |
272 | 0 | prop->FirstChild()->IsMenuPopupFrame(), |
273 | 0 | "popup list should have exactly one nsMenuPopupFrame"); |
274 | 0 | return prop; |
275 | 0 | } |
276 | | |
277 | | void |
278 | | nsMenuFrame::DestroyPopupList() |
279 | 0 | { |
280 | 0 | NS_ASSERTION(HasPopup(), "huh?"); |
281 | 0 | nsFrameList* prop = RemoveProperty(PopupListProperty()); |
282 | 0 | NS_ASSERTION(prop && prop->IsEmpty(), |
283 | 0 | "popup list must exist and be empty when destroying"); |
284 | 0 | RemoveStateBits(NS_STATE_MENU_HAS_POPUP_LIST); |
285 | 0 | prop->Delete(PresShell()); |
286 | 0 | } |
287 | | |
288 | | void |
289 | | nsMenuFrame::SetPopupFrame(nsFrameList& aFrameList) |
290 | 0 | { |
291 | 0 | for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { |
292 | 0 | nsMenuPopupFrame* popupFrame = do_QueryFrame(e.get()); |
293 | 0 | if (popupFrame) { |
294 | 0 | // Remove the frame from the list and store it in a nsFrameList* property. |
295 | 0 | aFrameList.RemoveFrame(popupFrame); |
296 | 0 | nsFrameList* popupList = new (PresShell()) nsFrameList(popupFrame, popupFrame); |
297 | 0 | SetProperty(PopupListProperty(), popupList); |
298 | 0 | AddStateBits(NS_STATE_MENU_HAS_POPUP_LIST); |
299 | 0 | break; |
300 | 0 | } |
301 | 0 | } |
302 | 0 | } |
303 | | |
304 | | void |
305 | | nsMenuFrame::SetInitialChildList(ChildListID aListID, |
306 | | nsFrameList& aChildList) |
307 | 0 | { |
308 | 0 | if (aListID == kPrincipalList || aListID == kPopupList) { |
309 | 0 | NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?"); |
310 | 0 | SetPopupFrame(aChildList); |
311 | 0 | } |
312 | 0 | nsBoxFrame::SetInitialChildList(aListID, aChildList); |
313 | 0 | } |
314 | | |
315 | | void |
316 | | nsMenuFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) |
317 | 0 | { |
318 | 0 | if (mReflowCallbackPosted) { |
319 | 0 | PresShell()->CancelReflowCallback(this); |
320 | 0 | mReflowCallbackPosted = false; |
321 | 0 | } |
322 | 0 |
|
323 | 0 | // Kill our timer if one is active. This is not strictly necessary as |
324 | 0 | // the pointer to this frame will be cleared from the mediator, but |
325 | 0 | // this is done for added safety. |
326 | 0 | if (mOpenTimer) { |
327 | 0 | mOpenTimer->Cancel(); |
328 | 0 | } |
329 | 0 |
|
330 | 0 | StopBlinking(); |
331 | 0 |
|
332 | 0 | // Null out the pointer to this frame in the mediator wrapper so that it |
333 | 0 | // doesn't try to interact with a deallocated frame. |
334 | 0 | mTimerMediator->ClearFrame(); |
335 | 0 |
|
336 | 0 | // if the menu content is just being hidden, it may be made visible again |
337 | 0 | // later, so make sure to clear the highlighting. |
338 | 0 | mContent->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, |
339 | 0 | false); |
340 | 0 |
|
341 | 0 | // are we our menu parent's current menu item? |
342 | 0 | nsMenuParent* menuParent = GetMenuParent(); |
343 | 0 | if (menuParent && menuParent->GetCurrentMenuItem() == this) { |
344 | 0 | // yes; tell it that we're going away |
345 | 0 | menuParent->CurrentMenuIsBeingDestroyed(); |
346 | 0 | } |
347 | 0 |
|
348 | 0 | nsFrameList* popupList = GetPopupList(); |
349 | 0 | if (popupList) { |
350 | 0 | popupList->DestroyFramesFrom(aDestructRoot, aPostDestroyData); |
351 | 0 | DestroyPopupList(); |
352 | 0 | } |
353 | 0 |
|
354 | 0 | nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData); |
355 | 0 | } |
356 | | |
357 | | void |
358 | | nsMenuFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, |
359 | | const nsDisplayListSet& aLists) |
360 | 0 | { |
361 | 0 | if (!aBuilder->IsForEventDelivery()) { |
362 | 0 | nsBoxFrame::BuildDisplayListForChildren(aBuilder, aLists); |
363 | 0 | return; |
364 | 0 | } |
365 | 0 | |
366 | 0 | nsDisplayListCollection set(aBuilder); |
367 | 0 | nsBoxFrame::BuildDisplayListForChildren(aBuilder, set); |
368 | 0 |
|
369 | 0 | WrapListsInRedirector(aBuilder, set, aLists); |
370 | 0 | } |
371 | | |
372 | | nsresult |
373 | | nsMenuFrame::HandleEvent(nsPresContext* aPresContext, |
374 | | WidgetGUIEvent* aEvent, |
375 | | nsEventStatus* aEventStatus) |
376 | 0 | { |
377 | 0 | NS_ENSURE_ARG_POINTER(aEventStatus); |
378 | 0 | if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { |
379 | 0 | return NS_OK; |
380 | 0 | } |
381 | 0 | nsMenuParent* menuParent = GetMenuParent(); |
382 | 0 | if (menuParent && menuParent->IsMenuLocked()) { |
383 | 0 | return NS_OK; |
384 | 0 | } |
385 | 0 | |
386 | 0 | AutoWeakFrame weakFrame(this); |
387 | 0 | if (*aEventStatus == nsEventStatus_eIgnore) |
388 | 0 | *aEventStatus = nsEventStatus_eConsumeDoDefault; |
389 | 0 |
|
390 | 0 | // If a menu just opened, ignore the mouseup event that might occur after a |
391 | 0 | // the mousedown event that opened it. However, if a different mousedown |
392 | 0 | // event occurs, just clear this flag. |
393 | 0 | if (gMenuJustOpenedOrClosed) { |
394 | 0 | if (aEvent->mMessage == eMouseDown) { |
395 | 0 | gMenuJustOpenedOrClosed = false; |
396 | 0 | } else if (aEvent->mMessage == eMouseUp) { |
397 | 0 | return NS_OK; |
398 | 0 | } |
399 | 0 | } |
400 | 0 | |
401 | 0 | bool onmenu = IsOnMenu(); |
402 | 0 |
|
403 | 0 | if (aEvent->mMessage == eKeyPress && !IsDisabled()) { |
404 | 0 | WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); |
405 | 0 | uint32_t keyCode = keyEvent->mKeyCode; |
406 | | #ifdef XP_MACOSX |
407 | | // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed) |
408 | | if (!IsOpen() && ((keyEvent->mCharCode == ' ' && !keyEvent->IsMeta()) || |
409 | | (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) { |
410 | | |
411 | | // When pressing space, don't open the menu if performing an incremental search. |
412 | | if (keyEvent->mCharCode != ' ' || |
413 | | !nsMenuPopupFrame::IsWithinIncrementalTime(keyEvent->mTime)) { |
414 | | *aEventStatus = nsEventStatus_eConsumeNoDefault; |
415 | | OpenMenu(false); |
416 | | } |
417 | | } |
418 | | #else |
419 | | // On other platforms, toggle menulist on unmodified F4 or Alt arrow |
420 | 0 | if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) || |
421 | 0 | ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) { |
422 | 0 | *aEventStatus = nsEventStatus_eConsumeNoDefault; |
423 | 0 | ToggleMenuState(); |
424 | 0 | } |
425 | 0 | #endif |
426 | 0 | } |
427 | 0 | else if (aEvent->mMessage == eMouseDown && |
428 | 0 | aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton && |
429 | 0 | !IsDisabled() && IsMenu()) { |
430 | 0 | // The menu item was selected. Bring up the menu. |
431 | 0 | // We have children. |
432 | 0 | // Don't prevent the default action here, since that will also cancel |
433 | 0 | // potential drag starts. |
434 | 0 | if (!menuParent || menuParent->IsMenuBar()) { |
435 | 0 | ToggleMenuState(); |
436 | 0 | } |
437 | 0 | else { |
438 | 0 | if (!IsOpen()) { |
439 | 0 | menuParent->ChangeMenuItem(this, false, false); |
440 | 0 | OpenMenu(false); |
441 | 0 | } |
442 | 0 | } |
443 | 0 | } |
444 | 0 | else if ( |
445 | 0 | #ifndef NSCONTEXTMENUISMOUSEUP |
446 | 0 | (aEvent->mMessage == eMouseUp && |
447 | 0 | aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) && |
448 | | #else |
449 | | aEvent->mMessage == eContextMenu && |
450 | | #endif |
451 | 0 | onmenu && !IsMenu() && !IsDisabled()) { |
452 | 0 | // if this menu is a context menu it accepts right-clicks...fire away! |
453 | 0 | // Make sure we cancel default processing of the context menu event so |
454 | 0 | // that it doesn't bubble and get seen again by the popuplistener and show |
455 | 0 | // another context menu. |
456 | 0 | // |
457 | 0 | // Furthermore (there's always more, isn't there?), on some platforms (win32 |
458 | 0 | // being one of them) we get the context menu event on a mouse up while |
459 | 0 | // on others we get it on a mouse down. For the ones where we get it on a |
460 | 0 | // mouse down, we must continue listening for the right button up event to |
461 | 0 | // dismiss the menu. |
462 | 0 | if (menuParent->IsContextMenu()) { |
463 | 0 | *aEventStatus = nsEventStatus_eConsumeNoDefault; |
464 | 0 | Execute(aEvent); |
465 | 0 | } |
466 | 0 | } |
467 | 0 | else if (aEvent->mMessage == eMouseUp && |
468 | 0 | aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton && |
469 | 0 | !IsMenu() && !IsDisabled()) { |
470 | 0 | // Execute the execute event handler. |
471 | 0 | *aEventStatus = nsEventStatus_eConsumeNoDefault; |
472 | 0 | Execute(aEvent); |
473 | 0 | } |
474 | 0 | else if (aEvent->mMessage == eMouseOut) { |
475 | 0 | // Kill our timer if one is active. |
476 | 0 | if (mOpenTimer) { |
477 | 0 | mOpenTimer->Cancel(); |
478 | 0 | mOpenTimer = nullptr; |
479 | 0 | } |
480 | 0 |
|
481 | 0 | // Deactivate the menu. |
482 | 0 | if (menuParent) { |
483 | 0 | bool onmenubar = menuParent->IsMenuBar(); |
484 | 0 | if (!(onmenubar && menuParent->IsActive())) { |
485 | 0 | if (IsMenu() && !onmenubar && IsOpen()) { |
486 | 0 | // Submenus don't get closed up immediately. |
487 | 0 | } |
488 | 0 | else if (this == menuParent->GetCurrentMenuItem() |
489 | | #ifdef XP_WIN |
490 | | && GetParentMenuListType() == eNotMenuList |
491 | | #endif |
492 | 0 | ) { |
493 | 0 | menuParent->ChangeMenuItem(nullptr, false, false); |
494 | 0 | } |
495 | 0 | } |
496 | 0 | } |
497 | 0 | } |
498 | 0 | else if (aEvent->mMessage == eMouseMove && |
499 | 0 | (onmenu || (menuParent && menuParent->IsMenuBar()))) { |
500 | 0 | if (gMenuJustOpenedOrClosed) { |
501 | 0 | gMenuJustOpenedOrClosed = false; |
502 | 0 | return NS_OK; |
503 | 0 | } |
504 | 0 | |
505 | 0 | if (IsDisabled() && GetParentMenuListType() != eNotMenuList) { |
506 | 0 | return NS_OK; |
507 | 0 | } |
508 | 0 | |
509 | 0 | // Let the menu parent know we're the new item. |
510 | 0 | menuParent->ChangeMenuItem(this, false, false); |
511 | 0 | NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); |
512 | 0 | NS_ENSURE_TRUE(menuParent, NS_OK); |
513 | 0 |
|
514 | 0 | // we need to check if we really became the current menu |
515 | 0 | // item or not |
516 | 0 | nsMenuFrame *realCurrentItem = menuParent->GetCurrentMenuItem(); |
517 | 0 | if (realCurrentItem != this) { |
518 | 0 | // we didn't (presumably because a context menu was active) |
519 | 0 | return NS_OK; |
520 | 0 | } |
521 | 0 | |
522 | 0 | // Hovering over a menu in a popup should open it without a need for a click. |
523 | 0 | // A timer is used so that it doesn't open if the user moves the mouse quickly |
524 | 0 | // past the menu. This conditional check ensures that only menus have this |
525 | 0 | // behaviour |
526 | 0 | if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer && !menuParent->IsMenuBar()) { |
527 | 0 | int32_t menuDelay = |
528 | 0 | LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms |
529 | 0 |
|
530 | 0 | // We're a menu, we're built, we're closed, and no timer has been kicked off. |
531 | 0 | NS_NewTimerWithCallback(getter_AddRefs(mOpenTimer), |
532 | 0 | mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT, |
533 | 0 | mContent->OwnerDoc()->EventTargetFor(TaskCategory::Other)); |
534 | 0 | } |
535 | 0 | } |
536 | 0 |
|
537 | 0 | return NS_OK; |
538 | 0 | } |
539 | | |
540 | | void |
541 | | nsMenuFrame::ToggleMenuState() |
542 | 0 | { |
543 | 0 | if (IsOpen()) |
544 | 0 | CloseMenu(false); |
545 | 0 | else |
546 | 0 | OpenMenu(false); |
547 | 0 | } |
548 | | |
549 | | void |
550 | | nsMenuFrame::PopupOpened() |
551 | 0 | { |
552 | 0 | gMenuJustOpenedOrClosed = true; |
553 | 0 |
|
554 | 0 | AutoWeakFrame weakFrame(this); |
555 | 0 | mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::open, |
556 | 0 | NS_LITERAL_STRING("true"), true); |
557 | 0 | if (!weakFrame.IsAlive()) |
558 | 0 | return; |
559 | 0 | |
560 | 0 | nsMenuParent* menuParent = GetMenuParent(); |
561 | 0 | if (menuParent) { |
562 | 0 | menuParent->SetActive(true); |
563 | 0 | // Make sure the current menu which is being toggled on |
564 | 0 | // the menubar is highlighted |
565 | 0 | menuParent->SetCurrentMenuItem(this); |
566 | 0 | } |
567 | 0 | } |
568 | | |
569 | | void |
570 | | nsMenuFrame::PopupClosed(bool aDeselectMenu) |
571 | 0 | { |
572 | 0 | AutoWeakFrame weakFrame(this); |
573 | 0 | nsContentUtils::AddScriptRunner( |
574 | 0 | new nsUnsetAttrRunnable(mContent->AsElement(), nsGkAtoms::open)); |
575 | 0 | if (!weakFrame.IsAlive()) |
576 | 0 | return; |
577 | 0 | |
578 | 0 | // if the popup is for a menu on a menubar, inform menubar to deactivate |
579 | 0 | nsMenuParent* menuParent = GetMenuParent(); |
580 | 0 | if (menuParent && menuParent->MenuClosed()) { |
581 | 0 | if (aDeselectMenu) { |
582 | 0 | SelectMenu(false); |
583 | 0 | } else { |
584 | 0 | // We are not deselecting the parent menu while closing the popup, so send |
585 | 0 | // a DOMMenuItemActive event to the menu to indicate that the menu is |
586 | 0 | // becoming active again. |
587 | 0 | nsMenuFrame *current = menuParent->GetCurrentMenuItem(); |
588 | 0 | if (current) { |
589 | 0 | // However, if the menu is a descendant on a menubar, and the menubar |
590 | 0 | // has the 'stay active' flag set, it means that the menubar is switching |
591 | 0 | // to another toplevel menu entirely (for example from Edit to View), so |
592 | 0 | // don't fire the DOMMenuItemActive event or else we'll send extraneous |
593 | 0 | // events for submenus. nsMenuBarFrame::ChangeMenuItem has already deselected |
594 | 0 | // the old menu, so it doesn't need to happen again here, and the new |
595 | 0 | // menu can be selected right away. |
596 | 0 | nsIFrame* parent = current; |
597 | 0 | while (parent) { |
598 | 0 | nsMenuBarFrame* menubar = do_QueryFrame(parent); |
599 | 0 | if (menubar && menubar->GetStayActive()) |
600 | 0 | return; |
601 | 0 | |
602 | 0 | parent = parent->GetParent(); |
603 | 0 | } |
604 | 0 |
|
605 | 0 | nsCOMPtr<nsIRunnable> event = |
606 | 0 | new nsMenuActivateEvent(current->GetContent()->AsElement(), |
607 | 0 | PresContext(), true); |
608 | 0 | mContent->OwnerDoc()->Dispatch(TaskCategory::Other, |
609 | 0 | event.forget()); |
610 | 0 | } |
611 | 0 | } |
612 | 0 | } |
613 | 0 | } |
614 | | |
615 | | NS_IMETHODIMP |
616 | | nsMenuFrame::SelectMenu(bool aActivateFlag) |
617 | 0 | { |
618 | 0 | if (mContent) { |
619 | 0 | // When a menu opens a submenu, the mouse will often be moved onto a |
620 | 0 | // sibling before moving onto an item within the submenu, causing the |
621 | 0 | // parent to become deselected. We need to ensure that the parent menu |
622 | 0 | // is reselected when an item in the submenu is selected, so navigate up |
623 | 0 | // from the item to its popup, and then to the popup above that. |
624 | 0 | if (aActivateFlag) { |
625 | 0 | nsIFrame* parent = GetParent(); |
626 | 0 | while (parent) { |
627 | 0 | nsMenuPopupFrame* menupopup = do_QueryFrame(parent); |
628 | 0 | if (menupopup) { |
629 | 0 | // a menu is always the direct parent of a menupopup |
630 | 0 | nsMenuFrame* menu = do_QueryFrame(menupopup->GetParent()); |
631 | 0 | if (menu) { |
632 | 0 | // a popup however is not necessarily the direct parent of a menu |
633 | 0 | nsIFrame* popupParent = menu->GetParent(); |
634 | 0 | while (popupParent) { |
635 | 0 | menupopup = do_QueryFrame(popupParent); |
636 | 0 | if (menupopup) { |
637 | 0 | menupopup->SetCurrentMenuItem(menu); |
638 | 0 | break; |
639 | 0 | } |
640 | 0 | popupParent = popupParent->GetParent(); |
641 | 0 | } |
642 | 0 | } |
643 | 0 | break; |
644 | 0 | } |
645 | 0 | parent = parent->GetParent(); |
646 | 0 | } |
647 | 0 | } |
648 | 0 |
|
649 | 0 | // cancel the close timer if selecting a menu within the popup to be closed |
650 | 0 | nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); |
651 | 0 | if (pm) { |
652 | 0 | nsMenuParent* menuParent = GetMenuParent(); |
653 | 0 | pm->CancelMenuTimer(menuParent); |
654 | 0 | } |
655 | 0 |
|
656 | 0 | nsCOMPtr<nsIRunnable> event = |
657 | 0 | new nsMenuActivateEvent(mContent->AsElement(), PresContext(), aActivateFlag); |
658 | 0 | mContent->OwnerDoc()->Dispatch(TaskCategory::Other, |
659 | 0 | event.forget()); |
660 | 0 | } |
661 | 0 |
|
662 | 0 | return NS_OK; |
663 | 0 | } |
664 | | |
665 | | nsresult |
666 | | nsMenuFrame::AttributeChanged(int32_t aNameSpaceID, |
667 | | nsAtom* aAttribute, |
668 | | int32_t aModType) |
669 | 0 | { |
670 | 0 | if (aAttribute == nsGkAtoms::acceltext && mIgnoreAccelTextChange) { |
671 | 0 | // Reset the flag so that only one change is ignored. |
672 | 0 | mIgnoreAccelTextChange = false; |
673 | 0 | return NS_OK; |
674 | 0 | } |
675 | 0 | |
676 | 0 | if (aAttribute == nsGkAtoms::checked || |
677 | 0 | aAttribute == nsGkAtoms::acceltext || |
678 | 0 | aAttribute == nsGkAtoms::key || |
679 | 0 | aAttribute == nsGkAtoms::type || |
680 | 0 | aAttribute == nsGkAtoms::name) { |
681 | 0 | nsCOMPtr<nsIRunnable> event = |
682 | 0 | new nsMenuAttributeChangedEvent(this, aAttribute); |
683 | 0 | nsContentUtils::AddScriptRunner(event); |
684 | 0 | } |
685 | 0 | return NS_OK; |
686 | 0 | } |
687 | | |
688 | | nsIContent* |
689 | | nsMenuFrame::GetAnchor() |
690 | 0 | { |
691 | 0 | mozilla::dom::Element* anchor = nullptr; |
692 | 0 |
|
693 | 0 | nsAutoString id; |
694 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::anchor, id); |
695 | 0 | if (!id.IsEmpty()) { |
696 | 0 | nsIDocument* doc = mContent->OwnerDoc(); |
697 | 0 |
|
698 | 0 | anchor = |
699 | 0 | doc->GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, id); |
700 | 0 | if (!anchor) { |
701 | 0 | anchor = doc->GetElementById(id); |
702 | 0 | } |
703 | 0 | } |
704 | 0 |
|
705 | 0 | // Always return the menu's content if the anchor wasn't set or wasn't found. |
706 | 0 | return anchor && anchor->GetPrimaryFrame() ? anchor : GetContent(); |
707 | 0 | } |
708 | | |
709 | | void |
710 | | nsMenuFrame::OpenMenu(bool aSelectFirstItem) |
711 | 0 | { |
712 | 0 | if (!mContent) |
713 | 0 | return; |
714 | 0 | |
715 | 0 | nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); |
716 | 0 | if (pm) { |
717 | 0 | pm->KillMenuTimer(); |
718 | 0 | // This opens the menu asynchronously |
719 | 0 | pm->ShowMenu(mContent, aSelectFirstItem, true); |
720 | 0 | } |
721 | 0 | } |
722 | | |
723 | | void |
724 | | nsMenuFrame::CloseMenu(bool aDeselectMenu) |
725 | 0 | { |
726 | 0 | gMenuJustOpenedOrClosed = true; |
727 | 0 |
|
728 | 0 | // Close the menu asynchronously |
729 | 0 | nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); |
730 | 0 | if (pm && HasPopup()) |
731 | 0 | pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true, false); |
732 | 0 | } |
733 | | |
734 | | bool |
735 | | nsMenuFrame::IsSizedToPopup(nsIContent* aContent, bool aRequireAlways) |
736 | 0 | { |
737 | 0 | MOZ_ASSERT(aContent->IsElement()); |
738 | 0 | nsAutoString sizedToPopup; |
739 | 0 | aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup, |
740 | 0 | sizedToPopup); |
741 | 0 | return sizedToPopup.EqualsLiteral("always") || |
742 | 0 | (!aRequireAlways && sizedToPopup.EqualsLiteral("pref")); |
743 | 0 | } |
744 | | |
745 | | nsSize |
746 | | nsMenuFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) |
747 | 0 | { |
748 | 0 | nsSize size = nsBoxFrame::GetXULMinSize(aBoxLayoutState); |
749 | 0 | DISPLAY_MIN_SIZE(this, size); |
750 | 0 |
|
751 | 0 | if (IsSizedToPopup(mContent, true)) |
752 | 0 | SizeToPopup(aBoxLayoutState, size); |
753 | 0 |
|
754 | 0 | return size; |
755 | 0 | } |
756 | | |
757 | | NS_IMETHODIMP |
758 | | nsMenuFrame::DoXULLayout(nsBoxLayoutState& aState) |
759 | 0 | { |
760 | 0 | // lay us out |
761 | 0 | nsresult rv = nsBoxFrame::DoXULLayout(aState); |
762 | 0 |
|
763 | 0 | nsMenuPopupFrame* popupFrame = GetPopup(); |
764 | 0 | if (popupFrame) { |
765 | 0 | bool sizeToPopup = IsSizedToPopup(mContent, false); |
766 | 0 | popupFrame->LayoutPopup(aState, this, GetAnchor()->GetPrimaryFrame(), sizeToPopup); |
767 | 0 | } |
768 | 0 |
|
769 | 0 | return rv; |
770 | 0 | } |
771 | | |
772 | | // |
773 | | // Enter |
774 | | // |
775 | | // Called when the user hits the <Enter>/<Return> keys or presses the |
776 | | // shortcut key. If this is a leaf item, the item's action will be executed. |
777 | | // In either case, do nothing if the item is disabled. |
778 | | // |
779 | | nsMenuFrame* |
780 | | nsMenuFrame::Enter(WidgetGUIEvent* aEvent) |
781 | 0 | { |
782 | 0 | if (IsDisabled()) { |
783 | | #ifdef XP_WIN |
784 | | // behavior on Windows - close the popup chain |
785 | | nsMenuParent* menuParent = GetMenuParent(); |
786 | | if (menuParent) { |
787 | | nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); |
788 | | if (pm) { |
789 | | nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny); |
790 | | if (popup) |
791 | | pm->HidePopup(popup->GetContent(), true, true, true, false); |
792 | | } |
793 | | } |
794 | | #endif // #ifdef XP_WIN |
795 | | // this menu item was disabled - exit |
796 | 0 | return nullptr; |
797 | 0 | } |
798 | 0 |
|
799 | 0 | if (!IsOpen()) { |
800 | 0 | // The enter key press applies to us. |
801 | 0 | nsMenuParent* menuParent = GetMenuParent(); |
802 | 0 | if (!IsMenu() && menuParent) |
803 | 0 | Execute(aEvent); // Execute our event handler |
804 | 0 | else |
805 | 0 | return this; |
806 | 0 | } |
807 | 0 | |
808 | 0 | return nullptr; |
809 | 0 | } |
810 | | |
811 | | bool |
812 | | nsMenuFrame::IsOpen() |
813 | 0 | { |
814 | 0 | nsMenuPopupFrame* popupFrame = GetPopup(); |
815 | 0 | return popupFrame && popupFrame->IsOpen(); |
816 | 0 | } |
817 | | |
818 | | bool |
819 | | nsMenuFrame::IsMenu() |
820 | 0 | { |
821 | 0 | return mIsMenu; |
822 | 0 | } |
823 | | |
824 | | nsMenuListType |
825 | | nsMenuFrame::GetParentMenuListType() |
826 | 0 | { |
827 | 0 | nsMenuParent* menuParent = GetMenuParent(); |
828 | 0 | if (menuParent && menuParent->IsMenu()) { |
829 | 0 | nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(menuParent); |
830 | 0 | nsIFrame* parentMenu = popupFrame->GetParent(); |
831 | 0 | if (parentMenu) { |
832 | 0 | nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent()); |
833 | 0 | if (menulist) { |
834 | 0 | return eReadonlyMenuList; |
835 | 0 | } |
836 | 0 | } |
837 | 0 | } |
838 | 0 | return eNotMenuList; |
839 | 0 | } |
840 | | |
841 | | nsresult |
842 | | nsMenuFrame::Notify(nsITimer* aTimer) |
843 | 0 | { |
844 | 0 | // Our timer has fired. |
845 | 0 | if (aTimer == mOpenTimer.get()) { |
846 | 0 | mOpenTimer = nullptr; |
847 | 0 |
|
848 | 0 | nsMenuParent* menuParent = GetMenuParent(); |
849 | 0 | if (!IsOpen() && menuParent) { |
850 | 0 | // make sure we didn't open a context menu in the meantime |
851 | 0 | // (i.e. the user right-clicked while hovering over a submenu). |
852 | 0 | nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); |
853 | 0 | if (pm) { |
854 | 0 | if ((!pm->HasContextMenu(nullptr) || menuParent->IsContextMenu()) && |
855 | 0 | mContent->AsElement()->AttrValueIs(kNameSpaceID_None, |
856 | 0 | nsGkAtoms::menuactive, |
857 | 0 | nsGkAtoms::_true, eCaseMatters)) { |
858 | 0 | OpenMenu(false); |
859 | 0 | } |
860 | 0 | } |
861 | 0 | } |
862 | 0 | } else if (aTimer == mBlinkTimer) { |
863 | 0 | switch (mBlinkState++) { |
864 | 0 | case 0: |
865 | 0 | NS_ASSERTION(false, "Blink timer fired while not blinking"); |
866 | 0 | StopBlinking(); |
867 | 0 | break; |
868 | 0 | case 1: |
869 | 0 | { |
870 | 0 | // Turn the highlight back on and wait for a while before closing the menu. |
871 | 0 | AutoWeakFrame weakFrame(this); |
872 | 0 | mContent->AsElement()->SetAttr(kNameSpaceID_None, |
873 | 0 | nsGkAtoms::menuactive, |
874 | 0 | NS_LITERAL_STRING("true"), true); |
875 | 0 | if (weakFrame.IsAlive()) { |
876 | 0 | aTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT); |
877 | 0 | } |
878 | 0 | } |
879 | 0 | break; |
880 | 0 | default: { |
881 | 0 | nsMenuParent* menuParent = GetMenuParent(); |
882 | 0 | if (menuParent) { |
883 | 0 | menuParent->LockMenuUntilClosed(false); |
884 | 0 | } |
885 | 0 | PassMenuCommandEventToPopupManager(); |
886 | 0 | StopBlinking(); |
887 | 0 | break; |
888 | 0 | } |
889 | 0 | } |
890 | 0 | } |
891 | 0 |
|
892 | 0 | return NS_OK; |
893 | 0 | } |
894 | | |
895 | | bool |
896 | | nsMenuFrame::IsDisabled() |
897 | 0 | { |
898 | 0 | return mContent->AsElement()->AttrValueIs(kNameSpaceID_None, |
899 | 0 | nsGkAtoms::disabled, |
900 | 0 | nsGkAtoms::_true, eCaseMatters); |
901 | 0 | } |
902 | | |
903 | | void |
904 | | nsMenuFrame::UpdateMenuType() |
905 | 0 | { |
906 | 0 | static Element::AttrValuesArray strings[] = |
907 | 0 | {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr}; |
908 | 0 | switch (mContent->AsElement()->FindAttrValueIn(kNameSpaceID_None, |
909 | 0 | nsGkAtoms::type, |
910 | 0 | strings, eCaseMatters)) { |
911 | 0 | case 0: mType = eMenuType_Checkbox; break; |
912 | 0 | case 1: |
913 | 0 | mType = eMenuType_Radio; |
914 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, mGroupName); |
915 | 0 | break; |
916 | 0 |
|
917 | 0 | default: |
918 | 0 | if (mType != eMenuType_Normal) { |
919 | 0 | AutoWeakFrame weakFrame(this); |
920 | 0 | mContent->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, |
921 | 0 | true); |
922 | 0 | NS_ENSURE_TRUE_VOID(weakFrame.IsAlive()); |
923 | 0 | } |
924 | 0 | mType = eMenuType_Normal; |
925 | 0 | break; |
926 | 0 | } |
927 | 0 | UpdateMenuSpecialState(); |
928 | 0 | } |
929 | | |
930 | | /* update checked-ness for type="checkbox" and type="radio" */ |
931 | | void |
932 | | nsMenuFrame::UpdateMenuSpecialState() |
933 | 0 | { |
934 | 0 | bool newChecked = |
935 | 0 | mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked, |
936 | 0 | nsGkAtoms::_true, eCaseMatters); |
937 | 0 | if (newChecked == mChecked) { |
938 | 0 | /* checked state didn't change */ |
939 | 0 |
|
940 | 0 | if (mType != eMenuType_Radio) |
941 | 0 | return; // only Radio possibly cares about other kinds of change |
942 | 0 | |
943 | 0 | if (!mChecked || mGroupName.IsEmpty()) |
944 | 0 | return; // no interesting change |
945 | 0 | } else { |
946 | 0 | mChecked = newChecked; |
947 | 0 | if (mType != eMenuType_Radio || !mChecked) |
948 | 0 | /* |
949 | 0 | * Unchecking something requires no further changes, and only |
950 | 0 | * menuRadio has to do additional work when checked. |
951 | 0 | */ |
952 | 0 | return; |
953 | 0 | } |
954 | 0 | |
955 | 0 | /* |
956 | 0 | * If we get this far, we're type=radio, and: |
957 | 0 | * - our name= changed, or |
958 | 0 | * - we went from checked="false" to checked="true" |
959 | 0 | */ |
960 | 0 | |
961 | 0 | /* |
962 | 0 | * Behavioural note: |
963 | 0 | * If we're checked and renamed _into_ an existing radio group, we are |
964 | 0 | * made the new checked item, and we unselect the previous one. |
965 | 0 | * |
966 | 0 | * The only other reasonable behaviour would be to check for another selected |
967 | 0 | * item in that group. If found, unselect ourselves, otherwise we're the |
968 | 0 | * selected item. That, however, would be a lot more work, and I don't think |
969 | 0 | * it's better at all. |
970 | 0 | */ |
971 | 0 | |
972 | 0 | /* walk siblings, looking for the other checked item with the same name */ |
973 | 0 | // get the first sibling in this menu popup. This frame may be it, and if we're |
974 | 0 | // being called at creation time, this frame isn't yet in the parent's child list. |
975 | 0 | // All I'm saying is that this may fail, but it's most likely alright. |
976 | 0 | nsIFrame* firstMenuItem = nsXULPopupManager::GetNextMenuItem(GetParent(), nullptr, true, false); |
977 | 0 | nsIFrame* sib = firstMenuItem; |
978 | 0 | while (sib) { |
979 | 0 | nsMenuFrame* menu = do_QueryFrame(sib); |
980 | 0 | if (sib != this) { |
981 | 0 | if (menu && menu->GetMenuType() == eMenuType_Radio && |
982 | 0 | menu->IsChecked() && menu->GetRadioGroupName() == mGroupName) { |
983 | 0 | /* uncheck the old item */ |
984 | 0 | sib->GetContent()->AsElement()->UnsetAttr( |
985 | 0 | kNameSpaceID_None, nsGkAtoms::checked, true); |
986 | 0 | /* XXX in DEBUG, check to make sure that there aren't two checked items */ |
987 | 0 | return; |
988 | 0 | } |
989 | 0 | } |
990 | 0 | sib = nsXULPopupManager::GetNextMenuItem(GetParent(), menu, true, true); |
991 | 0 | if (sib == firstMenuItem) { |
992 | 0 | break; |
993 | 0 | } |
994 | 0 | } |
995 | 0 | } |
996 | | |
997 | | void |
998 | | nsMenuFrame::BuildAcceleratorText(bool aNotify) |
999 | 0 | { |
1000 | 0 | nsAutoString accelText; |
1001 | 0 |
|
1002 | 0 | if ((GetStateBits() & NS_STATE_ACCELTEXT_IS_DERIVED) == 0) { |
1003 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, |
1004 | 0 | accelText); |
1005 | 0 | if (!accelText.IsEmpty()) |
1006 | 0 | return; |
1007 | 0 | } |
1008 | 0 | // accelText is definitely empty here. |
1009 | 0 | |
1010 | 0 | // Now we're going to compute the accelerator text, so remember that we did. |
1011 | 0 | AddStateBits(NS_STATE_ACCELTEXT_IS_DERIVED); |
1012 | 0 |
|
1013 | 0 | // If anything below fails, just leave the accelerator text blank. |
1014 | 0 | AutoWeakFrame weakFrame(this); |
1015 | 0 | mContent->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, aNotify); |
1016 | 0 | NS_ENSURE_TRUE_VOID(weakFrame.IsAlive()); |
1017 | 0 |
|
1018 | 0 | // See if we have a key node and use that instead. |
1019 | 0 | nsAutoString keyValue; |
1020 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue); |
1021 | 0 | if (keyValue.IsEmpty()) |
1022 | 0 | return; |
1023 | 0 | |
1024 | 0 | // Turn the document into a DOM document so we can use getElementById |
1025 | 0 | nsIDocument *document = mContent->GetUncomposedDoc(); |
1026 | 0 | if (!document) |
1027 | 0 | return; |
1028 | 0 | |
1029 | 0 | //XXXsmaug If mContent is in shadow dom, should we use |
1030 | 0 | // ShadowRoot::GetElementById()? |
1031 | 0 | Element* keyElement = document->GetElementById(keyValue); |
1032 | 0 | if (!keyElement) { |
1033 | | #ifdef DEBUG |
1034 | | nsAutoString label; |
1035 | | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label); |
1036 | | nsAutoString msg = NS_LITERAL_STRING("Key '") + |
1037 | | keyValue + |
1038 | | NS_LITERAL_STRING("' of menu item '") + |
1039 | | label + |
1040 | | NS_LITERAL_STRING("' could not be found"); |
1041 | | NS_WARNING(NS_ConvertUTF16toUTF8(msg).get()); |
1042 | | #endif |
1043 | | return; |
1044 | 0 | } |
1045 | 0 |
|
1046 | 0 | // get the string to display as accelerator text |
1047 | 0 | // check the key element's attributes in this order: |
1048 | 0 | // |keytext|, |key|, |keycode| |
1049 | 0 | nsAutoString accelString; |
1050 | 0 | keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keytext, accelString); |
1051 | 0 |
|
1052 | 0 | if (accelString.IsEmpty()) { |
1053 | 0 | keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, accelString); |
1054 | 0 |
|
1055 | 0 | if (!accelString.IsEmpty()) { |
1056 | 0 | ToUpperCase(accelString); |
1057 | 0 | } else { |
1058 | 0 | nsAutoString keyCode; |
1059 | 0 | keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCode); |
1060 | 0 | ToUpperCase(keyCode); |
1061 | 0 |
|
1062 | 0 | nsresult rv; |
1063 | 0 | nsCOMPtr<nsIStringBundleService> bundleService = |
1064 | 0 | mozilla::services::GetStringBundleService(); |
1065 | 0 | if (bundleService) { |
1066 | 0 | nsCOMPtr<nsIStringBundle> bundle; |
1067 | 0 | rv = bundleService->CreateBundle( |
1068 | 0 | keyCode.EqualsLiteral("VK_RETURN") |
1069 | 0 | ? "chrome://global-platform/locale/platformKeys.properties" |
1070 | 0 | : "chrome://global/locale/keys.properties", |
1071 | 0 | getter_AddRefs(bundle)); |
1072 | 0 | if (NS_SUCCEEDED(rv) && bundle) { |
1073 | 0 | nsAutoString keyName; |
1074 | 0 | rv = bundle->GetStringFromName(NS_ConvertUTF16toUTF8(keyCode).get(), |
1075 | 0 | keyName); |
1076 | 0 | if (NS_SUCCEEDED(rv)) { |
1077 | 0 | accelString = keyName; |
1078 | 0 | } |
1079 | 0 | } |
1080 | 0 | } |
1081 | 0 |
|
1082 | 0 | // nothing usable found, bail |
1083 | 0 | if (accelString.IsEmpty()) |
1084 | 0 | return; |
1085 | 0 | } |
1086 | 0 | } |
1087 | 0 | |
1088 | 0 | nsAutoString modifiers; |
1089 | 0 | keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers); |
1090 | 0 |
|
1091 | 0 | char* str = ToNewCString(modifiers); |
1092 | 0 | char* newStr; |
1093 | 0 | char* token = nsCRT::strtok(str, ", \t", &newStr); |
1094 | 0 |
|
1095 | 0 | nsAutoString shiftText; |
1096 | 0 | nsAutoString altText; |
1097 | 0 | nsAutoString metaText; |
1098 | 0 | nsAutoString controlText; |
1099 | 0 | nsAutoString osText; |
1100 | 0 | nsAutoString modifierSeparator; |
1101 | 0 |
|
1102 | 0 | nsContentUtils::GetShiftText(shiftText); |
1103 | 0 | nsContentUtils::GetAltText(altText); |
1104 | 0 | nsContentUtils::GetMetaText(metaText); |
1105 | 0 | nsContentUtils::GetControlText(controlText); |
1106 | 0 | nsContentUtils::GetOSText(osText); |
1107 | 0 | nsContentUtils::GetModifierSeparatorText(modifierSeparator); |
1108 | 0 |
|
1109 | 0 | while (token) { |
1110 | 0 |
|
1111 | 0 | if (PL_strcmp(token, "shift") == 0) |
1112 | 0 | accelText += shiftText; |
1113 | 0 | else if (PL_strcmp(token, "alt") == 0) |
1114 | 0 | accelText += altText; |
1115 | 0 | else if (PL_strcmp(token, "meta") == 0) |
1116 | 0 | accelText += metaText; |
1117 | 0 | else if (PL_strcmp(token, "os") == 0) |
1118 | 0 | accelText += osText; |
1119 | 0 | else if (PL_strcmp(token, "control") == 0) |
1120 | 0 | accelText += controlText; |
1121 | 0 | else if (PL_strcmp(token, "accel") == 0) { |
1122 | 0 | switch (WidgetInputEvent::AccelModifier()) { |
1123 | 0 | case MODIFIER_META: |
1124 | 0 | accelText += metaText; |
1125 | 0 | break; |
1126 | 0 | case MODIFIER_OS: |
1127 | 0 | accelText += osText; |
1128 | 0 | break; |
1129 | 0 | case MODIFIER_ALT: |
1130 | 0 | accelText += altText; |
1131 | 0 | break; |
1132 | 0 | case MODIFIER_CONTROL: |
1133 | 0 | accelText += controlText; |
1134 | 0 | break; |
1135 | 0 | default: |
1136 | 0 | MOZ_CRASH( |
1137 | 0 | "Handle the new result of WidgetInputEvent::AccelModifier()"); |
1138 | 0 | break; |
1139 | 0 | } |
1140 | 0 | } |
1141 | 0 | |
1142 | 0 | accelText += modifierSeparator; |
1143 | 0 |
|
1144 | 0 | token = nsCRT::strtok(newStr, ", \t", &newStr); |
1145 | 0 | } |
1146 | 0 |
|
1147 | 0 | free(str); |
1148 | 0 |
|
1149 | 0 | accelText += accelString; |
1150 | 0 |
|
1151 | 0 | mIgnoreAccelTextChange = true; |
1152 | 0 | mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, |
1153 | 0 | accelText, aNotify); |
1154 | 0 | NS_ENSURE_TRUE_VOID(weakFrame.IsAlive()); |
1155 | 0 |
|
1156 | 0 | mIgnoreAccelTextChange = false; |
1157 | 0 | } |
1158 | | |
1159 | | void |
1160 | | nsMenuFrame::Execute(WidgetGUIEvent* aEvent) |
1161 | 0 | { |
1162 | 0 | // flip "checked" state if we're a checkbox menu, or an un-checked radio menu |
1163 | 0 | bool needToFlipChecked = false; |
1164 | 0 | if (mType == eMenuType_Checkbox || (mType == eMenuType_Radio && !mChecked)) { |
1165 | 0 | needToFlipChecked = !mContent->AsElement()->AttrValueIs(kNameSpaceID_None, |
1166 | 0 | nsGkAtoms::autocheck, |
1167 | 0 | nsGkAtoms::_false, |
1168 | 0 | eCaseMatters); |
1169 | 0 | } |
1170 | 0 |
|
1171 | 0 | nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1")); |
1172 | 0 | if (sound) |
1173 | 0 | sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE); |
1174 | 0 |
|
1175 | 0 | StartBlinking(aEvent, needToFlipChecked); |
1176 | 0 | } |
1177 | | |
1178 | | bool |
1179 | | nsMenuFrame::ShouldBlink() |
1180 | 0 | { |
1181 | 0 | int32_t shouldBlink = |
1182 | 0 | LookAndFeel::GetInt(LookAndFeel::eIntID_ChosenMenuItemsShouldBlink, 0); |
1183 | 0 | if (!shouldBlink) |
1184 | 0 | return false; |
1185 | 0 | |
1186 | 0 | return true; |
1187 | 0 | } |
1188 | | |
1189 | | void |
1190 | | nsMenuFrame::StartBlinking(WidgetGUIEvent* aEvent, bool aFlipChecked) |
1191 | 0 | { |
1192 | 0 | StopBlinking(); |
1193 | 0 | CreateMenuCommandEvent(aEvent, aFlipChecked); |
1194 | 0 |
|
1195 | 0 | if (!ShouldBlink()) { |
1196 | 0 | PassMenuCommandEventToPopupManager(); |
1197 | 0 | return; |
1198 | 0 | } |
1199 | 0 | |
1200 | 0 | // Blink off. |
1201 | 0 | AutoWeakFrame weakFrame(this); |
1202 | 0 | mContent->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true); |
1203 | 0 | if (!weakFrame.IsAlive()) |
1204 | 0 | return; |
1205 | 0 | |
1206 | 0 | nsMenuParent* menuParent = GetMenuParent(); |
1207 | 0 | if (menuParent) { |
1208 | 0 | // Make this menu ignore events from now on. |
1209 | 0 | menuParent->LockMenuUntilClosed(true); |
1210 | 0 | } |
1211 | 0 |
|
1212 | 0 | // Set up a timer to blink back on. |
1213 | 0 | NS_NewTimerWithCallback(getter_AddRefs(mBlinkTimer), |
1214 | 0 | mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT, |
1215 | 0 | mContent->OwnerDoc()->EventTargetFor(TaskCategory::Other)); |
1216 | 0 | mBlinkState = 1; |
1217 | 0 | } |
1218 | | |
1219 | | void |
1220 | | nsMenuFrame::StopBlinking() |
1221 | 0 | { |
1222 | 0 | mBlinkState = 0; |
1223 | 0 | if (mBlinkTimer) { |
1224 | 0 | mBlinkTimer->Cancel(); |
1225 | 0 | mBlinkTimer = nullptr; |
1226 | 0 | } |
1227 | 0 | mDelayedMenuCommandEvent = nullptr; |
1228 | 0 | } |
1229 | | |
1230 | | void |
1231 | | nsMenuFrame::CreateMenuCommandEvent(WidgetGUIEvent* aEvent, bool aFlipChecked) |
1232 | 0 | { |
1233 | 0 | // Create a trusted event if the triggering event was trusted, or if |
1234 | 0 | // we're called from chrome code (since at least one of our caller |
1235 | 0 | // passes in a null event). |
1236 | 0 | bool isTrusted = aEvent ? aEvent->IsTrusted() : |
1237 | 0 | nsContentUtils::IsCallerChrome(); |
1238 | 0 |
|
1239 | 0 | bool shift = false, control = false, alt = false, meta = false; |
1240 | 0 | WidgetInputEvent* inputEvent = aEvent ? aEvent->AsInputEvent() : nullptr; |
1241 | 0 | if (inputEvent) { |
1242 | 0 | shift = inputEvent->IsShift(); |
1243 | 0 | control = inputEvent->IsControl(); |
1244 | 0 | alt = inputEvent->IsAlt(); |
1245 | 0 | meta = inputEvent->IsMeta(); |
1246 | 0 | } |
1247 | 0 |
|
1248 | 0 | // Because the command event is firing asynchronously, a flag is needed to |
1249 | 0 | // indicate whether user input is being handled. This ensures that a popup |
1250 | 0 | // window won't get blocked. |
1251 | 0 | bool userinput = EventStateManager::IsHandlingUserInput(); |
1252 | 0 |
|
1253 | 0 | mDelayedMenuCommandEvent = |
1254 | 0 | new nsXULMenuCommandEvent(mContent->AsElement(), isTrusted, shift, control, |
1255 | 0 | alt, meta, userinput, aFlipChecked); |
1256 | 0 | } |
1257 | | |
1258 | | void |
1259 | | nsMenuFrame::PassMenuCommandEventToPopupManager() |
1260 | 0 | { |
1261 | 0 | nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); |
1262 | 0 | nsMenuParent* menuParent = GetMenuParent(); |
1263 | 0 | if (pm && menuParent && mDelayedMenuCommandEvent) { |
1264 | 0 | pm->ExecuteMenu(mContent, mDelayedMenuCommandEvent); |
1265 | 0 | } |
1266 | 0 | mDelayedMenuCommandEvent = nullptr; |
1267 | 0 | } |
1268 | | |
1269 | | void |
1270 | | nsMenuFrame::RemoveFrame(ChildListID aListID, |
1271 | | nsIFrame* aOldFrame) |
1272 | 0 | { |
1273 | 0 | nsFrameList* popupList = GetPopupList(); |
1274 | 0 | if (popupList && popupList->FirstChild() == aOldFrame) { |
1275 | 0 | popupList->RemoveFirstChild(); |
1276 | 0 | aOldFrame->Destroy(); |
1277 | 0 | DestroyPopupList(); |
1278 | 0 | PresShell()-> |
1279 | 0 | FrameNeedsReflow(this, nsIPresShell::eTreeChange, |
1280 | 0 | NS_FRAME_HAS_DIRTY_CHILDREN); |
1281 | 0 | return; |
1282 | 0 | } |
1283 | 0 | nsBoxFrame::RemoveFrame(aListID, aOldFrame); |
1284 | 0 | } |
1285 | | |
1286 | | void |
1287 | | nsMenuFrame::InsertFrames(ChildListID aListID, |
1288 | | nsIFrame* aPrevFrame, |
1289 | | nsFrameList& aFrameList) |
1290 | 0 | { |
1291 | 0 | if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { |
1292 | 0 | SetPopupFrame(aFrameList); |
1293 | 0 | if (HasPopup()) { |
1294 | 0 | PresShell()-> |
1295 | 0 | FrameNeedsReflow(this, nsIPresShell::eTreeChange, |
1296 | 0 | NS_FRAME_HAS_DIRTY_CHILDREN); |
1297 | 0 | } |
1298 | 0 | } |
1299 | 0 |
|
1300 | 0 | if (aFrameList.IsEmpty()) |
1301 | 0 | return; |
1302 | 0 | |
1303 | 0 | if (MOZ_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) { |
1304 | 0 | aPrevFrame = nullptr; |
1305 | 0 | } |
1306 | 0 |
|
1307 | 0 | nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); |
1308 | 0 | } |
1309 | | |
1310 | | void |
1311 | | nsMenuFrame::AppendFrames(ChildListID aListID, |
1312 | | nsFrameList& aFrameList) |
1313 | 0 | { |
1314 | 0 | if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { |
1315 | 0 | SetPopupFrame(aFrameList); |
1316 | 0 | if (HasPopup()) { |
1317 | 0 | PresShell()-> |
1318 | 0 | FrameNeedsReflow(this, nsIPresShell::eTreeChange, |
1319 | 0 | NS_FRAME_HAS_DIRTY_CHILDREN); |
1320 | 0 | } |
1321 | 0 | } |
1322 | 0 |
|
1323 | 0 | if (aFrameList.IsEmpty()) |
1324 | 0 | return; |
1325 | 0 | |
1326 | 0 | nsBoxFrame::AppendFrames(aListID, aFrameList); |
1327 | 0 | } |
1328 | | |
1329 | | bool |
1330 | | nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize) |
1331 | 0 | { |
1332 | 0 | if (!IsXULCollapsed()) { |
1333 | 0 | bool widthSet, heightSet; |
1334 | 0 | nsSize tmpSize(-1, 0); |
1335 | 0 | nsIFrame::AddXULPrefSize(this, tmpSize, widthSet, heightSet); |
1336 | 0 | if (!widthSet && GetXULFlex() == 0) { |
1337 | 0 | nsMenuPopupFrame* popupFrame = GetPopup(); |
1338 | 0 | if (!popupFrame) |
1339 | 0 | return false; |
1340 | 0 | tmpSize = popupFrame->GetXULPrefSize(aState); |
1341 | 0 |
|
1342 | 0 | // Produce a size such that: |
1343 | 0 | // (1) the menu and its popup can be the same width |
1344 | 0 | // (2) there's enough room in the menu for the content and its |
1345 | 0 | // border-padding |
1346 | 0 | // (3) there's enough room in the popup for the content and its |
1347 | 0 | // scrollbar |
1348 | 0 | nsMargin borderPadding; |
1349 | 0 | GetXULBorderAndPadding(borderPadding); |
1350 | 0 |
|
1351 | 0 | // if there is a scroll frame, add the desired width of the scrollbar as well |
1352 | 0 | nsIScrollableFrame* scrollFrame = do_QueryFrame(popupFrame->PrincipalChildList().FirstChild()); |
1353 | 0 | nscoord scrollbarWidth = 0; |
1354 | 0 | if (scrollFrame) { |
1355 | 0 | scrollbarWidth = |
1356 | 0 | scrollFrame->GetDesiredScrollbarSizes(&aState).LeftRight(); |
1357 | 0 | } |
1358 | 0 |
|
1359 | 0 | aSize.width = |
1360 | 0 | tmpSize.width + std::max(borderPadding.LeftRight(), scrollbarWidth); |
1361 | 0 |
|
1362 | 0 | return true; |
1363 | 0 | } |
1364 | 0 | } |
1365 | 0 |
|
1366 | 0 | return false; |
1367 | 0 | } |
1368 | | |
1369 | | nsSize |
1370 | | nsMenuFrame::GetXULPrefSize(nsBoxLayoutState& aState) |
1371 | 0 | { |
1372 | 0 | nsSize size = nsBoxFrame::GetXULPrefSize(aState); |
1373 | 0 | DISPLAY_PREF_SIZE(this, size); |
1374 | 0 |
|
1375 | 0 | // If we are using sizetopopup="always" then |
1376 | 0 | // nsBoxFrame will already have enforced the minimum size |
1377 | 0 | if (!IsSizedToPopup(mContent, true) && |
1378 | 0 | IsSizedToPopup(mContent, false) && |
1379 | 0 | SizeToPopup(aState, size)) { |
1380 | 0 | // We now need to ensure that size is within the min - max range. |
1381 | 0 | nsSize minSize = nsBoxFrame::GetXULMinSize(aState); |
1382 | 0 | nsSize maxSize = GetXULMaxSize(aState); |
1383 | 0 | size = BoundsCheck(minSize, size, maxSize); |
1384 | 0 | } |
1385 | 0 |
|
1386 | 0 | return size; |
1387 | 0 | } |
1388 | | |
1389 | | NS_IMETHODIMP |
1390 | | nsMenuFrame::GetActiveChild(dom::Element** aResult) |
1391 | 0 | { |
1392 | 0 | nsMenuPopupFrame* popupFrame = GetPopup(); |
1393 | 0 | if (!popupFrame) |
1394 | 0 | return NS_ERROR_FAILURE; |
1395 | 0 | |
1396 | 0 | nsMenuFrame* menuFrame = popupFrame->GetCurrentMenuItem(); |
1397 | 0 | if (!menuFrame) { |
1398 | 0 | *aResult = nullptr; |
1399 | 0 | } |
1400 | 0 | else { |
1401 | 0 | RefPtr<dom::Element> elt = menuFrame->GetContent()->AsElement(); |
1402 | 0 | elt.forget(aResult); |
1403 | 0 | } |
1404 | 0 |
|
1405 | 0 | return NS_OK; |
1406 | 0 | } |
1407 | | |
1408 | | NS_IMETHODIMP |
1409 | | nsMenuFrame::SetActiveChild(dom::Element* aChild) |
1410 | 0 | { |
1411 | 0 | nsMenuPopupFrame* popupFrame = GetPopup(); |
1412 | 0 | if (!popupFrame) |
1413 | 0 | return NS_ERROR_FAILURE; |
1414 | 0 | |
1415 | 0 | if (!aChild) { |
1416 | 0 | // Remove the current selection |
1417 | 0 | popupFrame->ChangeMenuItem(nullptr, false, false); |
1418 | 0 | return NS_OK; |
1419 | 0 | } |
1420 | 0 | |
1421 | 0 | nsMenuFrame* menu = do_QueryFrame(aChild->GetPrimaryFrame()); |
1422 | 0 | if (menu) |
1423 | 0 | popupFrame->ChangeMenuItem(menu, false, false); |
1424 | 0 | return NS_OK; |
1425 | 0 | } |
1426 | | |
1427 | | nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame() |
1428 | 0 | { |
1429 | 0 | nsMenuPopupFrame* popupFrame = GetPopup(); |
1430 | 0 | if (!popupFrame) |
1431 | 0 | return nullptr; |
1432 | 0 | nsIFrame* childFrame = popupFrame->PrincipalChildList().FirstChild(); |
1433 | 0 | if (childFrame) |
1434 | 0 | return popupFrame->GetScrollFrame(childFrame); |
1435 | 0 | return nullptr; |
1436 | 0 | } |
1437 | | |
1438 | | // nsMenuTimerMediator implementation. |
1439 | | NS_IMPL_ISUPPORTS(nsMenuTimerMediator, nsITimerCallback) |
1440 | | |
1441 | | /** |
1442 | | * Constructs a wrapper around an nsMenuFrame. |
1443 | | * @param aFrame nsMenuFrame to create a wrapper around. |
1444 | | */ |
1445 | | nsMenuTimerMediator::nsMenuTimerMediator(nsMenuFrame *aFrame) |
1446 | | : mFrame(aFrame) |
1447 | 0 | { |
1448 | 0 | NS_ASSERTION(mFrame, "Must have frame"); |
1449 | 0 | } |
1450 | | |
1451 | | nsMenuTimerMediator::~nsMenuTimerMediator() |
1452 | 0 | { |
1453 | 0 | } |
1454 | | |
1455 | | /** |
1456 | | * Delegates the notification to the contained frame if it has not been destroyed. |
1457 | | * @param aTimer Timer which initiated the callback. |
1458 | | * @return NS_ERROR_FAILURE if the frame has been destroyed. |
1459 | | */ |
1460 | | NS_IMETHODIMP nsMenuTimerMediator::Notify(nsITimer* aTimer) |
1461 | 0 | { |
1462 | 0 | if (!mFrame) |
1463 | 0 | return NS_ERROR_FAILURE; |
1464 | 0 | |
1465 | 0 | return mFrame->Notify(aTimer); |
1466 | 0 | } |
1467 | | |
1468 | | /** |
1469 | | * Clear the pointer to the contained nsMenuFrame. This should be called |
1470 | | * when the contained nsMenuFrame is destroyed. |
1471 | | */ |
1472 | | void nsMenuTimerMediator::ClearFrame() |
1473 | 0 | { |
1474 | 0 | mFrame = nullptr; |
1475 | 0 | } |
1476 | | |
1477 | | /** |
1478 | | * Get the name of this timer callback. |
1479 | | * @param aName the name to return |
1480 | | */ |
1481 | | NS_IMETHODIMP |
1482 | | nsMenuTimerMediator::GetName(nsACString& aName) |
1483 | 0 | { |
1484 | 0 | aName.AssignLiteral("nsMenuTimerMediator"); |
1485 | 0 | return NS_OK; |
1486 | 0 | } |