layout/xul/nsMenuFrame.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial