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