1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/xul/nsMenuFrame.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1506 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsGkAtoms.h" 1.10 +#include "nsHTMLParts.h" 1.11 +#include "nsMenuFrame.h" 1.12 +#include "nsBoxFrame.h" 1.13 +#include "nsIContent.h" 1.14 +#include "nsIAtom.h" 1.15 +#include "nsPresContext.h" 1.16 +#include "nsIPresShell.h" 1.17 +#include "nsStyleContext.h" 1.18 +#include "nsCSSRendering.h" 1.19 +#include "nsNameSpaceManager.h" 1.20 +#include "nsMenuPopupFrame.h" 1.21 +#include "nsMenuBarFrame.h" 1.22 +#include "nsIDocument.h" 1.23 +#include "nsIDOMElement.h" 1.24 +#include "nsIComponentManager.h" 1.25 +#include "nsBoxLayoutState.h" 1.26 +#include "nsIScrollableFrame.h" 1.27 +#include "nsBindingManager.h" 1.28 +#include "nsIServiceManager.h" 1.29 +#include "nsCSSFrameConstructor.h" 1.30 +#include "nsIDOMKeyEvent.h" 1.31 +#include "nsXPIDLString.h" 1.32 +#include "nsReadableUtils.h" 1.33 +#include "nsUnicharUtils.h" 1.34 +#include "nsIStringBundle.h" 1.35 +#include "nsContentUtils.h" 1.36 +#include "nsDisplayList.h" 1.37 +#include "nsIReflowCallback.h" 1.38 +#include "nsISound.h" 1.39 +#include "nsIDOMXULMenuListElement.h" 1.40 +#include "mozilla/Attributes.h" 1.41 +#include "mozilla/EventDispatcher.h" 1.42 +#include "mozilla/EventStateManager.h" 1.43 +#include "mozilla/Likely.h" 1.44 +#include "mozilla/LookAndFeel.h" 1.45 +#include "mozilla/MouseEvents.h" 1.46 +#include "mozilla/Preferences.h" 1.47 +#include "mozilla/Services.h" 1.48 +#include "mozilla/TextEvents.h" 1.49 +#include "mozilla/dom/Element.h" 1.50 +#include <algorithm> 1.51 + 1.52 +using namespace mozilla; 1.53 + 1.54 +#define NS_MENU_POPUP_LIST_INDEX 0 1.55 + 1.56 +#if defined(XP_WIN) 1.57 +#define NSCONTEXTMENUISMOUSEUP 1 1.58 +#endif 1.59 + 1.60 +static void 1.61 +AssertNotCalled(void* aPropertyValue) 1.62 +{ 1.63 + NS_ERROR("popup list should never be destroyed by the FramePropertyTable"); 1.64 +} 1.65 +NS_DECLARE_FRAME_PROPERTY(PopupListProperty, AssertNotCalled) 1.66 + 1.67 +static int32_t gEatMouseMove = false; 1.68 + 1.69 +const int32_t kBlinkDelay = 67; // milliseconds 1.70 + 1.71 +// this class is used for dispatching menu activation events asynchronously. 1.72 +class nsMenuActivateEvent : public nsRunnable 1.73 +{ 1.74 +public: 1.75 + nsMenuActivateEvent(nsIContent *aMenu, 1.76 + nsPresContext* aPresContext, 1.77 + bool aIsActivate) 1.78 + : mMenu(aMenu), mPresContext(aPresContext), mIsActivate(aIsActivate) 1.79 + { 1.80 + } 1.81 + 1.82 + NS_IMETHOD Run() MOZ_OVERRIDE 1.83 + { 1.84 + nsAutoString domEventToFire; 1.85 + 1.86 + if (mIsActivate) { 1.87 + // Highlight the menu. 1.88 + mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, 1.89 + NS_LITERAL_STRING("true"), true); 1.90 + // The menuactivated event is used by accessibility to track the user's 1.91 + // movements through menus 1.92 + domEventToFire.AssignLiteral("DOMMenuItemActive"); 1.93 + } 1.94 + else { 1.95 + // Unhighlight the menu. 1.96 + mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true); 1.97 + domEventToFire.AssignLiteral("DOMMenuItemInactive"); 1.98 + } 1.99 + 1.100 + nsCOMPtr<nsIDOMEvent> event; 1.101 + if (NS_SUCCEEDED(EventDispatcher::CreateEvent(mMenu, mPresContext, nullptr, 1.102 + NS_LITERAL_STRING("Events"), 1.103 + getter_AddRefs(event)))) { 1.104 + event->InitEvent(domEventToFire, true, true); 1.105 + 1.106 + event->SetTrusted(true); 1.107 + 1.108 + EventDispatcher::DispatchDOMEvent(mMenu, nullptr, event, 1.109 + mPresContext, nullptr); 1.110 + } 1.111 + 1.112 + return NS_OK; 1.113 + } 1.114 + 1.115 +private: 1.116 + nsCOMPtr<nsIContent> mMenu; 1.117 + nsRefPtr<nsPresContext> mPresContext; 1.118 + bool mIsActivate; 1.119 +}; 1.120 + 1.121 +class nsMenuAttributeChangedEvent : public nsRunnable 1.122 +{ 1.123 +public: 1.124 + nsMenuAttributeChangedEvent(nsIFrame* aFrame, nsIAtom* aAttr) 1.125 + : mFrame(aFrame), mAttr(aAttr) 1.126 + { 1.127 + } 1.128 + 1.129 + NS_IMETHOD Run() MOZ_OVERRIDE 1.130 + { 1.131 + nsMenuFrame* frame = static_cast<nsMenuFrame*>(mFrame.GetFrame()); 1.132 + NS_ENSURE_STATE(frame); 1.133 + if (mAttr == nsGkAtoms::checked) { 1.134 + frame->UpdateMenuSpecialState(frame->PresContext()); 1.135 + } else if (mAttr == nsGkAtoms::acceltext) { 1.136 + // someone reset the accelText attribute, 1.137 + // so clear the bit that says *we* set it 1.138 + frame->RemoveStateBits(NS_STATE_ACCELTEXT_IS_DERIVED); 1.139 + frame->BuildAcceleratorText(true); 1.140 + } 1.141 + else if (mAttr == nsGkAtoms::key) { 1.142 + frame->BuildAcceleratorText(true); 1.143 + } else if (mAttr == nsGkAtoms::type || mAttr == nsGkAtoms::name) { 1.144 + frame->UpdateMenuType(frame->PresContext()); 1.145 + } 1.146 + return NS_OK; 1.147 + } 1.148 +protected: 1.149 + nsWeakFrame mFrame; 1.150 + nsCOMPtr<nsIAtom> mAttr; 1.151 +}; 1.152 + 1.153 +// 1.154 +// NS_NewMenuFrame and NS_NewMenuItemFrame 1.155 +// 1.156 +// Wrappers for creating a new menu popup container 1.157 +// 1.158 +nsIFrame* 1.159 +NS_NewMenuFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) 1.160 +{ 1.161 + nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext); 1.162 + it->SetIsMenu(true); 1.163 + return it; 1.164 +} 1.165 + 1.166 +nsIFrame* 1.167 +NS_NewMenuItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) 1.168 +{ 1.169 + nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext); 1.170 + it->SetIsMenu(false); 1.171 + return it; 1.172 +} 1.173 + 1.174 +NS_IMPL_FRAMEARENA_HELPERS(nsMenuFrame) 1.175 + 1.176 +NS_QUERYFRAME_HEAD(nsMenuFrame) 1.177 + NS_QUERYFRAME_ENTRY(nsMenuFrame) 1.178 +NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame) 1.179 + 1.180 +nsMenuFrame::nsMenuFrame(nsIPresShell* aShell, nsStyleContext* aContext): 1.181 + nsBoxFrame(aShell, aContext), 1.182 + mIsMenu(false), 1.183 + mChecked(false), 1.184 + mIgnoreAccelTextChange(false), 1.185 + mType(eMenuType_Normal), 1.186 + mMenuParent(nullptr), 1.187 + mBlinkState(0) 1.188 +{ 1.189 +} 1.190 + 1.191 +void 1.192 +nsMenuFrame::SetParent(nsIFrame* aParent) 1.193 +{ 1.194 + nsBoxFrame::SetParent(aParent); 1.195 + InitMenuParent(aParent); 1.196 +} 1.197 + 1.198 +void 1.199 +nsMenuFrame::InitMenuParent(nsIFrame* aParent) 1.200 +{ 1.201 + while (aParent) { 1.202 + nsMenuPopupFrame* popup = do_QueryFrame(aParent); 1.203 + if (popup) { 1.204 + mMenuParent = popup; 1.205 + break; 1.206 + } 1.207 + 1.208 + nsMenuBarFrame* menubar = do_QueryFrame(aParent); 1.209 + if (menubar) { 1.210 + mMenuParent = menubar; 1.211 + break; 1.212 + } 1.213 + 1.214 + aParent = aParent->GetParent(); 1.215 + } 1.216 +} 1.217 + 1.218 +class nsASyncMenuInitialization MOZ_FINAL : public nsIReflowCallback 1.219 +{ 1.220 +public: 1.221 + nsASyncMenuInitialization(nsIFrame* aFrame) 1.222 + : mWeakFrame(aFrame) 1.223 + { 1.224 + } 1.225 + 1.226 + virtual bool ReflowFinished() MOZ_OVERRIDE 1.227 + { 1.228 + bool shouldFlush = false; 1.229 + nsMenuFrame* menu = do_QueryFrame(mWeakFrame.GetFrame()); 1.230 + if (menu) { 1.231 + menu->UpdateMenuType(menu->PresContext()); 1.232 + shouldFlush = true; 1.233 + } 1.234 + delete this; 1.235 + return shouldFlush; 1.236 + } 1.237 + 1.238 + virtual void ReflowCallbackCanceled() MOZ_OVERRIDE 1.239 + { 1.240 + delete this; 1.241 + } 1.242 + 1.243 + nsWeakFrame mWeakFrame; 1.244 +}; 1.245 + 1.246 +void 1.247 +nsMenuFrame::Init(nsIContent* aContent, 1.248 + nsIFrame* aParent, 1.249 + nsIFrame* aPrevInFlow) 1.250 +{ 1.251 + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); 1.252 + 1.253 + // Set up a mediator which can be used for callbacks on this frame. 1.254 + mTimerMediator = new nsMenuTimerMediator(this); 1.255 + 1.256 + InitMenuParent(aParent); 1.257 + 1.258 + BuildAcceleratorText(false); 1.259 + nsIReflowCallback* cb = new nsASyncMenuInitialization(this); 1.260 + PresContext()->PresShell()->PostReflowCallback(cb); 1.261 +} 1.262 + 1.263 +const nsFrameList& 1.264 +nsMenuFrame::GetChildList(ChildListID aListID) const 1.265 +{ 1.266 + if (kPopupList == aListID) { 1.267 + nsFrameList* list = GetPopupList(); 1.268 + return list ? *list : nsFrameList::EmptyList(); 1.269 + } 1.270 + return nsBoxFrame::GetChildList(aListID); 1.271 +} 1.272 + 1.273 +void 1.274 +nsMenuFrame::GetChildLists(nsTArray<ChildList>* aLists) const 1.275 +{ 1.276 + nsBoxFrame::GetChildLists(aLists); 1.277 + nsFrameList* list = GetPopupList(); 1.278 + if (list) { 1.279 + list->AppendIfNonempty(aLists, kPopupList); 1.280 + } 1.281 +} 1.282 + 1.283 +nsMenuPopupFrame* 1.284 +nsMenuFrame::GetPopup() 1.285 +{ 1.286 + nsFrameList* popupList = GetPopupList(); 1.287 + return popupList ? static_cast<nsMenuPopupFrame*>(popupList->FirstChild()) : 1.288 + nullptr; 1.289 +} 1.290 + 1.291 +nsFrameList* 1.292 +nsMenuFrame::GetPopupList() const 1.293 +{ 1.294 + if (!HasPopup()) { 1.295 + return nullptr; 1.296 + } 1.297 + nsFrameList* prop = 1.298 + static_cast<nsFrameList*>(Properties().Get(PopupListProperty())); 1.299 + NS_ASSERTION(prop && prop->GetLength() == 1 && 1.300 + prop->FirstChild()->GetType() == nsGkAtoms::menuPopupFrame, 1.301 + "popup list should have exactly one nsMenuPopupFrame"); 1.302 + return prop; 1.303 +} 1.304 + 1.305 +void 1.306 +nsMenuFrame::DestroyPopupList() 1.307 +{ 1.308 + NS_ASSERTION(HasPopup(), "huh?"); 1.309 + nsFrameList* prop = 1.310 + static_cast<nsFrameList*>(Properties().Remove(PopupListProperty())); 1.311 + NS_ASSERTION(prop && prop->IsEmpty(), 1.312 + "popup list must exist and be empty when destroying"); 1.313 + RemoveStateBits(NS_STATE_MENU_HAS_POPUP_LIST); 1.314 + prop->Delete(PresContext()->PresShell()); 1.315 +} 1.316 + 1.317 +void 1.318 +nsMenuFrame::SetPopupFrame(nsFrameList& aFrameList) 1.319 +{ 1.320 + for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { 1.321 + nsMenuPopupFrame* popupFrame = do_QueryFrame(e.get()); 1.322 + if (popupFrame) { 1.323 + // Remove the frame from the list and store it in a nsFrameList* property. 1.324 + aFrameList.RemoveFrame(popupFrame); 1.325 + nsFrameList* popupList = new (PresContext()->PresShell()) nsFrameList(popupFrame, popupFrame); 1.326 + Properties().Set(PopupListProperty(), popupList); 1.327 + AddStateBits(NS_STATE_MENU_HAS_POPUP_LIST); 1.328 + break; 1.329 + } 1.330 + } 1.331 +} 1.332 + 1.333 +nsresult 1.334 +nsMenuFrame::SetInitialChildList(ChildListID aListID, 1.335 + nsFrameList& aChildList) 1.336 +{ 1.337 + NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?"); 1.338 + if (aListID == kPrincipalList || aListID == kPopupList) { 1.339 + SetPopupFrame(aChildList); 1.340 + } 1.341 + return nsBoxFrame::SetInitialChildList(aListID, aChildList); 1.342 +} 1.343 + 1.344 +void 1.345 +nsMenuFrame::DestroyFrom(nsIFrame* aDestructRoot) 1.346 +{ 1.347 + // Kill our timer if one is active. This is not strictly necessary as 1.348 + // the pointer to this frame will be cleared from the mediator, but 1.349 + // this is done for added safety. 1.350 + if (mOpenTimer) { 1.351 + mOpenTimer->Cancel(); 1.352 + } 1.353 + 1.354 + StopBlinking(); 1.355 + 1.356 + // Null out the pointer to this frame in the mediator wrapper so that it 1.357 + // doesn't try to interact with a deallocated frame. 1.358 + mTimerMediator->ClearFrame(); 1.359 + 1.360 + // if the menu content is just being hidden, it may be made visible again 1.361 + // later, so make sure to clear the highlighting. 1.362 + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, false); 1.363 + 1.364 + // are we our menu parent's current menu item? 1.365 + if (mMenuParent && mMenuParent->GetCurrentMenuItem() == this) { 1.366 + // yes; tell it that we're going away 1.367 + mMenuParent->CurrentMenuIsBeingDestroyed(); 1.368 + } 1.369 + 1.370 + nsFrameList* popupList = GetPopupList(); 1.371 + if (popupList) { 1.372 + popupList->DestroyFramesFrom(aDestructRoot); 1.373 + DestroyPopupList(); 1.374 + } 1.375 + 1.376 + nsBoxFrame::DestroyFrom(aDestructRoot); 1.377 +} 1.378 + 1.379 +void 1.380 +nsMenuFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, 1.381 + const nsRect& aDirtyRect, 1.382 + const nsDisplayListSet& aLists) 1.383 +{ 1.384 + if (!aBuilder->IsForEventDelivery()) { 1.385 + nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); 1.386 + return; 1.387 + } 1.388 + 1.389 + nsDisplayListCollection set; 1.390 + nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, set); 1.391 + 1.392 + WrapListsInRedirector(aBuilder, set, aLists); 1.393 +} 1.394 + 1.395 +nsresult 1.396 +nsMenuFrame::HandleEvent(nsPresContext* aPresContext, 1.397 + WidgetGUIEvent* aEvent, 1.398 + nsEventStatus* aEventStatus) 1.399 +{ 1.400 + NS_ENSURE_ARG_POINTER(aEventStatus); 1.401 + if (nsEventStatus_eConsumeNoDefault == *aEventStatus || 1.402 + (mMenuParent && mMenuParent->IsMenuLocked())) { 1.403 + return NS_OK; 1.404 + } 1.405 + 1.406 + nsWeakFrame weakFrame(this); 1.407 + if (*aEventStatus == nsEventStatus_eIgnore) 1.408 + *aEventStatus = nsEventStatus_eConsumeDoDefault; 1.409 + 1.410 + bool onmenu = IsOnMenu(); 1.411 + 1.412 + if (aEvent->message == NS_KEY_PRESS && !IsDisabled()) { 1.413 + WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); 1.414 + uint32_t keyCode = keyEvent->keyCode; 1.415 +#ifdef XP_MACOSX 1.416 + // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed) 1.417 + if (!IsOpen() && ((keyEvent->charCode == NS_VK_SPACE && !keyEvent->IsMeta()) || 1.418 + (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) { 1.419 + *aEventStatus = nsEventStatus_eConsumeNoDefault; 1.420 + OpenMenu(false); 1.421 + } 1.422 +#else 1.423 + // On other platforms, toggle menulist on unmodified F4 or Alt arrow 1.424 + if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) || 1.425 + ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) { 1.426 + *aEventStatus = nsEventStatus_eConsumeNoDefault; 1.427 + ToggleMenuState(); 1.428 + } 1.429 +#endif 1.430 + } 1.431 + else if (aEvent->message == NS_MOUSE_BUTTON_DOWN && 1.432 + aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton && 1.433 + !IsDisabled() && IsMenu()) { 1.434 + // The menu item was selected. Bring up the menu. 1.435 + // We have children. 1.436 + // Don't prevent the default action here, since that will also cancel 1.437 + // potential drag starts. 1.438 + if (!mMenuParent || mMenuParent->IsMenuBar()) { 1.439 + ToggleMenuState(); 1.440 + } 1.441 + else { 1.442 + if (!IsOpen()) { 1.443 + OpenMenu(false); 1.444 + } 1.445 + } 1.446 + } 1.447 + else if ( 1.448 +#ifndef NSCONTEXTMENUISMOUSEUP 1.449 + (aEvent->message == NS_MOUSE_BUTTON_UP && 1.450 + aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) && 1.451 +#else 1.452 + aEvent->message == NS_CONTEXTMENU && 1.453 +#endif 1.454 + onmenu && !IsMenu() && !IsDisabled()) { 1.455 + // if this menu is a context menu it accepts right-clicks...fire away! 1.456 + // Make sure we cancel default processing of the context menu event so 1.457 + // that it doesn't bubble and get seen again by the popuplistener and show 1.458 + // another context menu. 1.459 + // 1.460 + // Furthermore (there's always more, isn't there?), on some platforms (win32 1.461 + // being one of them) we get the context menu event on a mouse up while 1.462 + // on others we get it on a mouse down. For the ones where we get it on a 1.463 + // mouse down, we must continue listening for the right button up event to 1.464 + // dismiss the menu. 1.465 + if (mMenuParent->IsContextMenu()) { 1.466 + *aEventStatus = nsEventStatus_eConsumeNoDefault; 1.467 + Execute(aEvent); 1.468 + } 1.469 + } 1.470 + else if (aEvent->message == NS_MOUSE_BUTTON_UP && 1.471 + aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton && 1.472 + !IsMenu() && !IsDisabled()) { 1.473 + // Execute the execute event handler. 1.474 + *aEventStatus = nsEventStatus_eConsumeNoDefault; 1.475 + Execute(aEvent); 1.476 + } 1.477 + else if (aEvent->message == NS_MOUSE_EXIT_SYNTH) { 1.478 + // Kill our timer if one is active. 1.479 + if (mOpenTimer) { 1.480 + mOpenTimer->Cancel(); 1.481 + mOpenTimer = nullptr; 1.482 + } 1.483 + 1.484 + // Deactivate the menu. 1.485 + if (mMenuParent) { 1.486 + bool onmenubar = mMenuParent->IsMenuBar(); 1.487 + if (!(onmenubar && mMenuParent->IsActive())) { 1.488 + if (IsMenu() && !onmenubar && IsOpen()) { 1.489 + // Submenus don't get closed up immediately. 1.490 + } 1.491 + else if (this == mMenuParent->GetCurrentMenuItem()) { 1.492 + mMenuParent->ChangeMenuItem(nullptr, false); 1.493 + } 1.494 + } 1.495 + } 1.496 + } 1.497 + else if (aEvent->message == NS_MOUSE_MOVE && 1.498 + (onmenu || (mMenuParent && mMenuParent->IsMenuBar()))) { 1.499 + if (gEatMouseMove) { 1.500 + gEatMouseMove = false; 1.501 + return NS_OK; 1.502 + } 1.503 + 1.504 + // Let the menu parent know we're the new item. 1.505 + mMenuParent->ChangeMenuItem(this, false); 1.506 + NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); 1.507 + NS_ENSURE_TRUE(mMenuParent, NS_OK); 1.508 + 1.509 + // we need to check if we really became the current menu 1.510 + // item or not 1.511 + nsMenuFrame *realCurrentItem = mMenuParent->GetCurrentMenuItem(); 1.512 + if (realCurrentItem != this) { 1.513 + // we didn't (presumably because a context menu was active) 1.514 + return NS_OK; 1.515 + } 1.516 + 1.517 + // Hovering over a menu in a popup should open it without a need for a click. 1.518 + // A timer is used so that it doesn't open if the user moves the mouse quickly 1.519 + // past the menu. This conditional check ensures that only menus have this 1.520 + // behaviour 1.521 + if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer && !mMenuParent->IsMenuBar()) { 1.522 + int32_t menuDelay = 1.523 + LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms 1.524 + 1.525 + // We're a menu, we're built, we're closed, and no timer has been kicked off. 1.526 + mOpenTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.527 + mOpenTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT); 1.528 + } 1.529 + } 1.530 + 1.531 + return NS_OK; 1.532 +} 1.533 + 1.534 +void 1.535 +nsMenuFrame::ToggleMenuState() 1.536 +{ 1.537 + if (IsOpen()) 1.538 + CloseMenu(false); 1.539 + else 1.540 + OpenMenu(false); 1.541 +} 1.542 + 1.543 +void 1.544 +nsMenuFrame::PopupOpened() 1.545 +{ 1.546 + nsWeakFrame weakFrame(this); 1.547 + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, 1.548 + NS_LITERAL_STRING("true"), true); 1.549 + if (!weakFrame.IsAlive()) 1.550 + return; 1.551 + 1.552 + if (mMenuParent) { 1.553 + mMenuParent->SetActive(true); 1.554 + // Make sure the current menu which is being toggled on 1.555 + // the menubar is highlighted 1.556 + mMenuParent->SetCurrentMenuItem(this); 1.557 + } 1.558 +} 1.559 + 1.560 +void 1.561 +nsMenuFrame::PopupClosed(bool aDeselectMenu) 1.562 +{ 1.563 + nsWeakFrame weakFrame(this); 1.564 + nsContentUtils::AddScriptRunner( 1.565 + new nsUnsetAttrRunnable(mContent, nsGkAtoms::open)); 1.566 + if (!weakFrame.IsAlive()) 1.567 + return; 1.568 + 1.569 + // if the popup is for a menu on a menubar, inform menubar to deactivate 1.570 + if (mMenuParent && mMenuParent->MenuClosed()) { 1.571 + if (aDeselectMenu) { 1.572 + SelectMenu(false); 1.573 + } else { 1.574 + // We are not deselecting the parent menu while closing the popup, so send 1.575 + // a DOMMenuItemActive event to the menu to indicate that the menu is 1.576 + // becoming active again. 1.577 + nsMenuFrame *current = mMenuParent->GetCurrentMenuItem(); 1.578 + if (current) { 1.579 + // However, if the menu is a descendant on a menubar, and the menubar 1.580 + // has the 'stay active' flag set, it means that the menubar is switching 1.581 + // to another toplevel menu entirely (for example from Edit to View), so 1.582 + // don't fire the DOMMenuItemActive event or else we'll send extraneous 1.583 + // events for submenus. nsMenuBarFrame::ChangeMenuItem has already deselected 1.584 + // the old menu, so it doesn't need to happen again here, and the new 1.585 + // menu can be selected right away. 1.586 + nsIFrame* parent = current; 1.587 + while (parent) { 1.588 + nsMenuBarFrame* menubar = do_QueryFrame(parent); 1.589 + if (menubar && menubar->GetStayActive()) 1.590 + return; 1.591 + 1.592 + parent = parent->GetParent(); 1.593 + } 1.594 + 1.595 + nsCOMPtr<nsIRunnable> event = 1.596 + new nsMenuActivateEvent(current->GetContent(), 1.597 + PresContext(), true); 1.598 + NS_DispatchToCurrentThread(event); 1.599 + } 1.600 + } 1.601 + } 1.602 +} 1.603 + 1.604 +NS_IMETHODIMP 1.605 +nsMenuFrame::SelectMenu(bool aActivateFlag) 1.606 +{ 1.607 + if (mContent) { 1.608 + // When a menu opens a submenu, the mouse will often be moved onto a 1.609 + // sibling before moving onto an item within the submenu, causing the 1.610 + // parent to become deselected. We need to ensure that the parent menu 1.611 + // is reselected when an item in the submenu is selected, so navigate up 1.612 + // from the item to its popup, and then to the popup above that. 1.613 + if (aActivateFlag) { 1.614 + nsIFrame* parent = GetParent(); 1.615 + while (parent) { 1.616 + nsMenuPopupFrame* menupopup = do_QueryFrame(parent); 1.617 + if (menupopup) { 1.618 + // a menu is always the direct parent of a menupopup 1.619 + nsMenuFrame* menu = do_QueryFrame(menupopup->GetParent()); 1.620 + if (menu) { 1.621 + // a popup however is not necessarily the direct parent of a menu 1.622 + nsIFrame* popupParent = menu->GetParent(); 1.623 + while (popupParent) { 1.624 + menupopup = do_QueryFrame(popupParent); 1.625 + if (menupopup) { 1.626 + menupopup->SetCurrentMenuItem(menu); 1.627 + break; 1.628 + } 1.629 + popupParent = popupParent->GetParent(); 1.630 + } 1.631 + } 1.632 + break; 1.633 + } 1.634 + parent = parent->GetParent(); 1.635 + } 1.636 + } 1.637 + 1.638 + // cancel the close timer if selecting a menu within the popup to be closed 1.639 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.640 + if (pm) 1.641 + pm->CancelMenuTimer(mMenuParent); 1.642 + 1.643 + nsCOMPtr<nsIRunnable> event = 1.644 + new nsMenuActivateEvent(mContent, PresContext(), aActivateFlag); 1.645 + NS_DispatchToCurrentThread(event); 1.646 + } 1.647 + 1.648 + return NS_OK; 1.649 +} 1.650 + 1.651 +nsresult 1.652 +nsMenuFrame::AttributeChanged(int32_t aNameSpaceID, 1.653 + nsIAtom* aAttribute, 1.654 + int32_t aModType) 1.655 +{ 1.656 + if (aAttribute == nsGkAtoms::acceltext && mIgnoreAccelTextChange) { 1.657 + // Reset the flag so that only one change is ignored. 1.658 + mIgnoreAccelTextChange = false; 1.659 + return NS_OK; 1.660 + } 1.661 + 1.662 + if (aAttribute == nsGkAtoms::checked || 1.663 + aAttribute == nsGkAtoms::acceltext || 1.664 + aAttribute == nsGkAtoms::key || 1.665 + aAttribute == nsGkAtoms::type || 1.666 + aAttribute == nsGkAtoms::name) { 1.667 + nsCOMPtr<nsIRunnable> event = 1.668 + new nsMenuAttributeChangedEvent(this, aAttribute); 1.669 + nsContentUtils::AddScriptRunner(event); 1.670 + } 1.671 + return NS_OK; 1.672 +} 1.673 + 1.674 +nsIContent* 1.675 +nsMenuFrame::GetAnchor() 1.676 +{ 1.677 + mozilla::dom::Element* anchor = nullptr; 1.678 + 1.679 + nsAutoString id; 1.680 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::anchor, id); 1.681 + if (!id.IsEmpty()) { 1.682 + nsIDocument* doc = mContent->OwnerDoc(); 1.683 + 1.684 + anchor = 1.685 + doc->GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, id); 1.686 + if (!anchor) { 1.687 + anchor = doc->GetElementById(id); 1.688 + } 1.689 + } 1.690 + 1.691 + // Always return the menu's content if the anchor wasn't set or wasn't found. 1.692 + return anchor && anchor->GetPrimaryFrame() ? anchor : mContent; 1.693 +} 1.694 + 1.695 +void 1.696 +nsMenuFrame::OpenMenu(bool aSelectFirstItem) 1.697 +{ 1.698 + if (!mContent) 1.699 + return; 1.700 + 1.701 + gEatMouseMove = true; 1.702 + 1.703 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.704 + if (pm) { 1.705 + pm->KillMenuTimer(); 1.706 + // This opens the menu asynchronously 1.707 + pm->ShowMenu(mContent, aSelectFirstItem, true); 1.708 + } 1.709 +} 1.710 + 1.711 +void 1.712 +nsMenuFrame::CloseMenu(bool aDeselectMenu) 1.713 +{ 1.714 + gEatMouseMove = true; 1.715 + 1.716 + // Close the menu asynchronously 1.717 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.718 + if (pm && HasPopup()) 1.719 + pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true, false); 1.720 +} 1.721 + 1.722 +bool 1.723 +nsMenuFrame::IsSizedToPopup(nsIContent* aContent, bool aRequireAlways) 1.724 +{ 1.725 + nsAutoString sizedToPopup; 1.726 + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup, sizedToPopup); 1.727 + return sizedToPopup.EqualsLiteral("always") || 1.728 + (!aRequireAlways && sizedToPopup.EqualsLiteral("pref")); 1.729 +} 1.730 + 1.731 +nsSize 1.732 +nsMenuFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState) 1.733 +{ 1.734 + nsSize size = nsBoxFrame::GetMinSize(aBoxLayoutState); 1.735 + DISPLAY_MIN_SIZE(this, size); 1.736 + 1.737 + if (IsSizedToPopup(mContent, true)) 1.738 + SizeToPopup(aBoxLayoutState, size); 1.739 + 1.740 + return size; 1.741 +} 1.742 + 1.743 +NS_IMETHODIMP 1.744 +nsMenuFrame::DoLayout(nsBoxLayoutState& aState) 1.745 +{ 1.746 + // lay us out 1.747 + nsresult rv = nsBoxFrame::DoLayout(aState); 1.748 + 1.749 + nsMenuPopupFrame* popupFrame = GetPopup(); 1.750 + if (popupFrame) { 1.751 + bool sizeToPopup = IsSizedToPopup(mContent, false); 1.752 + popupFrame->LayoutPopup(aState, this, GetAnchor()->GetPrimaryFrame(), sizeToPopup); 1.753 + } 1.754 + 1.755 + return rv; 1.756 +} 1.757 + 1.758 +#ifdef DEBUG_LAYOUT 1.759 +nsresult 1.760 +nsMenuFrame::SetDebug(nsBoxLayoutState& aState, bool aDebug) 1.761 +{ 1.762 + // see if our state matches the given debug state 1.763 + bool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG; 1.764 + bool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet); 1.765 + 1.766 + // if it doesn't then tell each child below us the new debug state 1.767 + if (debugChanged) 1.768 + { 1.769 + nsBoxFrame::SetDebug(aState, aDebug); 1.770 + nsMenuPopupFrame* popupFrame = GetPopup(); 1.771 + if (popupFrame) 1.772 + SetDebug(aState, popupFrame, aDebug); 1.773 + } 1.774 + 1.775 + return NS_OK; 1.776 +} 1.777 + 1.778 +nsresult 1.779 +nsMenuFrame::SetDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug) 1.780 +{ 1.781 + if (!aList) 1.782 + return NS_OK; 1.783 + 1.784 + while (aList) { 1.785 + if (aList->IsBoxFrame()) 1.786 + aList->SetDebug(aState, aDebug); 1.787 + 1.788 + aList = aList->GetNextSibling(); 1.789 + } 1.790 + 1.791 + return NS_OK; 1.792 +} 1.793 +#endif 1.794 + 1.795 +// 1.796 +// Enter 1.797 +// 1.798 +// Called when the user hits the <Enter>/<Return> keys or presses the 1.799 +// shortcut key. If this is a leaf item, the item's action will be executed. 1.800 +// In either case, do nothing if the item is disabled. 1.801 +// 1.802 +nsMenuFrame* 1.803 +nsMenuFrame::Enter(WidgetGUIEvent* aEvent) 1.804 +{ 1.805 + if (IsDisabled()) { 1.806 +#ifdef XP_WIN 1.807 + // behavior on Windows - close the popup chain 1.808 + if (mMenuParent) { 1.809 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.810 + if (pm) { 1.811 + nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny); 1.812 + if (popup) 1.813 + pm->HidePopup(popup->GetContent(), true, true, true, false); 1.814 + } 1.815 + } 1.816 +#endif // #ifdef XP_WIN 1.817 + // this menu item was disabled - exit 1.818 + return nullptr; 1.819 + } 1.820 + 1.821 + if (!IsOpen()) { 1.822 + // The enter key press applies to us. 1.823 + if (!IsMenu() && mMenuParent) 1.824 + Execute(aEvent); // Execute our event handler 1.825 + else 1.826 + return this; 1.827 + } 1.828 + 1.829 + return nullptr; 1.830 +} 1.831 + 1.832 +bool 1.833 +nsMenuFrame::IsOpen() 1.834 +{ 1.835 + nsMenuPopupFrame* popupFrame = GetPopup(); 1.836 + return popupFrame && popupFrame->IsOpen(); 1.837 +} 1.838 + 1.839 +bool 1.840 +nsMenuFrame::IsMenu() 1.841 +{ 1.842 + return mIsMenu; 1.843 +} 1.844 + 1.845 +nsMenuListType 1.846 +nsMenuFrame::GetParentMenuListType() 1.847 +{ 1.848 + if (mMenuParent && mMenuParent->IsMenu()) { 1.849 + nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(mMenuParent); 1.850 + nsIFrame* parentMenu = popupFrame->GetParent(); 1.851 + if (parentMenu) { 1.852 + nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent()); 1.853 + if (menulist) { 1.854 + bool isEditable = false; 1.855 + menulist->GetEditable(&isEditable); 1.856 + return isEditable ? eEditableMenuList : eReadonlyMenuList; 1.857 + } 1.858 + } 1.859 + } 1.860 + return eNotMenuList; 1.861 +} 1.862 + 1.863 +nsresult 1.864 +nsMenuFrame::Notify(nsITimer* aTimer) 1.865 +{ 1.866 + // Our timer has fired. 1.867 + if (aTimer == mOpenTimer.get()) { 1.868 + mOpenTimer = nullptr; 1.869 + 1.870 + if (!IsOpen() && mMenuParent) { 1.871 + // make sure we didn't open a context menu in the meantime 1.872 + // (i.e. the user right-clicked while hovering over a submenu). 1.873 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.874 + if (pm) { 1.875 + if ((!pm->HasContextMenu(nullptr) || mMenuParent->IsContextMenu()) && 1.876 + mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive, 1.877 + nsGkAtoms::_true, eCaseMatters)) { 1.878 + OpenMenu(false); 1.879 + } 1.880 + } 1.881 + } 1.882 + } else if (aTimer == mBlinkTimer) { 1.883 + switch (mBlinkState++) { 1.884 + case 0: 1.885 + NS_ASSERTION(false, "Blink timer fired while not blinking"); 1.886 + StopBlinking(); 1.887 + break; 1.888 + case 1: 1.889 + { 1.890 + // Turn the highlight back on and wait for a while before closing the menu. 1.891 + nsWeakFrame weakFrame(this); 1.892 + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, 1.893 + NS_LITERAL_STRING("true"), true); 1.894 + if (weakFrame.IsAlive()) { 1.895 + aTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT); 1.896 + } 1.897 + } 1.898 + break; 1.899 + default: 1.900 + if (mMenuParent) { 1.901 + mMenuParent->LockMenuUntilClosed(false); 1.902 + } 1.903 + PassMenuCommandEventToPopupManager(); 1.904 + StopBlinking(); 1.905 + break; 1.906 + } 1.907 + } 1.908 + 1.909 + return NS_OK; 1.910 +} 1.911 + 1.912 +bool 1.913 +nsMenuFrame::IsDisabled() 1.914 +{ 1.915 + return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, 1.916 + nsGkAtoms::_true, eCaseMatters); 1.917 +} 1.918 + 1.919 +void 1.920 +nsMenuFrame::UpdateMenuType(nsPresContext* aPresContext) 1.921 +{ 1.922 + static nsIContent::AttrValuesArray strings[] = 1.923 + {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr}; 1.924 + switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, 1.925 + strings, eCaseMatters)) { 1.926 + case 0: mType = eMenuType_Checkbox; break; 1.927 + case 1: 1.928 + mType = eMenuType_Radio; 1.929 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, mGroupName); 1.930 + break; 1.931 + 1.932 + default: 1.933 + if (mType != eMenuType_Normal) { 1.934 + nsWeakFrame weakFrame(this); 1.935 + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, 1.936 + true); 1.937 + ENSURE_TRUE(weakFrame.IsAlive()); 1.938 + } 1.939 + mType = eMenuType_Normal; 1.940 + break; 1.941 + } 1.942 + UpdateMenuSpecialState(aPresContext); 1.943 +} 1.944 + 1.945 +/* update checked-ness for type="checkbox" and type="radio" */ 1.946 +void 1.947 +nsMenuFrame::UpdateMenuSpecialState(nsPresContext* aPresContext) 1.948 +{ 1.949 + bool newChecked = 1.950 + mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked, 1.951 + nsGkAtoms::_true, eCaseMatters); 1.952 + if (newChecked == mChecked) { 1.953 + /* checked state didn't change */ 1.954 + 1.955 + if (mType != eMenuType_Radio) 1.956 + return; // only Radio possibly cares about other kinds of change 1.957 + 1.958 + if (!mChecked || mGroupName.IsEmpty()) 1.959 + return; // no interesting change 1.960 + } else { 1.961 + mChecked = newChecked; 1.962 + if (mType != eMenuType_Radio || !mChecked) 1.963 + /* 1.964 + * Unchecking something requires no further changes, and only 1.965 + * menuRadio has to do additional work when checked. 1.966 + */ 1.967 + return; 1.968 + } 1.969 + 1.970 + /* 1.971 + * If we get this far, we're type=radio, and: 1.972 + * - our name= changed, or 1.973 + * - we went from checked="false" to checked="true" 1.974 + */ 1.975 + 1.976 + /* 1.977 + * Behavioural note: 1.978 + * If we're checked and renamed _into_ an existing radio group, we are 1.979 + * made the new checked item, and we unselect the previous one. 1.980 + * 1.981 + * The only other reasonable behaviour would be to check for another selected 1.982 + * item in that group. If found, unselect ourselves, otherwise we're the 1.983 + * selected item. That, however, would be a lot more work, and I don't think 1.984 + * it's better at all. 1.985 + */ 1.986 + 1.987 + /* walk siblings, looking for the other checked item with the same name */ 1.988 + // get the first sibling in this menu popup. This frame may be it, and if we're 1.989 + // being called at creation time, this frame isn't yet in the parent's child list. 1.990 + // All I'm saying is that this may fail, but it's most likely alright. 1.991 + nsIFrame* sib = GetParent()->GetFirstPrincipalChild(); 1.992 + 1.993 + while (sib) { 1.994 + if (sib != this) { 1.995 + nsMenuFrame* menu = do_QueryFrame(sib); 1.996 + if (menu && menu->GetMenuType() == eMenuType_Radio && 1.997 + menu->IsChecked() && menu->GetRadioGroupName() == mGroupName) { 1.998 + /* uncheck the old item */ 1.999 + sib->GetContent()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, 1.1000 + true); 1.1001 + /* XXX in DEBUG, check to make sure that there aren't two checked items */ 1.1002 + return; 1.1003 + } 1.1004 + } 1.1005 + 1.1006 + sib = sib->GetNextSibling(); 1.1007 + } 1.1008 +} 1.1009 + 1.1010 +void 1.1011 +nsMenuFrame::BuildAcceleratorText(bool aNotify) 1.1012 +{ 1.1013 + nsAutoString accelText; 1.1014 + 1.1015 + if ((GetStateBits() & NS_STATE_ACCELTEXT_IS_DERIVED) == 0) { 1.1016 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText); 1.1017 + if (!accelText.IsEmpty()) 1.1018 + return; 1.1019 + } 1.1020 + // accelText is definitely empty here. 1.1021 + 1.1022 + // Now we're going to compute the accelerator text, so remember that we did. 1.1023 + AddStateBits(NS_STATE_ACCELTEXT_IS_DERIVED); 1.1024 + 1.1025 + // If anything below fails, just leave the accelerator text blank. 1.1026 + nsWeakFrame weakFrame(this); 1.1027 + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, aNotify); 1.1028 + ENSURE_TRUE(weakFrame.IsAlive()); 1.1029 + 1.1030 + // See if we have a key node and use that instead. 1.1031 + nsAutoString keyValue; 1.1032 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue); 1.1033 + if (keyValue.IsEmpty()) 1.1034 + return; 1.1035 + 1.1036 + // Turn the document into a DOM document so we can use getElementById 1.1037 + nsIDocument *document = mContent->GetDocument(); 1.1038 + if (!document) 1.1039 + return; 1.1040 + 1.1041 + nsIContent *keyElement = document->GetElementById(keyValue); 1.1042 + if (!keyElement) { 1.1043 +#ifdef DEBUG 1.1044 + nsAutoString label; 1.1045 + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label); 1.1046 + nsAutoString msg = NS_LITERAL_STRING("Key '") + 1.1047 + keyValue + 1.1048 + NS_LITERAL_STRING("' of menu item '") + 1.1049 + label + 1.1050 + NS_LITERAL_STRING("' could not be found"); 1.1051 + NS_WARNING(NS_ConvertUTF16toUTF8(msg).get()); 1.1052 +#endif 1.1053 + return; 1.1054 + } 1.1055 + 1.1056 + // get the string to display as accelerator text 1.1057 + // check the key element's attributes in this order: 1.1058 + // |keytext|, |key|, |keycode| 1.1059 + nsAutoString accelString; 1.1060 + keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keytext, accelString); 1.1061 + 1.1062 + if (accelString.IsEmpty()) { 1.1063 + keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, accelString); 1.1064 + 1.1065 + if (!accelString.IsEmpty()) { 1.1066 + ToUpperCase(accelString); 1.1067 + } else { 1.1068 + nsAutoString keyCode; 1.1069 + keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCode); 1.1070 + ToUpperCase(keyCode); 1.1071 + 1.1072 + nsresult rv; 1.1073 + nsCOMPtr<nsIStringBundleService> bundleService = 1.1074 + mozilla::services::GetStringBundleService(); 1.1075 + if (bundleService) { 1.1076 + nsCOMPtr<nsIStringBundle> bundle; 1.1077 + rv = bundleService->CreateBundle("chrome://global/locale/keys.properties", 1.1078 + getter_AddRefs(bundle)); 1.1079 + 1.1080 + if (NS_SUCCEEDED(rv) && bundle) { 1.1081 + nsXPIDLString keyName; 1.1082 + rv = bundle->GetStringFromName(keyCode.get(), getter_Copies(keyName)); 1.1083 + if (keyName) 1.1084 + accelString = keyName; 1.1085 + } 1.1086 + } 1.1087 + 1.1088 + // nothing usable found, bail 1.1089 + if (accelString.IsEmpty()) 1.1090 + return; 1.1091 + } 1.1092 + } 1.1093 + 1.1094 + static int32_t accelKey = 0; 1.1095 + 1.1096 + if (!accelKey) 1.1097 + { 1.1098 + // Compiled-in defaults, in case we can't get LookAndFeel -- 1.1099 + // command for mac, control for all other platforms. 1.1100 +#ifdef XP_MACOSX 1.1101 + accelKey = nsIDOMKeyEvent::DOM_VK_META; 1.1102 +#else 1.1103 + accelKey = nsIDOMKeyEvent::DOM_VK_CONTROL; 1.1104 +#endif 1.1105 + 1.1106 + // Get the accelerator key value from prefs, overriding the default: 1.1107 + accelKey = Preferences::GetInt("ui.key.accelKey", accelKey); 1.1108 + } 1.1109 + 1.1110 + nsAutoString modifiers; 1.1111 + keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers); 1.1112 + 1.1113 + char* str = ToNewCString(modifiers); 1.1114 + char* newStr; 1.1115 + char* token = nsCRT::strtok(str, ", \t", &newStr); 1.1116 + 1.1117 + nsAutoString shiftText; 1.1118 + nsAutoString altText; 1.1119 + nsAutoString metaText; 1.1120 + nsAutoString controlText; 1.1121 + nsAutoString osText; 1.1122 + nsAutoString modifierSeparator; 1.1123 + 1.1124 + nsContentUtils::GetShiftText(shiftText); 1.1125 + nsContentUtils::GetAltText(altText); 1.1126 + nsContentUtils::GetMetaText(metaText); 1.1127 + nsContentUtils::GetControlText(controlText); 1.1128 + nsContentUtils::GetOSText(osText); 1.1129 + nsContentUtils::GetModifierSeparatorText(modifierSeparator); 1.1130 + 1.1131 + while (token) { 1.1132 + 1.1133 + if (PL_strcmp(token, "shift") == 0) 1.1134 + accelText += shiftText; 1.1135 + else if (PL_strcmp(token, "alt") == 0) 1.1136 + accelText += altText; 1.1137 + else if (PL_strcmp(token, "meta") == 0) 1.1138 + accelText += metaText; 1.1139 + else if (PL_strcmp(token, "os") == 0) 1.1140 + accelText += osText; 1.1141 + else if (PL_strcmp(token, "control") == 0) 1.1142 + accelText += controlText; 1.1143 + else if (PL_strcmp(token, "accel") == 0) { 1.1144 + switch (accelKey) 1.1145 + { 1.1146 + case nsIDOMKeyEvent::DOM_VK_META: 1.1147 + accelText += metaText; 1.1148 + break; 1.1149 + 1.1150 + case nsIDOMKeyEvent::DOM_VK_WIN: 1.1151 + accelText += osText; 1.1152 + break; 1.1153 + 1.1154 + case nsIDOMKeyEvent::DOM_VK_ALT: 1.1155 + accelText += altText; 1.1156 + break; 1.1157 + 1.1158 + case nsIDOMKeyEvent::DOM_VK_CONTROL: 1.1159 + default: 1.1160 + accelText += controlText; 1.1161 + break; 1.1162 + } 1.1163 + } 1.1164 + 1.1165 + accelText += modifierSeparator; 1.1166 + 1.1167 + token = nsCRT::strtok(newStr, ", \t", &newStr); 1.1168 + } 1.1169 + 1.1170 + nsMemory::Free(str); 1.1171 + 1.1172 + accelText += accelString; 1.1173 + 1.1174 + mIgnoreAccelTextChange = true; 1.1175 + mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText, aNotify); 1.1176 + ENSURE_TRUE(weakFrame.IsAlive()); 1.1177 + 1.1178 + mIgnoreAccelTextChange = false; 1.1179 +} 1.1180 + 1.1181 +void 1.1182 +nsMenuFrame::Execute(WidgetGUIEvent* aEvent) 1.1183 +{ 1.1184 + // flip "checked" state if we're a checkbox menu, or an un-checked radio menu 1.1185 + bool needToFlipChecked = false; 1.1186 + if (mType == eMenuType_Checkbox || (mType == eMenuType_Radio && !mChecked)) { 1.1187 + needToFlipChecked = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck, 1.1188 + nsGkAtoms::_false, eCaseMatters); 1.1189 + } 1.1190 + 1.1191 + nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1")); 1.1192 + if (sound) 1.1193 + sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE); 1.1194 + 1.1195 + StartBlinking(aEvent, needToFlipChecked); 1.1196 +} 1.1197 + 1.1198 +bool 1.1199 +nsMenuFrame::ShouldBlink() 1.1200 +{ 1.1201 + int32_t shouldBlink = 1.1202 + LookAndFeel::GetInt(LookAndFeel::eIntID_ChosenMenuItemsShouldBlink, 0); 1.1203 + if (!shouldBlink) 1.1204 + return false; 1.1205 + 1.1206 + // Don't blink in editable menulists. 1.1207 + if (GetParentMenuListType() == eEditableMenuList) 1.1208 + return false; 1.1209 + 1.1210 + return true; 1.1211 +} 1.1212 + 1.1213 +void 1.1214 +nsMenuFrame::StartBlinking(WidgetGUIEvent* aEvent, bool aFlipChecked) 1.1215 +{ 1.1216 + StopBlinking(); 1.1217 + CreateMenuCommandEvent(aEvent, aFlipChecked); 1.1218 + 1.1219 + if (!ShouldBlink()) { 1.1220 + PassMenuCommandEventToPopupManager(); 1.1221 + return; 1.1222 + } 1.1223 + 1.1224 + // Blink off. 1.1225 + nsWeakFrame weakFrame(this); 1.1226 + mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true); 1.1227 + if (!weakFrame.IsAlive()) 1.1228 + return; 1.1229 + 1.1230 + if (mMenuParent) { 1.1231 + // Make this menu ignore events from now on. 1.1232 + mMenuParent->LockMenuUntilClosed(true); 1.1233 + } 1.1234 + 1.1235 + // Set up a timer to blink back on. 1.1236 + mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.1237 + mBlinkTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT); 1.1238 + mBlinkState = 1; 1.1239 +} 1.1240 + 1.1241 +void 1.1242 +nsMenuFrame::StopBlinking() 1.1243 +{ 1.1244 + mBlinkState = 0; 1.1245 + if (mBlinkTimer) { 1.1246 + mBlinkTimer->Cancel(); 1.1247 + mBlinkTimer = nullptr; 1.1248 + } 1.1249 + mDelayedMenuCommandEvent = nullptr; 1.1250 +} 1.1251 + 1.1252 +void 1.1253 +nsMenuFrame::CreateMenuCommandEvent(WidgetGUIEvent* aEvent, bool aFlipChecked) 1.1254 +{ 1.1255 + // Create a trusted event if the triggering event was trusted, or if 1.1256 + // we're called from chrome code (since at least one of our caller 1.1257 + // passes in a null event). 1.1258 + bool isTrusted = aEvent ? aEvent->mFlags.mIsTrusted : 1.1259 + nsContentUtils::IsCallerChrome(); 1.1260 + 1.1261 + bool shift = false, control = false, alt = false, meta = false; 1.1262 + WidgetInputEvent* inputEvent = aEvent ? aEvent->AsInputEvent() : nullptr; 1.1263 + if (inputEvent) { 1.1264 + shift = inputEvent->IsShift(); 1.1265 + control = inputEvent->IsControl(); 1.1266 + alt = inputEvent->IsAlt(); 1.1267 + meta = inputEvent->IsMeta(); 1.1268 + } 1.1269 + 1.1270 + // Because the command event is firing asynchronously, a flag is needed to 1.1271 + // indicate whether user input is being handled. This ensures that a popup 1.1272 + // window won't get blocked. 1.1273 + bool userinput = EventStateManager::IsHandlingUserInput(); 1.1274 + 1.1275 + mDelayedMenuCommandEvent = 1.1276 + new nsXULMenuCommandEvent(mContent, isTrusted, shift, control, alt, meta, 1.1277 + userinput, aFlipChecked); 1.1278 +} 1.1279 + 1.1280 +void 1.1281 +nsMenuFrame::PassMenuCommandEventToPopupManager() 1.1282 +{ 1.1283 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.1284 + if (pm && mMenuParent && mDelayedMenuCommandEvent) { 1.1285 + pm->ExecuteMenu(mContent, mDelayedMenuCommandEvent); 1.1286 + } 1.1287 + mDelayedMenuCommandEvent = nullptr; 1.1288 +} 1.1289 + 1.1290 +nsresult 1.1291 +nsMenuFrame::RemoveFrame(ChildListID aListID, 1.1292 + nsIFrame* aOldFrame) 1.1293 +{ 1.1294 + nsFrameList* popupList = GetPopupList(); 1.1295 + if (popupList && popupList->FirstChild() == aOldFrame) { 1.1296 + popupList->RemoveFirstChild(); 1.1297 + aOldFrame->Destroy(); 1.1298 + DestroyPopupList(); 1.1299 + PresContext()->PresShell()-> 1.1300 + FrameNeedsReflow(this, nsIPresShell::eTreeChange, 1.1301 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.1302 + return NS_OK; 1.1303 + } 1.1304 + return nsBoxFrame::RemoveFrame(aListID, aOldFrame); 1.1305 +} 1.1306 + 1.1307 +nsresult 1.1308 +nsMenuFrame::InsertFrames(ChildListID aListID, 1.1309 + nsIFrame* aPrevFrame, 1.1310 + nsFrameList& aFrameList) 1.1311 +{ 1.1312 + if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { 1.1313 + SetPopupFrame(aFrameList); 1.1314 + if (HasPopup()) { 1.1315 +#ifdef DEBUG_LAYOUT 1.1316 + nsBoxLayoutState state(PresContext()); 1.1317 + SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG); 1.1318 +#endif 1.1319 + 1.1320 + PresContext()->PresShell()-> 1.1321 + FrameNeedsReflow(this, nsIPresShell::eTreeChange, 1.1322 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.1323 + } 1.1324 + } 1.1325 + 1.1326 + if (aFrameList.IsEmpty()) 1.1327 + return NS_OK; 1.1328 + 1.1329 + if (MOZ_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) { 1.1330 + aPrevFrame = nullptr; 1.1331 + } 1.1332 + 1.1333 + return nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); 1.1334 +} 1.1335 + 1.1336 +nsresult 1.1337 +nsMenuFrame::AppendFrames(ChildListID aListID, 1.1338 + nsFrameList& aFrameList) 1.1339 +{ 1.1340 + if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) { 1.1341 + SetPopupFrame(aFrameList); 1.1342 + if (HasPopup()) { 1.1343 + 1.1344 +#ifdef DEBUG_LAYOUT 1.1345 + nsBoxLayoutState state(PresContext()); 1.1346 + SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG); 1.1347 +#endif 1.1348 + PresContext()->PresShell()-> 1.1349 + FrameNeedsReflow(this, nsIPresShell::eTreeChange, 1.1350 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.1351 + } 1.1352 + } 1.1353 + 1.1354 + if (aFrameList.IsEmpty()) 1.1355 + return NS_OK; 1.1356 + 1.1357 + return nsBoxFrame::AppendFrames(aListID, aFrameList); 1.1358 +} 1.1359 + 1.1360 +bool 1.1361 +nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize) 1.1362 +{ 1.1363 + if (!IsCollapsed()) { 1.1364 + bool widthSet, heightSet; 1.1365 + nsSize tmpSize(-1, 0); 1.1366 + nsIFrame::AddCSSPrefSize(this, tmpSize, widthSet, heightSet); 1.1367 + if (!widthSet && GetFlex(aState) == 0) { 1.1368 + nsMenuPopupFrame* popupFrame = GetPopup(); 1.1369 + if (!popupFrame) 1.1370 + return false; 1.1371 + tmpSize = popupFrame->GetPrefSize(aState); 1.1372 + 1.1373 + // Produce a size such that: 1.1374 + // (1) the menu and its popup can be the same width 1.1375 + // (2) there's enough room in the menu for the content and its 1.1376 + // border-padding 1.1377 + // (3) there's enough room in the popup for the content and its 1.1378 + // scrollbar 1.1379 + nsMargin borderPadding; 1.1380 + GetBorderAndPadding(borderPadding); 1.1381 + 1.1382 + // if there is a scroll frame, add the desired width of the scrollbar as well 1.1383 + nsIScrollableFrame* scrollFrame = do_QueryFrame(popupFrame->GetFirstPrincipalChild()); 1.1384 + nscoord scrollbarWidth = 0; 1.1385 + if (scrollFrame) { 1.1386 + scrollbarWidth = 1.1387 + scrollFrame->GetDesiredScrollbarSizes(&aState).LeftRight(); 1.1388 + } 1.1389 + 1.1390 + aSize.width = 1.1391 + tmpSize.width + std::max(borderPadding.LeftRight(), scrollbarWidth); 1.1392 + 1.1393 + return true; 1.1394 + } 1.1395 + } 1.1396 + 1.1397 + return false; 1.1398 +} 1.1399 + 1.1400 +nsSize 1.1401 +nsMenuFrame::GetPrefSize(nsBoxLayoutState& aState) 1.1402 +{ 1.1403 + nsSize size = nsBoxFrame::GetPrefSize(aState); 1.1404 + DISPLAY_PREF_SIZE(this, size); 1.1405 + 1.1406 + // If we are using sizetopopup="always" then 1.1407 + // nsBoxFrame will already have enforced the minimum size 1.1408 + if (!IsSizedToPopup(mContent, true) && 1.1409 + IsSizedToPopup(mContent, false) && 1.1410 + SizeToPopup(aState, size)) { 1.1411 + // We now need to ensure that size is within the min - max range. 1.1412 + nsSize minSize = nsBoxFrame::GetMinSize(aState); 1.1413 + nsSize maxSize = GetMaxSize(aState); 1.1414 + size = BoundsCheck(minSize, size, maxSize); 1.1415 + } 1.1416 + 1.1417 + return size; 1.1418 +} 1.1419 + 1.1420 +NS_IMETHODIMP 1.1421 +nsMenuFrame::GetActiveChild(nsIDOMElement** aResult) 1.1422 +{ 1.1423 + nsMenuPopupFrame* popupFrame = GetPopup(); 1.1424 + if (!popupFrame) 1.1425 + return NS_ERROR_FAILURE; 1.1426 + 1.1427 + nsMenuFrame* menuFrame = popupFrame->GetCurrentMenuItem(); 1.1428 + if (!menuFrame) { 1.1429 + *aResult = nullptr; 1.1430 + } 1.1431 + else { 1.1432 + nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(menuFrame->GetContent())); 1.1433 + *aResult = elt; 1.1434 + NS_IF_ADDREF(*aResult); 1.1435 + } 1.1436 + 1.1437 + return NS_OK; 1.1438 +} 1.1439 + 1.1440 +NS_IMETHODIMP 1.1441 +nsMenuFrame::SetActiveChild(nsIDOMElement* aChild) 1.1442 +{ 1.1443 + nsMenuPopupFrame* popupFrame = GetPopup(); 1.1444 + if (!popupFrame) 1.1445 + return NS_ERROR_FAILURE; 1.1446 + 1.1447 + if (!aChild) { 1.1448 + // Remove the current selection 1.1449 + popupFrame->ChangeMenuItem(nullptr, false); 1.1450 + return NS_OK; 1.1451 + } 1.1452 + 1.1453 + nsCOMPtr<nsIContent> child(do_QueryInterface(aChild)); 1.1454 + 1.1455 + nsMenuFrame* menu = do_QueryFrame(child->GetPrimaryFrame()); 1.1456 + if (menu) 1.1457 + popupFrame->ChangeMenuItem(menu, false); 1.1458 + return NS_OK; 1.1459 +} 1.1460 + 1.1461 +nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame() 1.1462 +{ 1.1463 + nsMenuPopupFrame* popupFrame = GetPopup(); 1.1464 + if (!popupFrame) 1.1465 + return nullptr; 1.1466 + nsIFrame* childFrame = popupFrame->GetFirstPrincipalChild(); 1.1467 + if (childFrame) 1.1468 + return popupFrame->GetScrollFrame(childFrame); 1.1469 + return nullptr; 1.1470 +} 1.1471 + 1.1472 +// nsMenuTimerMediator implementation. 1.1473 +NS_IMPL_ISUPPORTS(nsMenuTimerMediator, nsITimerCallback) 1.1474 + 1.1475 +/** 1.1476 + * Constructs a wrapper around an nsMenuFrame. 1.1477 + * @param aFrame nsMenuFrame to create a wrapper around. 1.1478 + */ 1.1479 +nsMenuTimerMediator::nsMenuTimerMediator(nsMenuFrame *aFrame) : 1.1480 + mFrame(aFrame) 1.1481 +{ 1.1482 + NS_ASSERTION(mFrame, "Must have frame"); 1.1483 +} 1.1484 + 1.1485 +nsMenuTimerMediator::~nsMenuTimerMediator() 1.1486 +{ 1.1487 +} 1.1488 + 1.1489 +/** 1.1490 + * Delegates the notification to the contained frame if it has not been destroyed. 1.1491 + * @param aTimer Timer which initiated the callback. 1.1492 + * @return NS_ERROR_FAILURE if the frame has been destroyed. 1.1493 + */ 1.1494 +NS_IMETHODIMP nsMenuTimerMediator::Notify(nsITimer* aTimer) 1.1495 +{ 1.1496 + if (!mFrame) 1.1497 + return NS_ERROR_FAILURE; 1.1498 + 1.1499 + return mFrame->Notify(aTimer); 1.1500 +} 1.1501 + 1.1502 +/** 1.1503 + * Clear the pointer to the contained nsMenuFrame. This should be called 1.1504 + * when the contained nsMenuFrame is destroyed. 1.1505 + */ 1.1506 +void nsMenuTimerMediator::ClearFrame() 1.1507 +{ 1.1508 + mFrame = nullptr; 1.1509 +}