layout/xul/nsXULPopupManager.cpp

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; 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 "nsXULPopupManager.h"
michael@0 8 #include "nsMenuFrame.h"
michael@0 9 #include "nsMenuPopupFrame.h"
michael@0 10 #include "nsMenuBarFrame.h"
michael@0 11 #include "nsIPopupBoxObject.h"
michael@0 12 #include "nsMenuBarListener.h"
michael@0 13 #include "nsContentUtils.h"
michael@0 14 #include "nsIDOMDocument.h"
michael@0 15 #include "nsIDOMEvent.h"
michael@0 16 #include "nsIDOMXULElement.h"
michael@0 17 #include "nsIXULDocument.h"
michael@0 18 #include "nsIXULTemplateBuilder.h"
michael@0 19 #include "nsCSSFrameConstructor.h"
michael@0 20 #include "nsLayoutUtils.h"
michael@0 21 #include "nsViewManager.h"
michael@0 22 #include "nsIComponentManager.h"
michael@0 23 #include "nsITimer.h"
michael@0 24 #include "nsFocusManager.h"
michael@0 25 #include "nsIDocShell.h"
michael@0 26 #include "nsPIDOMWindow.h"
michael@0 27 #include "nsIInterfaceRequestorUtils.h"
michael@0 28 #include "nsIBaseWindow.h"
michael@0 29 #include "nsIDOMKeyEvent.h"
michael@0 30 #include "nsIDOMMouseEvent.h"
michael@0 31 #include "nsCaret.h"
michael@0 32 #include "nsIDocument.h"
michael@0 33 #include "nsPIWindowRoot.h"
michael@0 34 #include "nsFrameManager.h"
michael@0 35 #include "nsIObserverService.h"
michael@0 36 #include "mozilla/dom/Element.h"
michael@0 37 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
michael@0 38 #include "mozilla/EventDispatcher.h"
michael@0 39 #include "mozilla/EventStateManager.h"
michael@0 40 #include "mozilla/LookAndFeel.h"
michael@0 41 #include "mozilla/MouseEvents.h"
michael@0 42 #include "mozilla/Services.h"
michael@0 43
michael@0 44 using namespace mozilla;
michael@0 45 using namespace mozilla::dom;
michael@0 46
michael@0 47 static_assert(nsIDOMKeyEvent::DOM_VK_HOME == nsIDOMKeyEvent::DOM_VK_END + 1 &&
michael@0 48 nsIDOMKeyEvent::DOM_VK_LEFT == nsIDOMKeyEvent::DOM_VK_END + 2 &&
michael@0 49 nsIDOMKeyEvent::DOM_VK_UP == nsIDOMKeyEvent::DOM_VK_END + 3 &&
michael@0 50 nsIDOMKeyEvent::DOM_VK_RIGHT == nsIDOMKeyEvent::DOM_VK_END + 4 &&
michael@0 51 nsIDOMKeyEvent::DOM_VK_DOWN == nsIDOMKeyEvent::DOM_VK_END + 5,
michael@0 52 "nsXULPopupManager assumes some keyCode values are consecutive");
michael@0 53
michael@0 54 const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = {
michael@0 55 {
michael@0 56 eNavigationDirection_Last, // nsIDOMKeyEvent::DOM_VK_END
michael@0 57 eNavigationDirection_First, // nsIDOMKeyEvent::DOM_VK_HOME
michael@0 58 eNavigationDirection_Start, // nsIDOMKeyEvent::DOM_VK_LEFT
michael@0 59 eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP
michael@0 60 eNavigationDirection_End, // nsIDOMKeyEvent::DOM_VK_RIGHT
michael@0 61 eNavigationDirection_After // nsIDOMKeyEvent::DOM_VK_DOWN
michael@0 62 },
michael@0 63 {
michael@0 64 eNavigationDirection_Last, // nsIDOMKeyEvent::DOM_VK_END
michael@0 65 eNavigationDirection_First, // nsIDOMKeyEvent::DOM_VK_HOME
michael@0 66 eNavigationDirection_End, // nsIDOMKeyEvent::DOM_VK_LEFT
michael@0 67 eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP
michael@0 68 eNavigationDirection_Start, // nsIDOMKeyEvent::DOM_VK_RIGHT
michael@0 69 eNavigationDirection_After // nsIDOMKeyEvent::DOM_VK_DOWN
michael@0 70 }
michael@0 71 };
michael@0 72
michael@0 73 nsXULPopupManager* nsXULPopupManager::sInstance = nullptr;
michael@0 74
michael@0 75 nsIContent* nsMenuChainItem::Content()
michael@0 76 {
michael@0 77 return mFrame->GetContent();
michael@0 78 }
michael@0 79
michael@0 80 void nsMenuChainItem::SetParent(nsMenuChainItem* aParent)
michael@0 81 {
michael@0 82 if (mParent) {
michael@0 83 NS_ASSERTION(mParent->mChild == this, "Unexpected - parent's child not set to this");
michael@0 84 mParent->mChild = nullptr;
michael@0 85 }
michael@0 86 mParent = aParent;
michael@0 87 if (mParent) {
michael@0 88 if (mParent->mChild)
michael@0 89 mParent->mChild->mParent = nullptr;
michael@0 90 mParent->mChild = this;
michael@0 91 }
michael@0 92 }
michael@0 93
michael@0 94 void nsMenuChainItem::Detach(nsMenuChainItem** aRoot)
michael@0 95 {
michael@0 96 // If the item has a child, set the child's parent to this item's parent,
michael@0 97 // effectively removing the item from the chain. If the item has no child,
michael@0 98 // just set the parent to null.
michael@0 99 if (mChild) {
michael@0 100 NS_ASSERTION(this != *aRoot, "Unexpected - popup with child at end of chain");
michael@0 101 mChild->SetParent(mParent);
michael@0 102 }
michael@0 103 else {
michael@0 104 // An item without a child should be the first item in the chain, so set
michael@0 105 // the first item pointer, pointed to by aRoot, to the parent.
michael@0 106 NS_ASSERTION(this == *aRoot, "Unexpected - popup with no child not at end of chain");
michael@0 107 *aRoot = mParent;
michael@0 108 SetParent(nullptr);
michael@0 109 }
michael@0 110 }
michael@0 111
michael@0 112 NS_IMPL_ISUPPORTS(nsXULPopupManager,
michael@0 113 nsIDOMEventListener,
michael@0 114 nsITimerCallback,
michael@0 115 nsIObserver)
michael@0 116
michael@0 117 nsXULPopupManager::nsXULPopupManager() :
michael@0 118 mRangeOffset(0),
michael@0 119 mCachedMousePoint(0, 0),
michael@0 120 mCachedModifiers(0),
michael@0 121 mActiveMenuBar(nullptr),
michael@0 122 mPopups(nullptr),
michael@0 123 mNoHidePanels(nullptr),
michael@0 124 mTimerMenu(nullptr)
michael@0 125 {
michael@0 126 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
michael@0 127 if (obs) {
michael@0 128 obs->AddObserver(this, "xpcom-shutdown", false);
michael@0 129 }
michael@0 130 }
michael@0 131
michael@0 132 nsXULPopupManager::~nsXULPopupManager()
michael@0 133 {
michael@0 134 NS_ASSERTION(!mPopups && !mNoHidePanels, "XUL popups still open");
michael@0 135 }
michael@0 136
michael@0 137 nsresult
michael@0 138 nsXULPopupManager::Init()
michael@0 139 {
michael@0 140 sInstance = new nsXULPopupManager();
michael@0 141 NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY);
michael@0 142 NS_ADDREF(sInstance);
michael@0 143 return NS_OK;
michael@0 144 }
michael@0 145
michael@0 146 void
michael@0 147 nsXULPopupManager::Shutdown()
michael@0 148 {
michael@0 149 NS_IF_RELEASE(sInstance);
michael@0 150 }
michael@0 151
michael@0 152 NS_IMETHODIMP
michael@0 153 nsXULPopupManager::Observe(nsISupports *aSubject,
michael@0 154 const char *aTopic,
michael@0 155 const char16_t *aData)
michael@0 156 {
michael@0 157 if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
michael@0 158 if (mKeyListener) {
michael@0 159 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
michael@0 160 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
michael@0 161 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
michael@0 162 mKeyListener = nullptr;
michael@0 163 }
michael@0 164 mRangeParent = nullptr;
michael@0 165 // mOpeningPopup is cleared explicitly soon after using it.
michael@0 166 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
michael@0 167 if (obs) {
michael@0 168 obs->RemoveObserver(this, "xpcom-shutdown");
michael@0 169 }
michael@0 170 }
michael@0 171
michael@0 172 return NS_OK;
michael@0 173 }
michael@0 174
michael@0 175 nsXULPopupManager*
michael@0 176 nsXULPopupManager::GetInstance()
michael@0 177 {
michael@0 178 MOZ_ASSERT(sInstance);
michael@0 179 return sInstance;
michael@0 180 }
michael@0 181
michael@0 182 bool
michael@0 183 nsXULPopupManager::Rollup(uint32_t aCount, const nsIntPoint* pos, nsIContent** aLastRolledUp)
michael@0 184 {
michael@0 185 bool consume = false;
michael@0 186
michael@0 187 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 188 if (item) {
michael@0 189 if (aLastRolledUp) {
michael@0 190 // we need to get the popup that will be closed last, so that
michael@0 191 // widget can keep track of it so it doesn't reopen if a mouse
michael@0 192 // down event is going to processed.
michael@0 193 // Keep going up the menu chain to get the first level menu. This will
michael@0 194 // be the one that closes up last. It's possible that this menu doesn't
michael@0 195 // end up closing because the popuphiding event was cancelled, but in
michael@0 196 // that case we don't need to deal with the menu reopening as it will
michael@0 197 // already still be open.
michael@0 198 nsMenuChainItem* first = item;
michael@0 199 while (first->GetParent())
michael@0 200 first = first->GetParent();
michael@0 201 *aLastRolledUp = first->Content();
michael@0 202 }
michael@0 203
michael@0 204 consume = item->Frame()->ConsumeOutsideClicks();
michael@0 205 // If the click was over the anchor, always consume the click. This way,
michael@0 206 // clicking on a menu doesn't reopen the menu.
michael@0 207 if (!consume && pos) {
michael@0 208 nsCOMPtr<nsIContent> anchor = item->Frame()->GetAnchor();
michael@0 209 if (anchor && anchor->GetPrimaryFrame()) {
michael@0 210 // It's possible that some other element is above the anchor at the same
michael@0 211 // position, but the only thing that would happen is that the mouse
michael@0 212 // event will get consumed, so here only a quick coordinates check is
michael@0 213 // done rather than a slower complete check of what is at that location.
michael@0 214 if (anchor->GetPrimaryFrame()->GetScreenRect().Contains(*pos)) {
michael@0 215 consume = true;
michael@0 216 }
michael@0 217 }
michael@0 218 }
michael@0 219
michael@0 220 // if a number of popups to close has been specified, determine the last
michael@0 221 // popup to close
michael@0 222 nsIContent* lastPopup = nullptr;
michael@0 223 if (aCount != UINT32_MAX) {
michael@0 224 nsMenuChainItem* last = item;
michael@0 225 while (--aCount && last->GetParent()) {
michael@0 226 last = last->GetParent();
michael@0 227 }
michael@0 228 if (last) {
michael@0 229 lastPopup = last->Content();
michael@0 230 }
michael@0 231 }
michael@0 232
michael@0 233 HidePopup(item->Content(), true, true, false, true, lastPopup);
michael@0 234 }
michael@0 235
michael@0 236 return consume;
michael@0 237 }
michael@0 238
michael@0 239 ////////////////////////////////////////////////////////////////////////
michael@0 240 bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent()
michael@0 241 {
michael@0 242 // should rollup only for autocomplete widgets
michael@0 243 // XXXndeakin this should really be something the popup has more control over
michael@0 244
michael@0 245 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 246 if (!item)
michael@0 247 return false;
michael@0 248
michael@0 249 nsIContent* content = item->Frame()->GetContent();
michael@0 250 if (!content)
michael@0 251 return false;
michael@0 252
michael@0 253 if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
michael@0 254 nsGkAtoms::_true, eCaseMatters))
michael@0 255 return true;
michael@0 256
michael@0 257 if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
michael@0 258 nsGkAtoms::_false, eCaseMatters))
michael@0 259 return false;
michael@0 260
michael@0 261 nsAutoString value;
michael@0 262 content->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
michael@0 263 return StringBeginsWith(value, NS_LITERAL_STRING("autocomplete"));
michael@0 264 }
michael@0 265
michael@0 266 bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent()
michael@0 267 {
michael@0 268 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 269 if (!item)
michael@0 270 return false;
michael@0 271
michael@0 272 nsMenuPopupFrame* frame = item->Frame();
michael@0 273 if (frame->PopupType() != ePopupTypePanel)
michael@0 274 return true;
michael@0 275
michael@0 276 nsIContent* content = frame->GetContent();
michael@0 277 return !(content && content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
michael@0 278 nsGkAtoms::arrow, eCaseMatters));
michael@0 279 }
michael@0 280
michael@0 281 // a menu should not roll up if activated by a mouse activate message (eg. X-mouse)
michael@0 282 bool nsXULPopupManager::ShouldRollupOnMouseActivate()
michael@0 283 {
michael@0 284 return false;
michael@0 285 }
michael@0 286
michael@0 287 uint32_t
michael@0 288 nsXULPopupManager::GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain)
michael@0 289 {
michael@0 290 // this method is used by the widget code to determine the list of popups
michael@0 291 // that are open. If a mouse click occurs outside one of these popups, the
michael@0 292 // panels will roll up. If the click is inside a popup, they will not roll up
michael@0 293 uint32_t count = 0, sameTypeCount = 0;
michael@0 294
michael@0 295 NS_ASSERTION(aWidgetChain, "null parameter");
michael@0 296 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 297 while (item) {
michael@0 298 nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget();
michael@0 299 NS_ASSERTION(widget, "open popup has no widget");
michael@0 300 aWidgetChain->AppendElement(widget.get());
michael@0 301 // In the case when a menulist inside a panel is open, clicking in the
michael@0 302 // panel should still roll up the menu, so if a different type is found,
michael@0 303 // stop scanning.
michael@0 304 nsMenuChainItem* parent = item->GetParent();
michael@0 305 if (!sameTypeCount) {
michael@0 306 count++;
michael@0 307 if (!parent || item->Frame()->PopupType() != parent->Frame()->PopupType() ||
michael@0 308 item->IsContextMenu() != parent->IsContextMenu()) {
michael@0 309 sameTypeCount = count;
michael@0 310 }
michael@0 311 }
michael@0 312 item = parent;
michael@0 313 }
michael@0 314
michael@0 315 return sameTypeCount;
michael@0 316 }
michael@0 317
michael@0 318 nsIWidget*
michael@0 319 nsXULPopupManager::GetRollupWidget()
michael@0 320 {
michael@0 321 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 322 return item ? item->Frame()->GetWidget() : nullptr;
michael@0 323 }
michael@0 324
michael@0 325 void
michael@0 326 nsXULPopupManager::AdjustPopupsOnWindowChange(nsPIDOMWindow* aWindow)
michael@0 327 {
michael@0 328 // When the parent window is moved, adjust any child popups. Dismissable
michael@0 329 // menus and panels are expected to roll up when a window is moved, so there
michael@0 330 // is no need to check these popups, only the noautohide popups.
michael@0 331
michael@0 332 // The items are added to a list so that they can be adjusted bottom to top.
michael@0 333 nsTArray<nsMenuPopupFrame *> list;
michael@0 334
michael@0 335 nsMenuChainItem* item = mNoHidePanels;
michael@0 336 while (item) {
michael@0 337 // only move popups that are within the same window and where auto
michael@0 338 // positioning has not been disabled
michael@0 339 nsMenuPopupFrame* frame = item->Frame();
michael@0 340 if (frame->GetAutoPosition()) {
michael@0 341 nsIContent* popup = frame->GetContent();
michael@0 342 if (popup) {
michael@0 343 nsIDocument* document = popup->GetCurrentDoc();
michael@0 344 if (document) {
michael@0 345 nsPIDOMWindow* window = document->GetWindow();
michael@0 346 if (window) {
michael@0 347 window = window->GetPrivateRoot();
michael@0 348 if (window == aWindow) {
michael@0 349 list.AppendElement(frame);
michael@0 350 }
michael@0 351 }
michael@0 352 }
michael@0 353 }
michael@0 354 }
michael@0 355
michael@0 356 item = item->GetParent();
michael@0 357 }
michael@0 358
michael@0 359 for (int32_t l = list.Length() - 1; l >= 0; l--) {
michael@0 360 list[l]->SetPopupPosition(nullptr, true, false);
michael@0 361 }
michael@0 362 }
michael@0 363
michael@0 364 void nsXULPopupManager::AdjustPopupsOnWindowChange(nsIPresShell* aPresShell)
michael@0 365 {
michael@0 366 if (aPresShell->GetDocument()) {
michael@0 367 AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow());
michael@0 368 }
michael@0 369 }
michael@0 370
michael@0 371 static
michael@0 372 nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame)
michael@0 373 {
michael@0 374 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame);
michael@0 375 if (!menuPopupFrame)
michael@0 376 return nullptr;
michael@0 377
michael@0 378 // no point moving or resizing hidden popups
michael@0 379 if (menuPopupFrame->PopupState() != ePopupOpenAndVisible)
michael@0 380 return nullptr;
michael@0 381
michael@0 382 nsIWidget* widget = menuPopupFrame->GetWidget();
michael@0 383 if (widget && !widget->IsVisible())
michael@0 384 return nullptr;
michael@0 385
michael@0 386 return menuPopupFrame;
michael@0 387 }
michael@0 388
michael@0 389 void
michael@0 390 nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt)
michael@0 391 {
michael@0 392 nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
michael@0 393 if (!menuPopupFrame)
michael@0 394 return;
michael@0 395
michael@0 396 nsView* view = menuPopupFrame->GetView();
michael@0 397 if (!view)
michael@0 398 return;
michael@0 399
michael@0 400 // Don't do anything if the popup is already at the specified location. This
michael@0 401 // prevents recursive calls when a popup is positioned.
michael@0 402 nsIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
michael@0 403 nsIWidget* widget = menuPopupFrame->GetWidget();
michael@0 404 if (curDevSize.x == aPnt.x && curDevSize.y == aPnt.y &&
michael@0 405 (!widget || widget->GetClientOffset() == menuPopupFrame->GetLastClientOffset())) {
michael@0 406 return;
michael@0 407 }
michael@0 408
michael@0 409 // Update the popup's position using SetPopupPosition if the popup is
michael@0 410 // anchored and at the parent level as these maintain their position
michael@0 411 // relative to the parent window. Otherwise, just update the popup to
michael@0 412 // the specified screen coordinates.
michael@0 413 if (menuPopupFrame->IsAnchored() &&
michael@0 414 menuPopupFrame->PopupLevel() == ePopupLevelParent) {
michael@0 415 menuPopupFrame->SetPopupPosition(nullptr, true, false);
michael@0 416 }
michael@0 417 else {
michael@0 418 nsPresContext* presContext = menuPopupFrame->PresContext();
michael@0 419 aPnt.x = presContext->DevPixelsToIntCSSPixels(aPnt.x);
michael@0 420 aPnt.y = presContext->DevPixelsToIntCSSPixels(aPnt.y);
michael@0 421 menuPopupFrame->MoveTo(aPnt.x, aPnt.y, false);
michael@0 422 }
michael@0 423 }
michael@0 424
michael@0 425 void
michael@0 426 nsXULPopupManager::PopupResized(nsIFrame* aFrame, nsIntSize aSize)
michael@0 427 {
michael@0 428 nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
michael@0 429 if (!menuPopupFrame)
michael@0 430 return;
michael@0 431
michael@0 432 nsView* view = menuPopupFrame->GetView();
michael@0 433 if (!view)
michael@0 434 return;
michael@0 435
michael@0 436 nsIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
michael@0 437 // If the size is what we think it is, we have nothing to do.
michael@0 438 if (curDevSize.width == aSize.width && curDevSize.height == aSize.height)
michael@0 439 return;
michael@0 440
michael@0 441 // The size is different. Convert the actual size to css pixels and store it
michael@0 442 // as 'width' and 'height' attributes on the popup.
michael@0 443 nsPresContext* presContext = menuPopupFrame->PresContext();
michael@0 444
michael@0 445 nsIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width),
michael@0 446 presContext->DevPixelsToIntCSSPixels(aSize.height));
michael@0 447
michael@0 448 nsIContent* popup = menuPopupFrame->GetContent();
michael@0 449 nsAutoString width, height;
michael@0 450 width.AppendInt(newCSS.width);
michael@0 451 height.AppendInt(newCSS.height);
michael@0 452 popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false);
michael@0 453 popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
michael@0 454 }
michael@0 455
michael@0 456 nsMenuPopupFrame*
michael@0 457 nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush)
michael@0 458 {
michael@0 459 if (aShouldFlush) {
michael@0 460 nsIDocument *document = aContent->GetCurrentDoc();
michael@0 461 if (document) {
michael@0 462 nsCOMPtr<nsIPresShell> presShell = document->GetShell();
michael@0 463 if (presShell)
michael@0 464 presShell->FlushPendingNotifications(Flush_Layout);
michael@0 465 }
michael@0 466 }
michael@0 467
michael@0 468 return do_QueryFrame(aContent->GetPrimaryFrame());
michael@0 469 }
michael@0 470
michael@0 471 nsMenuChainItem*
michael@0 472 nsXULPopupManager::GetTopVisibleMenu()
michael@0 473 {
michael@0 474 nsMenuChainItem* item = mPopups;
michael@0 475 while (item && item->Frame()->PopupState() == ePopupInvisible)
michael@0 476 item = item->GetParent();
michael@0 477 return item;
michael@0 478 }
michael@0 479
michael@0 480 void
michael@0 481 nsXULPopupManager::GetMouseLocation(nsIDOMNode** aNode, int32_t* aOffset)
michael@0 482 {
michael@0 483 *aNode = mRangeParent;
michael@0 484 NS_IF_ADDREF(*aNode);
michael@0 485 *aOffset = mRangeOffset;
michael@0 486 }
michael@0 487
michael@0 488 void
michael@0 489 nsXULPopupManager::InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup,
michael@0 490 nsIContent** aTriggerContent)
michael@0 491 {
michael@0 492 mCachedMousePoint = nsIntPoint(0, 0);
michael@0 493
michael@0 494 if (aTriggerContent) {
michael@0 495 *aTriggerContent = nullptr;
michael@0 496 if (aEvent) {
michael@0 497 // get the trigger content from the event
michael@0 498 nsCOMPtr<nsIContent> target = do_QueryInterface(
michael@0 499 aEvent->InternalDOMEvent()->GetTarget());
michael@0 500 target.forget(aTriggerContent);
michael@0 501 }
michael@0 502 }
michael@0 503
michael@0 504 mCachedModifiers = 0;
michael@0 505
michael@0 506 nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aEvent);
michael@0 507 if (uiEvent) {
michael@0 508 uiEvent->GetRangeParent(getter_AddRefs(mRangeParent));
michael@0 509 uiEvent->GetRangeOffset(&mRangeOffset);
michael@0 510
michael@0 511 // get the event coordinates relative to the root frame of the document
michael@0 512 // containing the popup.
michael@0 513 NS_ASSERTION(aPopup, "Expected a popup node");
michael@0 514 WidgetEvent* event = aEvent->GetInternalNSEvent();
michael@0 515 if (event) {
michael@0 516 WidgetInputEvent* inputEvent = event->AsInputEvent();
michael@0 517 if (inputEvent) {
michael@0 518 mCachedModifiers = inputEvent->modifiers;
michael@0 519 }
michael@0 520 nsIDocument* doc = aPopup->GetCurrentDoc();
michael@0 521 if (doc) {
michael@0 522 nsIPresShell* presShell = doc->GetShell();
michael@0 523 nsPresContext* presContext;
michael@0 524 if (presShell && (presContext = presShell->GetPresContext())) {
michael@0 525 nsPresContext* rootDocPresContext =
michael@0 526 presContext->GetRootPresContext();
michael@0 527 if (!rootDocPresContext)
michael@0 528 return;
michael@0 529 nsIFrame* rootDocumentRootFrame = rootDocPresContext->
michael@0 530 PresShell()->FrameManager()->GetRootFrame();
michael@0 531 if ((event->eventStructType == NS_MOUSE_EVENT ||
michael@0 532 event->eventStructType == NS_MOUSE_SCROLL_EVENT ||
michael@0 533 event->eventStructType == NS_WHEEL_EVENT) &&
michael@0 534 !event->AsGUIEvent()->widget) {
michael@0 535 // no widget, so just use the client point if available
michael@0 536 nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
michael@0 537 nsIntPoint clientPt;
michael@0 538 mouseEvent->GetClientX(&clientPt.x);
michael@0 539 mouseEvent->GetClientY(&clientPt.y);
michael@0 540
michael@0 541 // XXX this doesn't handle IFRAMEs in transforms
michael@0 542 nsPoint thisDocToRootDocOffset = presShell->FrameManager()->
michael@0 543 GetRootFrame()->GetOffsetToCrossDoc(rootDocumentRootFrame);
michael@0 544 // convert to device pixels
michael@0 545 mCachedMousePoint.x = presContext->AppUnitsToDevPixels(
michael@0 546 nsPresContext::CSSPixelsToAppUnits(clientPt.x) + thisDocToRootDocOffset.x);
michael@0 547 mCachedMousePoint.y = presContext->AppUnitsToDevPixels(
michael@0 548 nsPresContext::CSSPixelsToAppUnits(clientPt.y) + thisDocToRootDocOffset.y);
michael@0 549 }
michael@0 550 else if (rootDocumentRootFrame) {
michael@0 551 nsPoint pnt =
michael@0 552 nsLayoutUtils::GetEventCoordinatesRelativeTo(event, rootDocumentRootFrame);
michael@0 553 mCachedMousePoint = nsIntPoint(rootDocPresContext->AppUnitsToDevPixels(pnt.x),
michael@0 554 rootDocPresContext->AppUnitsToDevPixels(pnt.y));
michael@0 555 }
michael@0 556 }
michael@0 557 }
michael@0 558 }
michael@0 559 }
michael@0 560 else {
michael@0 561 mRangeParent = nullptr;
michael@0 562 mRangeOffset = 0;
michael@0 563 }
michael@0 564 }
michael@0 565
michael@0 566 void
michael@0 567 nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate)
michael@0 568 {
michael@0 569 if (aActivate)
michael@0 570 mActiveMenuBar = aMenuBar;
michael@0 571 else if (mActiveMenuBar == aMenuBar)
michael@0 572 mActiveMenuBar = nullptr;
michael@0 573
michael@0 574 UpdateKeyboardListeners();
michael@0 575 }
michael@0 576
michael@0 577 void
michael@0 578 nsXULPopupManager::ShowMenu(nsIContent *aMenu,
michael@0 579 bool aSelectFirstItem,
michael@0 580 bool aAsynchronous)
michael@0 581 {
michael@0 582 // generate any template content first. Otherwise, the menupopup may not
michael@0 583 // have been created yet.
michael@0 584 if (aMenu) {
michael@0 585 nsIContent* element = aMenu;
michael@0 586 do {
michael@0 587 nsCOMPtr<nsIDOMXULElement> xulelem = do_QueryInterface(element);
michael@0 588 if (xulelem) {
michael@0 589 nsCOMPtr<nsIXULTemplateBuilder> builder;
michael@0 590 xulelem->GetBuilder(getter_AddRefs(builder));
michael@0 591 if (builder) {
michael@0 592 builder->CreateContents(aMenu, true);
michael@0 593 break;
michael@0 594 }
michael@0 595 }
michael@0 596 element = element->GetParent();
michael@0 597 } while (element);
michael@0 598 }
michael@0 599
michael@0 600 nsMenuFrame* menuFrame = do_QueryFrame(aMenu->GetPrimaryFrame());
michael@0 601 if (!menuFrame || !menuFrame->IsMenu())
michael@0 602 return;
michael@0 603
michael@0 604 nsMenuPopupFrame* popupFrame = menuFrame->GetPopup();
michael@0 605 if (!popupFrame || !MayShowPopup(popupFrame))
michael@0 606 return;
michael@0 607
michael@0 608 // inherit whether or not we're a context menu from the parent
michael@0 609 bool parentIsContextMenu = false;
michael@0 610 bool onMenuBar = false;
michael@0 611 bool onmenu = menuFrame->IsOnMenu();
michael@0 612
michael@0 613 nsMenuParent* parent = menuFrame->GetMenuParent();
michael@0 614 if (parent && onmenu) {
michael@0 615 parentIsContextMenu = parent->IsContextMenu();
michael@0 616 onMenuBar = parent->IsMenuBar();
michael@0 617 }
michael@0 618
michael@0 619 nsAutoString position;
michael@0 620 if (onMenuBar || !onmenu)
michael@0 621 position.AssignLiteral("after_start");
michael@0 622 else
michael@0 623 position.AssignLiteral("end_before");
michael@0 624
michael@0 625 // there is no trigger event for menus
michael@0 626 InitTriggerEvent(nullptr, nullptr, nullptr);
michael@0 627 popupFrame->InitializePopup(menuFrame->GetAnchor(), nullptr, position, 0, 0, true);
michael@0 628
michael@0 629 if (aAsynchronous) {
michael@0 630 nsCOMPtr<nsIRunnable> event =
michael@0 631 new nsXULPopupShowingEvent(popupFrame->GetContent(),
michael@0 632 parentIsContextMenu, aSelectFirstItem);
michael@0 633 NS_DispatchToCurrentThread(event);
michael@0 634 }
michael@0 635 else {
michael@0 636 nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
michael@0 637 FirePopupShowingEvent(popupContent, parentIsContextMenu, aSelectFirstItem);
michael@0 638 }
michael@0 639 }
michael@0 640
michael@0 641 void
michael@0 642 nsXULPopupManager::ShowPopup(nsIContent* aPopup,
michael@0 643 nsIContent* aAnchorContent,
michael@0 644 const nsAString& aPosition,
michael@0 645 int32_t aXPos, int32_t aYPos,
michael@0 646 bool aIsContextMenu,
michael@0 647 bool aAttributesOverride,
michael@0 648 bool aSelectFirstItem,
michael@0 649 nsIDOMEvent* aTriggerEvent)
michael@0 650 {
michael@0 651 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
michael@0 652 if (!popupFrame || !MayShowPopup(popupFrame))
michael@0 653 return;
michael@0 654
michael@0 655 nsCOMPtr<nsIContent> triggerContent;
michael@0 656 InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
michael@0 657
michael@0 658 popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition,
michael@0 659 aXPos, aYPos, aAttributesOverride);
michael@0 660
michael@0 661 FirePopupShowingEvent(aPopup, aIsContextMenu, aSelectFirstItem);
michael@0 662 }
michael@0 663
michael@0 664 void
michael@0 665 nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup,
michael@0 666 int32_t aXPos, int32_t aYPos,
michael@0 667 bool aIsContextMenu,
michael@0 668 nsIDOMEvent* aTriggerEvent)
michael@0 669 {
michael@0 670 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
michael@0 671 if (!popupFrame || !MayShowPopup(popupFrame))
michael@0 672 return;
michael@0 673
michael@0 674 nsCOMPtr<nsIContent> triggerContent;
michael@0 675 InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
michael@0 676
michael@0 677 popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos, aIsContextMenu);
michael@0 678 FirePopupShowingEvent(aPopup, aIsContextMenu, false);
michael@0 679 }
michael@0 680
michael@0 681 void
michael@0 682 nsXULPopupManager::ShowTooltipAtScreen(nsIContent* aPopup,
michael@0 683 nsIContent* aTriggerContent,
michael@0 684 int32_t aXPos, int32_t aYPos)
michael@0 685 {
michael@0 686 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
michael@0 687 if (!popupFrame || !MayShowPopup(popupFrame))
michael@0 688 return;
michael@0 689
michael@0 690 InitTriggerEvent(nullptr, nullptr, nullptr);
michael@0 691
michael@0 692 nsPresContext* pc = popupFrame->PresContext();
michael@0 693 mCachedMousePoint = nsIntPoint(pc->CSSPixelsToDevPixels(aXPos),
michael@0 694 pc->CSSPixelsToDevPixels(aYPos));
michael@0 695
michael@0 696 // coordinates are relative to the root widget
michael@0 697 nsPresContext* rootPresContext = pc->GetRootPresContext();
michael@0 698 if (rootPresContext) {
michael@0 699 nsIWidget *rootWidget = rootPresContext->GetRootWidget();
michael@0 700 if (rootWidget) {
michael@0 701 mCachedMousePoint -= rootWidget->WidgetToScreenOffset();
michael@0 702 }
michael@0 703 }
michael@0 704
michael@0 705 popupFrame->InitializePopupAtScreen(aTriggerContent, aXPos, aYPos, false);
michael@0 706
michael@0 707 FirePopupShowingEvent(aPopup, false, false);
michael@0 708 }
michael@0 709
michael@0 710 void
michael@0 711 nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup,
michael@0 712 nsIContent* aAnchorContent,
michael@0 713 nsAString& aAnchor,
michael@0 714 nsAString& aAlign,
michael@0 715 int32_t aXPos, int32_t aYPos,
michael@0 716 bool aIsContextMenu)
michael@0 717 {
michael@0 718 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
michael@0 719 if (!popupFrame || !MayShowPopup(popupFrame))
michael@0 720 return;
michael@0 721
michael@0 722 InitTriggerEvent(nullptr, nullptr, nullptr);
michael@0 723
michael@0 724 popupFrame->InitializePopupWithAnchorAlign(aAnchorContent, aAnchor,
michael@0 725 aAlign, aXPos, aYPos);
michael@0 726 FirePopupShowingEvent(aPopup, aIsContextMenu, false);
michael@0 727 }
michael@0 728
michael@0 729 static void
michael@0 730 CheckCaretDrawingState() {
michael@0 731
michael@0 732 // There is 1 caret per document, we need to find the focused
michael@0 733 // document and erase its caret.
michael@0 734 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
michael@0 735 if (fm) {
michael@0 736 nsCOMPtr<nsIDOMWindow> window;
michael@0 737 fm->GetFocusedWindow(getter_AddRefs(window));
michael@0 738 if (!window)
michael@0 739 return;
michael@0 740
michael@0 741 nsCOMPtr<nsIDOMDocument> domDoc;
michael@0 742 nsCOMPtr<nsIDocument> focusedDoc;
michael@0 743 window->GetDocument(getter_AddRefs(domDoc));
michael@0 744 focusedDoc = do_QueryInterface(domDoc);
michael@0 745 if (!focusedDoc)
michael@0 746 return;
michael@0 747
michael@0 748 nsIPresShell* presShell = focusedDoc->GetShell();
michael@0 749 if (!presShell)
michael@0 750 return;
michael@0 751
michael@0 752 nsRefPtr<nsCaret> caret = presShell->GetCaret();
michael@0 753 if (!caret)
michael@0 754 return;
michael@0 755 caret->CheckCaretDrawingState();
michael@0 756 }
michael@0 757 }
michael@0 758
michael@0 759 void
michael@0 760 nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
michael@0 761 nsMenuPopupFrame* aPopupFrame,
michael@0 762 bool aIsContextMenu,
michael@0 763 bool aSelectFirstItem)
michael@0 764 {
michael@0 765 nsPopupType popupType = aPopupFrame->PopupType();
michael@0 766 bool ismenu = (popupType == ePopupTypeMenu);
michael@0 767
michael@0 768 nsMenuChainItem* item =
michael@0 769 new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType);
michael@0 770 if (!item)
michael@0 771 return;
michael@0 772
michael@0 773 // install keyboard event listeners for navigating menus. For panels, the
michael@0 774 // escape key may be used to close the panel. However, the ignorekeys
michael@0 775 // attribute may be used to disable adding these event listeners for popups
michael@0 776 // that want to handle their own keyboard events.
michael@0 777 if (aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorekeys,
michael@0 778 nsGkAtoms::_true, eCaseMatters))
michael@0 779 item->SetIgnoreKeys(true);
michael@0 780
michael@0 781 if (ismenu) {
michael@0 782 // if the menu is on a menubar, use the menubar's listener instead
michael@0 783 nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent());
michael@0 784 if (menuFrame) {
michael@0 785 item->SetOnMenuBar(menuFrame->IsOnMenuBar());
michael@0 786 }
michael@0 787 }
michael@0 788
michael@0 789 // use a weak frame as the popup will set an open attribute if it is a menu
michael@0 790 nsWeakFrame weakFrame(aPopupFrame);
michael@0 791 aPopupFrame->ShowPopup(aIsContextMenu, aSelectFirstItem);
michael@0 792 ENSURE_TRUE(weakFrame.IsAlive());
michael@0 793
michael@0 794 // popups normally hide when an outside click occurs. Panels may use
michael@0 795 // the noautohide attribute to disable this behaviour. It is expected
michael@0 796 // that the application will hide these popups manually. The tooltip
michael@0 797 // listener will handle closing the tooltip also.
michael@0 798 if (aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip) {
michael@0 799 item->SetParent(mNoHidePanels);
michael@0 800 mNoHidePanels = item;
michael@0 801 }
michael@0 802 else {
michael@0 803 nsIContent* oldmenu = nullptr;
michael@0 804 if (mPopups)
michael@0 805 oldmenu = mPopups->Content();
michael@0 806 item->SetParent(mPopups);
michael@0 807 mPopups = item;
michael@0 808 SetCaptureState(oldmenu);
michael@0 809 }
michael@0 810
michael@0 811 if (aSelectFirstItem) {
michael@0 812 nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true);
michael@0 813 aPopupFrame->SetCurrentMenuItem(next);
michael@0 814 }
michael@0 815
michael@0 816 if (ismenu)
michael@0 817 UpdateMenuItems(aPopup);
michael@0 818
michael@0 819 // Caret visibility may have been affected, ensure that
michael@0 820 // the caret isn't now drawn when it shouldn't be.
michael@0 821 CheckCaretDrawingState();
michael@0 822 }
michael@0 823
michael@0 824 void
michael@0 825 nsXULPopupManager::HidePopup(nsIContent* aPopup,
michael@0 826 bool aHideChain,
michael@0 827 bool aDeselectMenu,
michael@0 828 bool aAsynchronous,
michael@0 829 bool aIsRollup,
michael@0 830 nsIContent* aLastPopup)
michael@0 831 {
michael@0 832 // if the popup is on the nohide panels list, remove it but don't close any
michael@0 833 // other panels
michael@0 834 nsMenuPopupFrame* popupFrame = nullptr;
michael@0 835 bool foundPanel = false;
michael@0 836 nsMenuChainItem* item = mNoHidePanels;
michael@0 837 while (item) {
michael@0 838 if (item->Content() == aPopup) {
michael@0 839 foundPanel = true;
michael@0 840 popupFrame = item->Frame();
michael@0 841 break;
michael@0 842 }
michael@0 843 item = item->GetParent();
michael@0 844 }
michael@0 845
michael@0 846 // when removing a menu, all of the child popups must be closed
michael@0 847 nsMenuChainItem* foundMenu = nullptr;
michael@0 848 item = mPopups;
michael@0 849 while (item) {
michael@0 850 if (item->Content() == aPopup) {
michael@0 851 foundMenu = item;
michael@0 852 break;
michael@0 853 }
michael@0 854 item = item->GetParent();
michael@0 855 }
michael@0 856
michael@0 857 nsPopupType type = ePopupTypePanel;
michael@0 858 bool deselectMenu = false;
michael@0 859 nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
michael@0 860 if (foundMenu) {
michael@0 861 // at this point, foundMenu will be set to the found item in the list. If
michael@0 862 // foundMenu is the topmost menu, the one to remove, then there are no other
michael@0 863 // popups to hide. If foundMenu is not the topmost menu, then there may be
michael@0 864 // open submenus below it. In this case, we need to make sure that those
michael@0 865 // submenus are closed up first. To do this, we scan up the menu list to
michael@0 866 // find the topmost popup with only menus between it and foundMenu and
michael@0 867 // close that menu first. In synchronous mode, the FirePopupHidingEvent
michael@0 868 // method will be called which in turn calls HidePopupCallback to close up
michael@0 869 // the next popup in the chain. These two methods will be called in
michael@0 870 // sequence recursively to close up all the necessary popups. In
michael@0 871 // asynchronous mode, a similar process occurs except that the
michael@0 872 // FirePopupHidingEvent method is called asynchronously. In either case,
michael@0 873 // nextPopup is set to the content node of the next popup to close, and
michael@0 874 // lastPopup is set to the last popup in the chain to close, which will be
michael@0 875 // aPopup, or null to close up all menus.
michael@0 876
michael@0 877 nsMenuChainItem* topMenu = foundMenu;
michael@0 878 // Use IsMenu to ensure that foundMenu is a menu and scan down the child
michael@0 879 // list until a non-menu is found. If foundMenu isn't a menu at all, don't
michael@0 880 // scan and just close up this menu.
michael@0 881 if (foundMenu->IsMenu()) {
michael@0 882 item = topMenu->GetChild();
michael@0 883 while (item && item->IsMenu()) {
michael@0 884 topMenu = item;
michael@0 885 item = item->GetChild();
michael@0 886 }
michael@0 887 }
michael@0 888
michael@0 889 deselectMenu = aDeselectMenu;
michael@0 890 popupToHide = topMenu->Content();
michael@0 891 popupFrame = topMenu->Frame();
michael@0 892 type = popupFrame->PopupType();
michael@0 893
michael@0 894 nsMenuChainItem* parent = topMenu->GetParent();
michael@0 895
michael@0 896 // close up another popup if there is one, and we are either hiding the
michael@0 897 // entire chain or the item to hide isn't the topmost popup.
michael@0 898 if (parent && (aHideChain || topMenu != foundMenu))
michael@0 899 nextPopup = parent->Content();
michael@0 900
michael@0 901 lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup);
michael@0 902 }
michael@0 903 else if (foundPanel) {
michael@0 904 popupToHide = aPopup;
michael@0 905 }
michael@0 906
michael@0 907 if (popupFrame) {
michael@0 908 nsPopupState state = popupFrame->PopupState();
michael@0 909 // if the popup is already being hidden, don't attempt to hide it again
michael@0 910 if (state == ePopupHiding)
michael@0 911 return;
michael@0 912 // change the popup state to hiding. Don't set the hiding state if the
michael@0 913 // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
michael@0 914 // run again. In the invisible state, we just want the events to fire.
michael@0 915 if (state != ePopupInvisible)
michael@0 916 popupFrame->SetPopupState(ePopupHiding);
michael@0 917
michael@0 918 // for menus, popupToHide is always the frontmost item in the list to hide.
michael@0 919 if (aAsynchronous) {
michael@0 920 nsCOMPtr<nsIRunnable> event =
michael@0 921 new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
michael@0 922 type, deselectMenu, aIsRollup);
michael@0 923 NS_DispatchToCurrentThread(event);
michael@0 924 }
michael@0 925 else {
michael@0 926 FirePopupHidingEvent(popupToHide, nextPopup, lastPopup,
michael@0 927 popupFrame->PresContext(), type, deselectMenu, aIsRollup);
michael@0 928 }
michael@0 929 }
michael@0 930 }
michael@0 931
michael@0 932 // This is used to hide the popup after a transition finishes.
michael@0 933 class TransitionEnder : public nsIDOMEventListener
michael@0 934 {
michael@0 935 public:
michael@0 936
michael@0 937 nsCOMPtr<nsIContent> mContent;
michael@0 938 bool mDeselectMenu;
michael@0 939
michael@0 940 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
michael@0 941 NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder)
michael@0 942
michael@0 943 TransitionEnder(nsIContent* aContent, bool aDeselectMenu)
michael@0 944 : mContent(aContent), mDeselectMenu(aDeselectMenu)
michael@0 945 {
michael@0 946 }
michael@0 947
michael@0 948 virtual ~TransitionEnder() { }
michael@0 949
michael@0 950 NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) MOZ_OVERRIDE
michael@0 951 {
michael@0 952 mContent->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false);
michael@0 953
michael@0 954 nsMenuPopupFrame* popupFrame = do_QueryFrame(mContent->GetPrimaryFrame());
michael@0 955
michael@0 956 // Now hide the popup. There could be other properties transitioning, but
michael@0 957 // we'll assume they all end at the same time and just hide the popup upon
michael@0 958 // the first one ending.
michael@0 959 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 960 if (pm && popupFrame) {
michael@0 961 pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr,
michael@0 962 popupFrame->PopupType(), mDeselectMenu);
michael@0 963 }
michael@0 964
michael@0 965 return NS_OK;
michael@0 966 }
michael@0 967 };
michael@0 968
michael@0 969 NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder)
michael@0 970 NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder)
michael@0 971 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder)
michael@0 972 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
michael@0 973 NS_INTERFACE_MAP_ENTRY(nsISupports)
michael@0 974 NS_INTERFACE_MAP_END
michael@0 975
michael@0 976 NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent);
michael@0 977
michael@0 978 void
michael@0 979 nsXULPopupManager::HidePopupCallback(nsIContent* aPopup,
michael@0 980 nsMenuPopupFrame* aPopupFrame,
michael@0 981 nsIContent* aNextPopup,
michael@0 982 nsIContent* aLastPopup,
michael@0 983 nsPopupType aPopupType,
michael@0 984 bool aDeselectMenu)
michael@0 985 {
michael@0 986 if (mCloseTimer && mTimerMenu == aPopupFrame) {
michael@0 987 mCloseTimer->Cancel();
michael@0 988 mCloseTimer = nullptr;
michael@0 989 mTimerMenu = nullptr;
michael@0 990 }
michael@0 991
michael@0 992 // The popup to hide is aPopup. Search the list again to find the item that
michael@0 993 // corresponds to the popup to hide aPopup. This is done because it's
michael@0 994 // possible someone added another item (attempted to open another popup)
michael@0 995 // or removed a popup frame during the event processing so the item isn't at
michael@0 996 // the front anymore.
michael@0 997 nsMenuChainItem* item = mNoHidePanels;
michael@0 998 while (item) {
michael@0 999 if (item->Content() == aPopup) {
michael@0 1000 item->Detach(&mNoHidePanels);
michael@0 1001 break;
michael@0 1002 }
michael@0 1003 item = item->GetParent();
michael@0 1004 }
michael@0 1005
michael@0 1006 if (!item) {
michael@0 1007 item = mPopups;
michael@0 1008 while (item) {
michael@0 1009 if (item->Content() == aPopup) {
michael@0 1010 item->Detach(&mPopups);
michael@0 1011 SetCaptureState(aPopup);
michael@0 1012 break;
michael@0 1013 }
michael@0 1014 item = item->GetParent();
michael@0 1015 }
michael@0 1016 }
michael@0 1017
michael@0 1018 delete item;
michael@0 1019
michael@0 1020 nsWeakFrame weakFrame(aPopupFrame);
michael@0 1021 aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed);
michael@0 1022 ENSURE_TRUE(weakFrame.IsAlive());
michael@0 1023
michael@0 1024 // send the popuphidden event synchronously. This event has no default
michael@0 1025 // behaviour.
michael@0 1026 nsEventStatus status = nsEventStatus_eIgnore;
michael@0 1027 WidgetMouseEvent event(true, NS_XUL_POPUP_HIDDEN, nullptr,
michael@0 1028 WidgetMouseEvent::eReal);
michael@0 1029 EventDispatcher::Dispatch(aPopup, aPopupFrame->PresContext(),
michael@0 1030 &event, nullptr, &status);
michael@0 1031 ENSURE_TRUE(weakFrame.IsAlive());
michael@0 1032
michael@0 1033 // if there are more popups to close, look for the next one
michael@0 1034 if (aNextPopup && aPopup != aLastPopup) {
michael@0 1035 nsMenuChainItem* foundMenu = nullptr;
michael@0 1036 nsMenuChainItem* item = mPopups;
michael@0 1037 while (item) {
michael@0 1038 if (item->Content() == aNextPopup) {
michael@0 1039 foundMenu = item;
michael@0 1040 break;
michael@0 1041 }
michael@0 1042 item = item->GetParent();
michael@0 1043 }
michael@0 1044
michael@0 1045 // continue hiding the chain of popups until the last popup aLastPopup
michael@0 1046 // is reached, or until a popup of a different type is reached. This
michael@0 1047 // last check is needed so that a menulist inside a non-menu panel only
michael@0 1048 // closes the menu and not the panel as well.
michael@0 1049 if (foundMenu &&
michael@0 1050 (aLastPopup || aPopupType == foundMenu->PopupType())) {
michael@0 1051
michael@0 1052 nsCOMPtr<nsIContent> popupToHide = item->Content();
michael@0 1053 nsMenuChainItem* parent = item->GetParent();
michael@0 1054
michael@0 1055 nsCOMPtr<nsIContent> nextPopup;
michael@0 1056 if (parent && popupToHide != aLastPopup)
michael@0 1057 nextPopup = parent->Content();
michael@0 1058
michael@0 1059 nsMenuPopupFrame* popupFrame = item->Frame();
michael@0 1060 nsPopupState state = popupFrame->PopupState();
michael@0 1061 if (state == ePopupHiding)
michael@0 1062 return;
michael@0 1063 if (state != ePopupInvisible)
michael@0 1064 popupFrame->SetPopupState(ePopupHiding);
michael@0 1065
michael@0 1066 FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup,
michael@0 1067 popupFrame->PresContext(),
michael@0 1068 foundMenu->PopupType(), aDeselectMenu, false);
michael@0 1069 }
michael@0 1070 }
michael@0 1071 }
michael@0 1072
michael@0 1073 void
michael@0 1074 nsXULPopupManager::HidePopup(nsIFrame* aFrame)
michael@0 1075 {
michael@0 1076 nsMenuPopupFrame* popup = do_QueryFrame(aFrame);
michael@0 1077 if (popup)
michael@0 1078 HidePopup(aFrame->GetContent(), false, true, false, false);
michael@0 1079 }
michael@0 1080
michael@0 1081 void
michael@0 1082 nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup)
michael@0 1083 {
michael@0 1084 // Don't close up immediately.
michael@0 1085 // Kick off a close timer.
michael@0 1086 KillMenuTimer();
michael@0 1087
michael@0 1088 int32_t menuDelay =
michael@0 1089 LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
michael@0 1090
michael@0 1091 // Kick off the timer.
michael@0 1092 mCloseTimer = do_CreateInstance("@mozilla.org/timer;1");
michael@0 1093 mCloseTimer->InitWithCallback(this, menuDelay, nsITimer::TYPE_ONE_SHOT);
michael@0 1094
michael@0 1095 // the popup will call PopupDestroyed if it is destroyed, which checks if it
michael@0 1096 // is set to mTimerMenu, so it should be safe to keep a reference to it
michael@0 1097 mTimerMenu = aPopup;
michael@0 1098 }
michael@0 1099
michael@0 1100 void
michael@0 1101 nsXULPopupManager::HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames,
michael@0 1102 bool aDeselectMenu)
michael@0 1103 {
michael@0 1104 // Create a weak frame list. This is done in a separate array with the
michael@0 1105 // right capacity predetermined, otherwise the array would get resized and
michael@0 1106 // move the weak frame pointers around.
michael@0 1107 nsTArray<nsWeakFrame> weakPopups(aFrames.Length());
michael@0 1108 uint32_t f;
michael@0 1109 for (f = 0; f < aFrames.Length(); f++) {
michael@0 1110 nsWeakFrame* wframe = weakPopups.AppendElement();
michael@0 1111 if (wframe)
michael@0 1112 *wframe = aFrames[f];
michael@0 1113 }
michael@0 1114
michael@0 1115 for (f = 0; f < weakPopups.Length(); f++) {
michael@0 1116 // check to ensure that the frame is still alive before hiding it.
michael@0 1117 if (weakPopups[f].IsAlive()) {
michael@0 1118 nsMenuPopupFrame* frame =
michael@0 1119 static_cast<nsMenuPopupFrame *>(weakPopups[f].GetFrame());
michael@0 1120 frame->HidePopup(true, ePopupInvisible);
michael@0 1121 }
michael@0 1122 }
michael@0 1123
michael@0 1124 SetCaptureState(nullptr);
michael@0 1125 }
michael@0 1126
michael@0 1127 bool
michael@0 1128 nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected)
michael@0 1129 {
michael@0 1130 nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell());
michael@0 1131 while(docShellItem) {
michael@0 1132 if (docShellItem == aExpected)
michael@0 1133 return true;
michael@0 1134
michael@0 1135 nsCOMPtr<nsIDocShellTreeItem> parent;
michael@0 1136 docShellItem->GetParent(getter_AddRefs(parent));
michael@0 1137 docShellItem = parent;
michael@0 1138 }
michael@0 1139
michael@0 1140 return false;
michael@0 1141 }
michael@0 1142
michael@0 1143 void
michael@0 1144 nsXULPopupManager::HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide)
michael@0 1145 {
michael@0 1146 nsTArray<nsMenuPopupFrame *> popupsToHide;
michael@0 1147
michael@0 1148 // iterate to get the set of popup frames to hide
michael@0 1149 nsMenuChainItem* item = mPopups;
michael@0 1150 while (item) {
michael@0 1151 nsMenuChainItem* parent = item->GetParent();
michael@0 1152 if (item->Frame()->PopupState() != ePopupInvisible &&
michael@0 1153 IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) {
michael@0 1154 nsMenuPopupFrame* frame = item->Frame();
michael@0 1155 item->Detach(&mPopups);
michael@0 1156 delete item;
michael@0 1157 popupsToHide.AppendElement(frame);
michael@0 1158 }
michael@0 1159 item = parent;
michael@0 1160 }
michael@0 1161
michael@0 1162 // now look for panels to hide
michael@0 1163 item = mNoHidePanels;
michael@0 1164 while (item) {
michael@0 1165 nsMenuChainItem* parent = item->GetParent();
michael@0 1166 if (item->Frame()->PopupState() != ePopupInvisible &&
michael@0 1167 IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) {
michael@0 1168 nsMenuPopupFrame* frame = item->Frame();
michael@0 1169 item->Detach(&mNoHidePanels);
michael@0 1170 delete item;
michael@0 1171 popupsToHide.AppendElement(frame);
michael@0 1172 }
michael@0 1173 item = parent;
michael@0 1174 }
michael@0 1175
michael@0 1176 HidePopupsInList(popupsToHide, true);
michael@0 1177 }
michael@0 1178
michael@0 1179 void
michael@0 1180 nsXULPopupManager::ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent)
michael@0 1181 {
michael@0 1182 CloseMenuMode cmm = CloseMenuMode_Auto;
michael@0 1183
michael@0 1184 static nsIContent::AttrValuesArray strings[] =
michael@0 1185 {&nsGkAtoms::none, &nsGkAtoms::single, nullptr};
michael@0 1186
michael@0 1187 switch (aMenu->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::closemenu,
michael@0 1188 strings, eCaseMatters)) {
michael@0 1189 case 0:
michael@0 1190 cmm = CloseMenuMode_None;
michael@0 1191 break;
michael@0 1192 case 1:
michael@0 1193 cmm = CloseMenuMode_Single;
michael@0 1194 break;
michael@0 1195 default:
michael@0 1196 break;
michael@0 1197 }
michael@0 1198
michael@0 1199 // When a menuitem is selected to be executed, first hide all the open
michael@0 1200 // popups, but don't remove them yet. This is needed when a menu command
michael@0 1201 // opens a modal dialog. The views associated with the popups needed to be
michael@0 1202 // hidden and the accesibility events fired before the command executes, but
michael@0 1203 // the popuphiding/popuphidden events are fired afterwards.
michael@0 1204 nsTArray<nsMenuPopupFrame *> popupsToHide;
michael@0 1205 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 1206 if (cmm != CloseMenuMode_None) {
michael@0 1207 while (item) {
michael@0 1208 // if it isn't a <menupopup>, don't close it automatically
michael@0 1209 if (!item->IsMenu())
michael@0 1210 break;
michael@0 1211 nsMenuChainItem* next = item->GetParent();
michael@0 1212 popupsToHide.AppendElement(item->Frame());
michael@0 1213 if (cmm == CloseMenuMode_Single) // only close one level of menu
michael@0 1214 break;
michael@0 1215 item = next;
michael@0 1216 }
michael@0 1217
michael@0 1218 // Now hide the popups. If the closemenu mode is auto, deselect the menu,
michael@0 1219 // otherwise only one popup is closing, so keep the parent menu selected.
michael@0 1220 HidePopupsInList(popupsToHide, cmm == CloseMenuMode_Auto);
michael@0 1221 }
michael@0 1222
michael@0 1223 aEvent->SetCloseMenuMode(cmm);
michael@0 1224 nsCOMPtr<nsIRunnable> event = aEvent;
michael@0 1225 NS_DispatchToCurrentThread(event);
michael@0 1226 }
michael@0 1227
michael@0 1228 void
michael@0 1229 nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
michael@0 1230 bool aIsContextMenu,
michael@0 1231 bool aSelectFirstItem)
michael@0 1232 {
michael@0 1233 nsCOMPtr<nsIContent> popup = aPopup; // keep a strong reference to the popup
michael@0 1234
michael@0 1235 nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
michael@0 1236 if (!popupFrame)
michael@0 1237 return;
michael@0 1238
michael@0 1239 nsPresContext *presContext = popupFrame->PresContext();
michael@0 1240 nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
michael@0 1241 nsPopupType popupType = popupFrame->PopupType();
michael@0 1242
michael@0 1243 // generate the child frames if they have not already been generated
michael@0 1244 if (!popupFrame->HasGeneratedChildren()) {
michael@0 1245 popupFrame->SetGeneratedChildren();
michael@0 1246 presShell->FrameConstructor()->GenerateChildFrames(popupFrame);
michael@0 1247 }
michael@0 1248
michael@0 1249 // get the frame again
michael@0 1250 nsIFrame* frame = aPopup->GetPrimaryFrame();
michael@0 1251 if (!frame)
michael@0 1252 return;
michael@0 1253
michael@0 1254 presShell->FrameNeedsReflow(frame, nsIPresShell::eTreeChange,
michael@0 1255 NS_FRAME_HAS_DIRTY_CHILDREN);
michael@0 1256
michael@0 1257 // cache the popup so that document.popupNode can retrieve the trigger node
michael@0 1258 // during the popupshowing event. It will be cleared below after the event
michael@0 1259 // has fired.
michael@0 1260 mOpeningPopup = aPopup;
michael@0 1261
michael@0 1262 nsEventStatus status = nsEventStatus_eIgnore;
michael@0 1263 WidgetMouseEvent event(true, NS_XUL_POPUP_SHOWING, nullptr,
michael@0 1264 WidgetMouseEvent::eReal);
michael@0 1265
michael@0 1266 // coordinates are relative to the root widget
michael@0 1267 nsPresContext* rootPresContext =
michael@0 1268 presShell->GetPresContext()->GetRootPresContext();
michael@0 1269 if (rootPresContext) {
michael@0 1270 rootPresContext->PresShell()->GetViewManager()->
michael@0 1271 GetRootWidget(getter_AddRefs(event.widget));
michael@0 1272 }
michael@0 1273 else {
michael@0 1274 event.widget = nullptr;
michael@0 1275 }
michael@0 1276
michael@0 1277 event.refPoint = LayoutDeviceIntPoint::FromUntyped(mCachedMousePoint);
michael@0 1278 event.modifiers = mCachedModifiers;
michael@0 1279 EventDispatcher::Dispatch(popup, presContext, &event, nullptr, &status);
michael@0 1280
michael@0 1281 mCachedMousePoint = nsIntPoint(0, 0);
michael@0 1282 mOpeningPopup = nullptr;
michael@0 1283
michael@0 1284 mCachedModifiers = 0;
michael@0 1285
michael@0 1286 // if a panel, blur whatever has focus so that the panel can take the focus.
michael@0 1287 // This is done after the popupshowing event in case that event is cancelled.
michael@0 1288 // Using noautofocus="true" will disable this behaviour, which is needed for
michael@0 1289 // the autocomplete widget as it manages focus itself.
michael@0 1290 if (popupType == ePopupTypePanel &&
michael@0 1291 !popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
michael@0 1292 nsGkAtoms::_true, eCaseMatters)) {
michael@0 1293 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
michael@0 1294 if (fm) {
michael@0 1295 nsIDocument* doc = popup->GetCurrentDoc();
michael@0 1296
michael@0 1297 // Only remove the focus if the currently focused item is ouside the
michael@0 1298 // popup. It isn't a big deal if the current focus is in a child popup
michael@0 1299 // inside the popup as that shouldn't be visible. This check ensures that
michael@0 1300 // a node inside the popup that is focused during a popupshowing event
michael@0 1301 // remains focused.
michael@0 1302 nsCOMPtr<nsIDOMElement> currentFocusElement;
michael@0 1303 fm->GetFocusedElement(getter_AddRefs(currentFocusElement));
michael@0 1304 nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement);
michael@0 1305 if (doc && currentFocus &&
michael@0 1306 !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) {
michael@0 1307 fm->ClearFocus(doc->GetWindow());
michael@0 1308 }
michael@0 1309 }
michael@0 1310 }
michael@0 1311
michael@0 1312 // clear these as they are no longer valid
michael@0 1313 mRangeParent = nullptr;
michael@0 1314 mRangeOffset = 0;
michael@0 1315
michael@0 1316 // get the frame again in case it went away
michael@0 1317 popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
michael@0 1318 if (popupFrame) {
michael@0 1319 // if the event was cancelled, don't open the popup, reset its state back
michael@0 1320 // to closed and clear its trigger content.
michael@0 1321 if (status == nsEventStatus_eConsumeNoDefault) {
michael@0 1322 popupFrame->SetPopupState(ePopupClosed);
michael@0 1323 popupFrame->ClearTriggerContent();
michael@0 1324 }
michael@0 1325 else {
michael@0 1326 ShowPopupCallback(aPopup, popupFrame, aIsContextMenu, aSelectFirstItem);
michael@0 1327 }
michael@0 1328 }
michael@0 1329 }
michael@0 1330
michael@0 1331 void
michael@0 1332 nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
michael@0 1333 nsIContent* aNextPopup,
michael@0 1334 nsIContent* aLastPopup,
michael@0 1335 nsPresContext *aPresContext,
michael@0 1336 nsPopupType aPopupType,
michael@0 1337 bool aDeselectMenu,
michael@0 1338 bool aIsRollup)
michael@0 1339 {
michael@0 1340 nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell();
michael@0 1341
michael@0 1342 nsEventStatus status = nsEventStatus_eIgnore;
michael@0 1343 WidgetMouseEvent event(true, NS_XUL_POPUP_HIDING, nullptr,
michael@0 1344 WidgetMouseEvent::eReal);
michael@0 1345 EventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status);
michael@0 1346
michael@0 1347 // when a panel is closed, blur whatever has focus inside the popup
michael@0 1348 if (aPopupType == ePopupTypePanel &&
michael@0 1349 !aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
michael@0 1350 nsGkAtoms::_true, eCaseMatters)) {
michael@0 1351 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
michael@0 1352 if (fm) {
michael@0 1353 nsIDocument* doc = aPopup->GetCurrentDoc();
michael@0 1354
michael@0 1355 // Remove the focus from the focused node only if it is inside the popup.
michael@0 1356 nsCOMPtr<nsIDOMElement> currentFocusElement;
michael@0 1357 fm->GetFocusedElement(getter_AddRefs(currentFocusElement));
michael@0 1358 nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement);
michael@0 1359 if (doc && currentFocus &&
michael@0 1360 nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) {
michael@0 1361 fm->ClearFocus(doc->GetWindow());
michael@0 1362 }
michael@0 1363 }
michael@0 1364 }
michael@0 1365
michael@0 1366 // get frame again in case it went away
michael@0 1367 nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
michael@0 1368 if (popupFrame) {
michael@0 1369 // if the event was cancelled, don't hide the popup, and reset its
michael@0 1370 // state back to open. Only popups in chrome shells can prevent a popup
michael@0 1371 // from hiding.
michael@0 1372 if (status == nsEventStatus_eConsumeNoDefault &&
michael@0 1373 !popupFrame->IsInContentShell()) {
michael@0 1374 popupFrame->SetPopupState(ePopupOpenAndVisible);
michael@0 1375 }
michael@0 1376 else {
michael@0 1377 // If the popup has an animate attribute and it is not set to false, assume
michael@0 1378 // that it has a closing transition and wait for it to finish. The transition
michael@0 1379 // may still occur either way, but the view will be hidden and you won't be
michael@0 1380 // able to see it. If there is a next popup, indicating that mutliple popups
michael@0 1381 // are rolling up, don't wait and hide the popup right away since the effect
michael@0 1382 // would likely be undesirable. This also does a quick check to see if the
michael@0 1383 // popup has a transition defined, and skips the wait if not. Transitions
michael@0 1384 // are currently disabled on Linux due to rendering issues on certain
michael@0 1385 // configurations.
michael@0 1386 #ifndef MOZ_WIDGET_GTK
michael@0 1387 if (!aNextPopup && aPopup->HasAttr(kNameSpaceID_None, nsGkAtoms::animate) &&
michael@0 1388 popupFrame->StyleDisplay()->mTransitionPropertyCount > 0) {
michael@0 1389 nsAutoString animate;
michael@0 1390 aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::animate, animate);
michael@0 1391
michael@0 1392 // If animate="false" then don't transition at all. If animate="cancel",
michael@0 1393 // only show the transition if cancelling the popup or rolling up.
michael@0 1394 // Otherwise, always show the transition.
michael@0 1395 if (!animate.EqualsLiteral("false") &&
michael@0 1396 (!animate.EqualsLiteral("cancel") || aIsRollup)) {
michael@0 1397 nsCOMPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aDeselectMenu);
michael@0 1398 aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
michael@0 1399 ender, false, false);
michael@0 1400 return;
michael@0 1401 }
michael@0 1402 }
michael@0 1403 #endif
michael@0 1404
michael@0 1405 HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup,
michael@0 1406 aPopupType, aDeselectMenu);
michael@0 1407 }
michael@0 1408 }
michael@0 1409 }
michael@0 1410
michael@0 1411 bool
michael@0 1412 nsXULPopupManager::IsPopupOpen(nsIContent* aPopup)
michael@0 1413 {
michael@0 1414 // a popup is open if it is in the open list. The assertions ensure that the
michael@0 1415 // frame is in the correct state. If the popup is in the hiding or invisible
michael@0 1416 // state, it will still be in the open popup list until it is closed.
michael@0 1417 nsMenuChainItem* item = mPopups;
michael@0 1418 while (item) {
michael@0 1419 if (item->Content() == aPopup) {
michael@0 1420 NS_ASSERTION(item->Frame()->IsOpen() ||
michael@0 1421 item->Frame()->PopupState() == ePopupHiding ||
michael@0 1422 item->Frame()->PopupState() == ePopupInvisible,
michael@0 1423 "popup in open list not actually open");
michael@0 1424 return true;
michael@0 1425 }
michael@0 1426 item = item->GetParent();
michael@0 1427 }
michael@0 1428
michael@0 1429 item = mNoHidePanels;
michael@0 1430 while (item) {
michael@0 1431 if (item->Content() == aPopup) {
michael@0 1432 NS_ASSERTION(item->Frame()->IsOpen() ||
michael@0 1433 item->Frame()->PopupState() == ePopupHiding ||
michael@0 1434 item->Frame()->PopupState() == ePopupInvisible,
michael@0 1435 "popup in open list not actually open");
michael@0 1436 return true;
michael@0 1437 }
michael@0 1438 item = item->GetParent();
michael@0 1439 }
michael@0 1440
michael@0 1441 return false;
michael@0 1442 }
michael@0 1443
michael@0 1444 bool
michael@0 1445 nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent)
michael@0 1446 {
michael@0 1447 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 1448 while (item) {
michael@0 1449 nsMenuPopupFrame* popup = item->Frame();
michael@0 1450 if (popup && popup->IsOpen()) {
michael@0 1451 nsMenuFrame* menuFrame = do_QueryFrame(popup->GetParent());
michael@0 1452 if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) {
michael@0 1453 return true;
michael@0 1454 }
michael@0 1455 }
michael@0 1456 item = item->GetParent();
michael@0 1457 }
michael@0 1458
michael@0 1459 return false;
michael@0 1460 }
michael@0 1461
michael@0 1462 nsIFrame*
michael@0 1463 nsXULPopupManager::GetTopPopup(nsPopupType aType)
michael@0 1464 {
michael@0 1465 if ((aType == ePopupTypePanel || aType == ePopupTypeTooltip) && mNoHidePanels)
michael@0 1466 return mNoHidePanels->Frame();
michael@0 1467
michael@0 1468 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 1469 while (item) {
michael@0 1470 if (item->PopupType() == aType || aType == ePopupTypeAny)
michael@0 1471 return item->Frame();
michael@0 1472 item = item->GetParent();
michael@0 1473 }
michael@0 1474
michael@0 1475 return nullptr;
michael@0 1476 }
michael@0 1477
michael@0 1478 void
michael@0 1479 nsXULPopupManager::GetVisiblePopups(nsTArray<nsIFrame *>& aPopups)
michael@0 1480 {
michael@0 1481 aPopups.Clear();
michael@0 1482
michael@0 1483 // Iterate over both lists of popups
michael@0 1484 nsMenuChainItem* item = mPopups;
michael@0 1485 for (int32_t list = 0; list < 2; list++) {
michael@0 1486 while (item) {
michael@0 1487 // Skip panels which are not open and visible as well as popups that
michael@0 1488 // are transparent to mouse events.
michael@0 1489 if (item->Frame()->PopupState() == ePopupOpenAndVisible &&
michael@0 1490 !item->Frame()->IsMouseTransparent()) {
michael@0 1491 aPopups.AppendElement(item->Frame());
michael@0 1492 }
michael@0 1493
michael@0 1494 item = item->GetParent();
michael@0 1495 }
michael@0 1496
michael@0 1497 item = mNoHidePanels;
michael@0 1498 }
michael@0 1499 }
michael@0 1500
michael@0 1501 already_AddRefed<nsIDOMNode>
michael@0 1502 nsXULPopupManager::GetLastTriggerNode(nsIDocument* aDocument, bool aIsTooltip)
michael@0 1503 {
michael@0 1504 if (!aDocument)
michael@0 1505 return nullptr;
michael@0 1506
michael@0 1507 nsCOMPtr<nsIDOMNode> node;
michael@0 1508
michael@0 1509 // if mOpeningPopup is set, it means that a popupshowing event is being
michael@0 1510 // fired. In this case, just use the cached node, as the popup is not yet in
michael@0 1511 // the list of open popups.
michael@0 1512 if (mOpeningPopup && mOpeningPopup->GetCurrentDoc() == aDocument &&
michael@0 1513 aIsTooltip == (mOpeningPopup->Tag() == nsGkAtoms::tooltip)) {
michael@0 1514 node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(GetPopupFrameForContent(mOpeningPopup, false)));
michael@0 1515 }
michael@0 1516 else {
michael@0 1517 nsMenuChainItem* item = aIsTooltip ? mNoHidePanels : mPopups;
michael@0 1518 while (item) {
michael@0 1519 // look for a popup of the same type and document.
michael@0 1520 if ((item->PopupType() == ePopupTypeTooltip) == aIsTooltip &&
michael@0 1521 item->Content()->GetCurrentDoc() == aDocument) {
michael@0 1522 node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(item->Frame()));
michael@0 1523 if (node)
michael@0 1524 break;
michael@0 1525 }
michael@0 1526 item = item->GetParent();
michael@0 1527 }
michael@0 1528 }
michael@0 1529
michael@0 1530 return node.forget();
michael@0 1531 }
michael@0 1532
michael@0 1533 bool
michael@0 1534 nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup)
michael@0 1535 {
michael@0 1536 // if a popup's IsOpen method returns true, then the popup must always be in
michael@0 1537 // the popup chain scanned in IsPopupOpen.
michael@0 1538 NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(aPopup->GetContent()),
michael@0 1539 "popup frame state doesn't match XULPopupManager open state");
michael@0 1540
michael@0 1541 nsPopupState state = aPopup->PopupState();
michael@0 1542
michael@0 1543 // if the popup is not in the open popup chain, then it must have a state that
michael@0 1544 // is either closed, in the process of being shown, or invisible.
michael@0 1545 NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed ||
michael@0 1546 state == ePopupShowing || state == ePopupInvisible,
michael@0 1547 "popup not in XULPopupManager open list is open");
michael@0 1548
michael@0 1549 // don't show popups unless they are closed or invisible
michael@0 1550 if (state != ePopupClosed && state != ePopupInvisible)
michael@0 1551 return false;
michael@0 1552
michael@0 1553 // Don't show popups that we already have in our popup chain
michael@0 1554 if (IsPopupOpen(aPopup->GetContent())) {
michael@0 1555 NS_WARNING("Refusing to show duplicate popup");
michael@0 1556 return false;
michael@0 1557 }
michael@0 1558
michael@0 1559 // if the popup was just rolled up, don't reopen it
michael@0 1560 nsCOMPtr<nsIWidget> widget = aPopup->GetWidget();
michael@0 1561 if (widget && widget->GetLastRollup() == aPopup->GetContent())
michael@0 1562 return false;
michael@0 1563
michael@0 1564 nsCOMPtr<nsIDocShellTreeItem> dsti = aPopup->PresContext()->GetDocShell();
michael@0 1565 nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti);
michael@0 1566 if (!baseWin)
michael@0 1567 return false;
michael@0 1568
michael@0 1569 // chrome shells can always open popups, but other types of shells can only
michael@0 1570 // open popups when they are focused and visible
michael@0 1571 if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
michael@0 1572 // only allow popups in active windows
michael@0 1573 nsCOMPtr<nsIDocShellTreeItem> root;
michael@0 1574 dsti->GetRootTreeItem(getter_AddRefs(root));
michael@0 1575 nsCOMPtr<nsIDOMWindow> rootWin = do_GetInterface(root);
michael@0 1576
michael@0 1577 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
michael@0 1578 if (!fm || !rootWin)
michael@0 1579 return false;
michael@0 1580
michael@0 1581 nsCOMPtr<nsIDOMWindow> activeWindow;
michael@0 1582 fm->GetActiveWindow(getter_AddRefs(activeWindow));
michael@0 1583 if (activeWindow != rootWin)
michael@0 1584 return false;
michael@0 1585
michael@0 1586 // only allow popups in visible frames
michael@0 1587 bool visible;
michael@0 1588 baseWin->GetVisibility(&visible);
michael@0 1589 if (!visible)
michael@0 1590 return false;
michael@0 1591 }
michael@0 1592
michael@0 1593 // platforms respond differently when an popup is opened in a minimized
michael@0 1594 // window, so this is always disabled.
michael@0 1595 nsCOMPtr<nsIWidget> mainWidget;
michael@0 1596 baseWin->GetMainWidget(getter_AddRefs(mainWidget));
michael@0 1597 if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) {
michael@0 1598 return false;
michael@0 1599 }
michael@0 1600
michael@0 1601 // cannot open a popup that is a submenu of a menupopup that isn't open.
michael@0 1602 nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent());
michael@0 1603 if (menuFrame) {
michael@0 1604 nsMenuParent* parentPopup = menuFrame->GetMenuParent();
michael@0 1605 if (parentPopup && !parentPopup->IsOpen())
michael@0 1606 return false;
michael@0 1607 }
michael@0 1608
michael@0 1609 return true;
michael@0 1610 }
michael@0 1611
michael@0 1612 void
michael@0 1613 nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup)
michael@0 1614 {
michael@0 1615 // when a popup frame is destroyed, just unhook it from the list of popups
michael@0 1616 if (mTimerMenu == aPopup) {
michael@0 1617 if (mCloseTimer) {
michael@0 1618 mCloseTimer->Cancel();
michael@0 1619 mCloseTimer = nullptr;
michael@0 1620 }
michael@0 1621 mTimerMenu = nullptr;
michael@0 1622 }
michael@0 1623
michael@0 1624 nsMenuChainItem* item = mNoHidePanels;
michael@0 1625 while (item) {
michael@0 1626 if (item->Frame() == aPopup) {
michael@0 1627 item->Detach(&mNoHidePanels);
michael@0 1628 delete item;
michael@0 1629 break;
michael@0 1630 }
michael@0 1631 item = item->GetParent();
michael@0 1632 }
michael@0 1633
michael@0 1634 nsTArray<nsMenuPopupFrame *> popupsToHide;
michael@0 1635
michael@0 1636 item = mPopups;
michael@0 1637 while (item) {
michael@0 1638 nsMenuPopupFrame* frame = item->Frame();
michael@0 1639 if (frame == aPopup) {
michael@0 1640 if (frame->PopupState() != ePopupInvisible) {
michael@0 1641 // Iterate through any child menus and hide them as well, since the
michael@0 1642 // parent is going away. We won't remove them from the list yet, just
michael@0 1643 // hide them, as they will be removed from the list when this function
michael@0 1644 // gets called for that child frame.
michael@0 1645 nsMenuChainItem* child = item->GetChild();
michael@0 1646 while (child) {
michael@0 1647 // if the popup is a child frame of the menu that was destroyed, add
michael@0 1648 // it to the list of popups to hide. Don't bother with the events
michael@0 1649 // since the frames are going away. If the child menu is not a child
michael@0 1650 // frame, for example, a context menu, use HidePopup instead, but call
michael@0 1651 // it asynchronously since we are in the middle of frame destruction.
michael@0 1652 nsMenuPopupFrame* childframe = child->Frame();
michael@0 1653 if (nsLayoutUtils::IsProperAncestorFrame(frame, childframe)) {
michael@0 1654 popupsToHide.AppendElement(childframe);
michael@0 1655 }
michael@0 1656 else {
michael@0 1657 // HidePopup will take care of hiding any of its children, so
michael@0 1658 // break out afterwards
michael@0 1659 HidePopup(child->Content(), false, false, true, false);
michael@0 1660 break;
michael@0 1661 }
michael@0 1662
michael@0 1663 child = child->GetChild();
michael@0 1664 }
michael@0 1665 }
michael@0 1666
michael@0 1667 item->Detach(&mPopups);
michael@0 1668 delete item;
michael@0 1669 break;
michael@0 1670 }
michael@0 1671
michael@0 1672 item = item->GetParent();
michael@0 1673 }
michael@0 1674
michael@0 1675 HidePopupsInList(popupsToHide, false);
michael@0 1676 }
michael@0 1677
michael@0 1678 bool
michael@0 1679 nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup)
michael@0 1680 {
michael@0 1681 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 1682 while (item && item->Frame() != aPopup) {
michael@0 1683 if (item->IsContextMenu())
michael@0 1684 return true;
michael@0 1685 item = item->GetParent();
michael@0 1686 }
michael@0 1687
michael@0 1688 return false;
michael@0 1689 }
michael@0 1690
michael@0 1691 void
michael@0 1692 nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup)
michael@0 1693 {
michael@0 1694 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 1695 if (item && aOldPopup == item->Content())
michael@0 1696 return;
michael@0 1697
michael@0 1698 if (mWidget) {
michael@0 1699 mWidget->CaptureRollupEvents(nullptr, false);
michael@0 1700 mWidget = nullptr;
michael@0 1701 }
michael@0 1702
michael@0 1703 if (item) {
michael@0 1704 nsMenuPopupFrame* popup = item->Frame();
michael@0 1705 mWidget = popup->GetWidget();
michael@0 1706 if (mWidget) {
michael@0 1707 mWidget->CaptureRollupEvents(nullptr, true);
michael@0 1708 popup->AttachedDismissalListener();
michael@0 1709 }
michael@0 1710 }
michael@0 1711
michael@0 1712 UpdateKeyboardListeners();
michael@0 1713 }
michael@0 1714
michael@0 1715 void
michael@0 1716 nsXULPopupManager::UpdateKeyboardListeners()
michael@0 1717 {
michael@0 1718 nsCOMPtr<EventTarget> newTarget;
michael@0 1719 bool isForMenu = false;
michael@0 1720 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 1721 if (item) {
michael@0 1722 if (!item->IgnoreKeys())
michael@0 1723 newTarget = item->Content()->GetDocument();
michael@0 1724 isForMenu = item->PopupType() == ePopupTypeMenu;
michael@0 1725 }
michael@0 1726 else if (mActiveMenuBar) {
michael@0 1727 newTarget = mActiveMenuBar->GetContent()->GetDocument();
michael@0 1728 isForMenu = true;
michael@0 1729 }
michael@0 1730
michael@0 1731 if (mKeyListener != newTarget) {
michael@0 1732 if (mKeyListener) {
michael@0 1733 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
michael@0 1734 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
michael@0 1735 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
michael@0 1736 mKeyListener = nullptr;
michael@0 1737 nsContentUtils::NotifyInstalledMenuKeyboardListener(false);
michael@0 1738 }
michael@0 1739
michael@0 1740 if (newTarget) {
michael@0 1741 newTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, true);
michael@0 1742 newTarget->AddEventListener(NS_LITERAL_STRING("keydown"), this, true);
michael@0 1743 newTarget->AddEventListener(NS_LITERAL_STRING("keyup"), this, true);
michael@0 1744 nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu);
michael@0 1745 mKeyListener = newTarget;
michael@0 1746 }
michael@0 1747 }
michael@0 1748 }
michael@0 1749
michael@0 1750 void
michael@0 1751 nsXULPopupManager::UpdateMenuItems(nsIContent* aPopup)
michael@0 1752 {
michael@0 1753 // Walk all of the menu's children, checking to see if any of them has a
michael@0 1754 // command attribute. If so, then several attributes must potentially be updated.
michael@0 1755
michael@0 1756 nsCOMPtr<nsIDocument> document = aPopup->GetCurrentDoc();
michael@0 1757 if (!document) {
michael@0 1758 return;
michael@0 1759 }
michael@0 1760
michael@0 1761 for (nsCOMPtr<nsIContent> grandChild = aPopup->GetFirstChild();
michael@0 1762 grandChild;
michael@0 1763 grandChild = grandChild->GetNextSibling()) {
michael@0 1764 if (grandChild->NodeInfo()->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL)) {
michael@0 1765 // See if we have a command attribute.
michael@0 1766 nsAutoString command;
michael@0 1767 grandChild->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
michael@0 1768 if (!command.IsEmpty()) {
michael@0 1769 // We do! Look it up in our document
michael@0 1770 nsRefPtr<dom::Element> commandElement =
michael@0 1771 document->GetElementById(command);
michael@0 1772 if (commandElement) {
michael@0 1773 nsAutoString commandValue;
michael@0 1774 // The menu's disabled state needs to be updated to match the command.
michael@0 1775 if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue))
michael@0 1776 grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue, true);
michael@0 1777 else
michael@0 1778 grandChild->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
michael@0 1779
michael@0 1780 // The menu's label, accesskey checked and hidden states need to be updated
michael@0 1781 // to match the command. Note that unlike the disabled state if the
michael@0 1782 // command has *no* value, we assume the menu is supplying its own.
michael@0 1783 if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue))
michael@0 1784 grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue, true);
michael@0 1785
michael@0 1786 if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue))
michael@0 1787 grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue, true);
michael@0 1788
michael@0 1789 if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue))
michael@0 1790 grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue, true);
michael@0 1791
michael@0 1792 if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue))
michael@0 1793 grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue, true);
michael@0 1794 }
michael@0 1795 }
michael@0 1796 }
michael@0 1797 }
michael@0 1798 }
michael@0 1799
michael@0 1800 // Notify
michael@0 1801 //
michael@0 1802 // The item selection timer has fired, we might have to readjust the
michael@0 1803 // selected item. There are two cases here that we are trying to deal with:
michael@0 1804 // (1) diagonal movement from a parent menu to a submenu passing briefly over
michael@0 1805 // other items, and
michael@0 1806 // (2) moving out from a submenu to a parent or grandparent menu.
michael@0 1807 // In both cases, |mTimerMenu| is the menu item that might have an open submenu and
michael@0 1808 // the first item in |mPopups| is the item the mouse is currently over, which could be
michael@0 1809 // none of them.
michael@0 1810 //
michael@0 1811 // case (1):
michael@0 1812 // As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the
michael@0 1813 // submenu, it probably passes through one or more sibilings (B). As the mouse passes
michael@0 1814 // through B, it becomes the current menu item and the timer is set and mTimerMenu is
michael@0 1815 // set to A. Before the timer fires, the mouse leaves the menu containing A and B and
michael@0 1816 // enters the submenus. Now when the timer fires, |mPopups| is null (!= |mTimerMenu|)
michael@0 1817 // so we have to see if anything in A's children is selected (recall that even disabled
michael@0 1818 // items are selected, the style just doesn't show it). If that is the case, we need to
michael@0 1819 // set the selected item back to A.
michael@0 1820 //
michael@0 1821 // case (2);
michael@0 1822 // Item A has an open submenu, and in it there is an item (B) which also has an open
michael@0 1823 // submenu (so there are 3 menus displayed right now). The mouse then leaves B's child
michael@0 1824 // submenu and selects an item that is a sibling of A, call it C. When the mouse enters C,
michael@0 1825 // the timer is set and |mTimerMenu| is A and |mPopups| is C. As the timer fires,
michael@0 1826 // the mouse is still within C. The correct behavior is to set the current item to C
michael@0 1827 // and close up the chain parented at A.
michael@0 1828 //
michael@0 1829 // This brings up the question of is the logic of case (1) enough? The answer is no,
michael@0 1830 // and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected
michael@0 1831 // child, and if it does, set the selected item to A. Because B has a submenu open, it
michael@0 1832 // is selected and as a result, A is set to be the selected item even though the mouse
michael@0 1833 // rests in C -- very wrong.
michael@0 1834 //
michael@0 1835 // The solution is to use the same idea, but instead of only checking one level,
michael@0 1836 // drill all the way down to the deepest open submenu and check if it has something
michael@0 1837 // selected. Since the mouse is in a grandparent, it won't, and we know that we can
michael@0 1838 // safely close up A and all its children.
michael@0 1839 //
michael@0 1840 // The code below melds the two cases together.
michael@0 1841 //
michael@0 1842 nsresult
michael@0 1843 nsXULPopupManager::Notify(nsITimer* aTimer)
michael@0 1844 {
michael@0 1845 if (aTimer == mCloseTimer)
michael@0 1846 KillMenuTimer();
michael@0 1847
michael@0 1848 return NS_OK;
michael@0 1849 }
michael@0 1850
michael@0 1851 void
michael@0 1852 nsXULPopupManager::KillMenuTimer()
michael@0 1853 {
michael@0 1854 if (mCloseTimer && mTimerMenu) {
michael@0 1855 mCloseTimer->Cancel();
michael@0 1856 mCloseTimer = nullptr;
michael@0 1857
michael@0 1858 if (mTimerMenu->IsOpen())
michael@0 1859 HidePopup(mTimerMenu->GetContent(), false, false, true, false);
michael@0 1860 }
michael@0 1861
michael@0 1862 mTimerMenu = nullptr;
michael@0 1863 }
michael@0 1864
michael@0 1865 void
michael@0 1866 nsXULPopupManager::CancelMenuTimer(nsMenuParent* aMenuParent)
michael@0 1867 {
michael@0 1868 if (mCloseTimer && mTimerMenu == aMenuParent) {
michael@0 1869 mCloseTimer->Cancel();
michael@0 1870 mCloseTimer = nullptr;
michael@0 1871 mTimerMenu = nullptr;
michael@0 1872 }
michael@0 1873 }
michael@0 1874
michael@0 1875 bool
michael@0 1876 nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent,
michael@0 1877 nsMenuPopupFrame* aFrame)
michael@0 1878 {
michael@0 1879 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 1880 if (!aFrame && item)
michael@0 1881 aFrame = item->Frame();
michael@0 1882
michael@0 1883 if (aFrame) {
michael@0 1884 bool action;
michael@0 1885 nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action);
michael@0 1886 if (result) {
michael@0 1887 aFrame->ChangeMenuItem(result, false);
michael@0 1888 if (action) {
michael@0 1889 WidgetGUIEvent* evt = aKeyEvent->GetInternalNSEvent()->AsGUIEvent();
michael@0 1890 nsMenuFrame* menuToOpen = result->Enter(evt);
michael@0 1891 if (menuToOpen) {
michael@0 1892 nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
michael@0 1893 ShowMenu(content, true, false);
michael@0 1894 }
michael@0 1895 }
michael@0 1896 return true;
michael@0 1897 }
michael@0 1898
michael@0 1899 return false;
michael@0 1900 }
michael@0 1901
michael@0 1902 if (mActiveMenuBar) {
michael@0 1903 nsMenuFrame* result = mActiveMenuBar->FindMenuWithShortcut(aKeyEvent);
michael@0 1904 if (result) {
michael@0 1905 mActiveMenuBar->SetActive(true);
michael@0 1906 result->OpenMenu(true);
michael@0 1907 return true;
michael@0 1908 }
michael@0 1909 }
michael@0 1910
michael@0 1911 return false;
michael@0 1912 }
michael@0 1913
michael@0 1914
michael@0 1915 bool
michael@0 1916 nsXULPopupManager::HandleKeyboardNavigation(uint32_t aKeyCode)
michael@0 1917 {
michael@0 1918 // navigate up through the open menus, looking for the topmost one
michael@0 1919 // in the same hierarchy
michael@0 1920 nsMenuChainItem* item = nullptr;
michael@0 1921 nsMenuChainItem* nextitem = GetTopVisibleMenu();
michael@0 1922
michael@0 1923 while (nextitem) {
michael@0 1924 item = nextitem;
michael@0 1925 nextitem = item->GetParent();
michael@0 1926
michael@0 1927 if (nextitem) {
michael@0 1928 // stop if the parent isn't a menu
michael@0 1929 if (!nextitem->IsMenu())
michael@0 1930 break;
michael@0 1931
michael@0 1932 // check to make sure that the parent is actually the parent menu. It won't
michael@0 1933 // be if the parent is in a different frame hierarchy, for example, for a
michael@0 1934 // context menu opened on another menu.
michael@0 1935 nsMenuParent* expectedParent = static_cast<nsMenuParent *>(nextitem->Frame());
michael@0 1936 nsMenuFrame* menuFrame = do_QueryFrame(item->Frame()->GetParent());
michael@0 1937 if (!menuFrame || menuFrame->GetMenuParent() != expectedParent) {
michael@0 1938 break;
michael@0 1939 }
michael@0 1940 }
michael@0 1941 }
michael@0 1942
michael@0 1943 nsIFrame* itemFrame;
michael@0 1944 if (item)
michael@0 1945 itemFrame = item->Frame();
michael@0 1946 else if (mActiveMenuBar)
michael@0 1947 itemFrame = mActiveMenuBar;
michael@0 1948 else
michael@0 1949 return false;
michael@0 1950
michael@0 1951 nsNavigationDirection theDirection;
michael@0 1952 NS_ASSERTION(aKeyCode >= nsIDOMKeyEvent::DOM_VK_END &&
michael@0 1953 aKeyCode <= nsIDOMKeyEvent::DOM_VK_DOWN, "Illegal key code");
michael@0 1954 theDirection = NS_DIRECTION_FROM_KEY_CODE(itemFrame, aKeyCode);
michael@0 1955
michael@0 1956 // if a popup is open, first check for navigation within the popup
michael@0 1957 if (item && HandleKeyboardNavigationInPopup(item, theDirection))
michael@0 1958 return true;
michael@0 1959
michael@0 1960 // no popup handled the key, so check the active menubar, if any
michael@0 1961 if (mActiveMenuBar) {
michael@0 1962 nsMenuFrame* currentMenu = mActiveMenuBar->GetCurrentMenuItem();
michael@0 1963
michael@0 1964 if (NS_DIRECTION_IS_INLINE(theDirection)) {
michael@0 1965 nsMenuFrame* nextItem = (theDirection == eNavigationDirection_End) ?
michael@0 1966 GetNextMenuItem(mActiveMenuBar, currentMenu, false) :
michael@0 1967 GetPreviousMenuItem(mActiveMenuBar, currentMenu, false);
michael@0 1968 mActiveMenuBar->ChangeMenuItem(nextItem, true);
michael@0 1969 return true;
michael@0 1970 }
michael@0 1971 else if (NS_DIRECTION_IS_BLOCK(theDirection)) {
michael@0 1972 // Open the menu and select its first item.
michael@0 1973 if (currentMenu) {
michael@0 1974 nsCOMPtr<nsIContent> content = currentMenu->GetContent();
michael@0 1975 ShowMenu(content, true, false);
michael@0 1976 }
michael@0 1977 return true;
michael@0 1978 }
michael@0 1979 }
michael@0 1980
michael@0 1981 return false;
michael@0 1982 }
michael@0 1983
michael@0 1984 bool
michael@0 1985 nsXULPopupManager::HandleKeyboardNavigationInPopup(nsMenuChainItem* item,
michael@0 1986 nsMenuPopupFrame* aFrame,
michael@0 1987 nsNavigationDirection aDir)
michael@0 1988 {
michael@0 1989 NS_ASSERTION(aFrame, "aFrame is null");
michael@0 1990 NS_ASSERTION(!item || item->Frame() == aFrame,
michael@0 1991 "aFrame is expected to be equal to item->Frame()");
michael@0 1992
michael@0 1993 nsMenuFrame* currentMenu = aFrame->GetCurrentMenuItem();
michael@0 1994
michael@0 1995 aFrame->ClearIncrementalString();
michael@0 1996
michael@0 1997 // This method only gets called if we're open.
michael@0 1998 if (!currentMenu && NS_DIRECTION_IS_INLINE(aDir)) {
michael@0 1999 // We've been opened, but we haven't had anything selected.
michael@0 2000 // We can handle End, but our parent handles Start.
michael@0 2001 if (aDir == eNavigationDirection_End) {
michael@0 2002 nsMenuFrame* nextItem = GetNextMenuItem(aFrame, nullptr, true);
michael@0 2003 if (nextItem) {
michael@0 2004 aFrame->ChangeMenuItem(nextItem, false);
michael@0 2005 return true;
michael@0 2006 }
michael@0 2007 }
michael@0 2008 return false;
michael@0 2009 }
michael@0 2010
michael@0 2011 bool isContainer = false;
michael@0 2012 bool isOpen = false;
michael@0 2013 if (currentMenu) {
michael@0 2014 isOpen = currentMenu->IsOpen();
michael@0 2015 isContainer = currentMenu->IsMenu();
michael@0 2016 if (isOpen) {
michael@0 2017 // for an open popup, have the child process the event
michael@0 2018 nsMenuChainItem* child = item ? item->GetChild() : nullptr;
michael@0 2019 if (child && HandleKeyboardNavigationInPopup(child, aDir))
michael@0 2020 return true;
michael@0 2021 }
michael@0 2022 else if (aDir == eNavigationDirection_End &&
michael@0 2023 isContainer && !currentMenu->IsDisabled()) {
michael@0 2024 // The menu is not yet open. Open it and select the first item.
michael@0 2025 nsCOMPtr<nsIContent> content = currentMenu->GetContent();
michael@0 2026 ShowMenu(content, true, false);
michael@0 2027 return true;
michael@0 2028 }
michael@0 2029 }
michael@0 2030
michael@0 2031 // For block progression, we can move in either direction
michael@0 2032 if (NS_DIRECTION_IS_BLOCK(aDir) ||
michael@0 2033 NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir)) {
michael@0 2034 nsMenuFrame* nextItem;
michael@0 2035
michael@0 2036 if (aDir == eNavigationDirection_Before)
michael@0 2037 nextItem = GetPreviousMenuItem(aFrame, currentMenu, true);
michael@0 2038 else if (aDir == eNavigationDirection_After)
michael@0 2039 nextItem = GetNextMenuItem(aFrame, currentMenu, true);
michael@0 2040 else if (aDir == eNavigationDirection_First)
michael@0 2041 nextItem = GetNextMenuItem(aFrame, nullptr, true);
michael@0 2042 else
michael@0 2043 nextItem = GetPreviousMenuItem(aFrame, nullptr, true);
michael@0 2044
michael@0 2045 if (nextItem) {
michael@0 2046 aFrame->ChangeMenuItem(nextItem, false);
michael@0 2047 return true;
michael@0 2048 }
michael@0 2049 }
michael@0 2050 else if (currentMenu && isContainer && isOpen) {
michael@0 2051 if (aDir == eNavigationDirection_Start) {
michael@0 2052 // close a submenu when Left is pressed
michael@0 2053 nsMenuPopupFrame* popupFrame = currentMenu->GetPopup();
michael@0 2054 if (popupFrame)
michael@0 2055 HidePopup(popupFrame->GetContent(), false, false, false, false);
michael@0 2056 return true;
michael@0 2057 }
michael@0 2058 }
michael@0 2059
michael@0 2060 return false;
michael@0 2061 }
michael@0 2062
michael@0 2063 bool
michael@0 2064 nsXULPopupManager::HandleKeyboardEventWithKeyCode(
michael@0 2065 nsIDOMKeyEvent* aKeyEvent,
michael@0 2066 nsMenuChainItem* aTopVisibleMenuItem)
michael@0 2067 {
michael@0 2068 uint32_t keyCode;
michael@0 2069 aKeyEvent->GetKeyCode(&keyCode);
michael@0 2070
michael@0 2071 // Escape should close panels, but the other keys should have no effect.
michael@0 2072 if (aTopVisibleMenuItem &&
michael@0 2073 aTopVisibleMenuItem->PopupType() != ePopupTypeMenu) {
michael@0 2074 if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE) {
michael@0 2075 HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
michael@0 2076 aKeyEvent->StopPropagation();
michael@0 2077 aKeyEvent->PreventDefault();
michael@0 2078 }
michael@0 2079 return true;
michael@0 2080 }
michael@0 2081
michael@0 2082 bool consume = (mPopups || mActiveMenuBar);
michael@0 2083 switch (keyCode) {
michael@0 2084 case nsIDOMKeyEvent::DOM_VK_LEFT:
michael@0 2085 case nsIDOMKeyEvent::DOM_VK_RIGHT:
michael@0 2086 case nsIDOMKeyEvent::DOM_VK_UP:
michael@0 2087 case nsIDOMKeyEvent::DOM_VK_DOWN:
michael@0 2088 case nsIDOMKeyEvent::DOM_VK_HOME:
michael@0 2089 case nsIDOMKeyEvent::DOM_VK_END:
michael@0 2090 HandleKeyboardNavigation(keyCode);
michael@0 2091 break;
michael@0 2092
michael@0 2093 case nsIDOMKeyEvent::DOM_VK_ESCAPE:
michael@0 2094 // Pressing Escape hides one level of menus only. If no menu is open,
michael@0 2095 // check if a menubar is active and inform it that a menu closed. Even
michael@0 2096 // though in this latter case, a menu didn't actually close, the effect
michael@0 2097 // ends up being the same. Similar for the tab key below.
michael@0 2098 if (aTopVisibleMenuItem) {
michael@0 2099 HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
michael@0 2100 } else if (mActiveMenuBar) {
michael@0 2101 mActiveMenuBar->MenuClosed();
michael@0 2102 }
michael@0 2103 break;
michael@0 2104
michael@0 2105 case nsIDOMKeyEvent::DOM_VK_TAB:
michael@0 2106 #ifndef XP_MACOSX
michael@0 2107 case nsIDOMKeyEvent::DOM_VK_F10:
michael@0 2108 #endif
michael@0 2109 // close popups or deactivate menubar when Tab or F10 are pressed
michael@0 2110 if (aTopVisibleMenuItem) {
michael@0 2111 Rollup(0, nullptr, nullptr);
michael@0 2112 } else if (mActiveMenuBar) {
michael@0 2113 mActiveMenuBar->MenuClosed();
michael@0 2114 }
michael@0 2115 break;
michael@0 2116
michael@0 2117 case nsIDOMKeyEvent::DOM_VK_RETURN: {
michael@0 2118 // If there is a popup open, check if the current item needs to be opened.
michael@0 2119 // Otherwise, tell the active menubar, if any, to activate the menu. The
michael@0 2120 // Enter method will return a menu if one needs to be opened as a result.
michael@0 2121 nsMenuFrame* menuToOpen = nullptr;
michael@0 2122 WidgetGUIEvent* GUIEvent = aKeyEvent->GetInternalNSEvent()->AsGUIEvent();
michael@0 2123 if (aTopVisibleMenuItem) {
michael@0 2124 menuToOpen = aTopVisibleMenuItem->Frame()->Enter(GUIEvent);
michael@0 2125 } else if (mActiveMenuBar) {
michael@0 2126 menuToOpen = mActiveMenuBar->Enter(GUIEvent);
michael@0 2127 }
michael@0 2128 if (menuToOpen) {
michael@0 2129 nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
michael@0 2130 ShowMenu(content, true, false);
michael@0 2131 }
michael@0 2132 break;
michael@0 2133 }
michael@0 2134
michael@0 2135 default:
michael@0 2136 return false;
michael@0 2137 }
michael@0 2138
michael@0 2139 if (consume) {
michael@0 2140 aKeyEvent->StopPropagation();
michael@0 2141 aKeyEvent->PreventDefault();
michael@0 2142 }
michael@0 2143 return true;
michael@0 2144 }
michael@0 2145
michael@0 2146 nsMenuFrame*
michael@0 2147 nsXULPopupManager::GetNextMenuItem(nsIFrame* aParent,
michael@0 2148 nsMenuFrame* aStart,
michael@0 2149 bool aIsPopup)
michael@0 2150 {
michael@0 2151 nsPresContext* presContext = aParent->PresContext();
michael@0 2152 nsIFrame* immediateParent = presContext->PresShell()->
michael@0 2153 FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr);
michael@0 2154 if (!immediateParent)
michael@0 2155 immediateParent = aParent;
michael@0 2156
michael@0 2157 nsIFrame* currFrame = nullptr;
michael@0 2158 if (aStart)
michael@0 2159 currFrame = aStart->GetNextSibling();
michael@0 2160 else
michael@0 2161 currFrame = immediateParent->GetFirstPrincipalChild();
michael@0 2162
michael@0 2163 while (currFrame) {
michael@0 2164 // See if it's a menu item.
michael@0 2165 if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
michael@0 2166 return do_QueryFrame(currFrame);
michael@0 2167 }
michael@0 2168 currFrame = currFrame->GetNextSibling();
michael@0 2169 }
michael@0 2170
michael@0 2171 currFrame = immediateParent->GetFirstPrincipalChild();
michael@0 2172
michael@0 2173 // Still don't have anything. Try cycling from the beginning.
michael@0 2174 while (currFrame && currFrame != aStart) {
michael@0 2175 // See if it's a menu item.
michael@0 2176 if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
michael@0 2177 return do_QueryFrame(currFrame);
michael@0 2178 }
michael@0 2179
michael@0 2180 currFrame = currFrame->GetNextSibling();
michael@0 2181 }
michael@0 2182
michael@0 2183 // No luck. Just return our start value.
michael@0 2184 return aStart;
michael@0 2185 }
michael@0 2186
michael@0 2187 nsMenuFrame*
michael@0 2188 nsXULPopupManager::GetPreviousMenuItem(nsIFrame* aParent,
michael@0 2189 nsMenuFrame* aStart,
michael@0 2190 bool aIsPopup)
michael@0 2191 {
michael@0 2192 nsPresContext* presContext = aParent->PresContext();
michael@0 2193 nsIFrame* immediateParent = presContext->PresShell()->
michael@0 2194 FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr);
michael@0 2195 if (!immediateParent)
michael@0 2196 immediateParent = aParent;
michael@0 2197
michael@0 2198 const nsFrameList& frames(immediateParent->PrincipalChildList());
michael@0 2199
michael@0 2200 nsIFrame* currFrame = nullptr;
michael@0 2201 if (aStart)
michael@0 2202 currFrame = aStart->GetPrevSibling();
michael@0 2203 else
michael@0 2204 currFrame = frames.LastChild();
michael@0 2205
michael@0 2206 while (currFrame) {
michael@0 2207 // See if it's a menu item.
michael@0 2208 if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
michael@0 2209 return do_QueryFrame(currFrame);
michael@0 2210 }
michael@0 2211 currFrame = currFrame->GetPrevSibling();
michael@0 2212 }
michael@0 2213
michael@0 2214 currFrame = frames.LastChild();
michael@0 2215
michael@0 2216 // Still don't have anything. Try cycling from the end.
michael@0 2217 while (currFrame && currFrame != aStart) {
michael@0 2218 // See if it's a menu item.
michael@0 2219 if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) {
michael@0 2220 return do_QueryFrame(currFrame);
michael@0 2221 }
michael@0 2222
michael@0 2223 currFrame = currFrame->GetPrevSibling();
michael@0 2224 }
michael@0 2225
michael@0 2226 // No luck. Just return our start value.
michael@0 2227 return aStart;
michael@0 2228 }
michael@0 2229
michael@0 2230 bool
michael@0 2231 nsXULPopupManager::IsValidMenuItem(nsPresContext* aPresContext,
michael@0 2232 nsIContent* aContent,
michael@0 2233 bool aOnPopup)
michael@0 2234 {
michael@0 2235 int32_t ns = aContent->GetNameSpaceID();
michael@0 2236 nsIAtom *tag = aContent->Tag();
michael@0 2237 if (ns == kNameSpaceID_XUL) {
michael@0 2238 if (tag != nsGkAtoms::menu && tag != nsGkAtoms::menuitem)
michael@0 2239 return false;
michael@0 2240 }
michael@0 2241 else if (ns != kNameSpaceID_XHTML || !aOnPopup || tag != nsGkAtoms::option) {
michael@0 2242 return false;
michael@0 2243 }
michael@0 2244
michael@0 2245 bool skipNavigatingDisabledMenuItem = true;
michael@0 2246 if (aOnPopup) {
michael@0 2247 skipNavigatingDisabledMenuItem =
michael@0 2248 LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem,
michael@0 2249 0) != 0;
michael@0 2250 }
michael@0 2251
michael@0 2252 return !(skipNavigatingDisabledMenuItem &&
michael@0 2253 aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
michael@0 2254 nsGkAtoms::_true, eCaseMatters));
michael@0 2255 }
michael@0 2256
michael@0 2257 nsresult
michael@0 2258 nsXULPopupManager::HandleEvent(nsIDOMEvent* aEvent)
michael@0 2259 {
michael@0 2260 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
michael@0 2261 NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
michael@0 2262
michael@0 2263 //handlers shouldn't be triggered by non-trusted events.
michael@0 2264 bool trustedEvent = false;
michael@0 2265 aEvent->GetIsTrusted(&trustedEvent);
michael@0 2266 if (!trustedEvent) {
michael@0 2267 return NS_OK;
michael@0 2268 }
michael@0 2269
michael@0 2270 nsAutoString eventType;
michael@0 2271 keyEvent->GetType(eventType);
michael@0 2272 if (eventType.EqualsLiteral("keyup")) {
michael@0 2273 return KeyUp(keyEvent);
michael@0 2274 }
michael@0 2275 if (eventType.EqualsLiteral("keydown")) {
michael@0 2276 return KeyDown(keyEvent);
michael@0 2277 }
michael@0 2278 if (eventType.EqualsLiteral("keypress")) {
michael@0 2279 return KeyPress(keyEvent);
michael@0 2280 }
michael@0 2281
michael@0 2282 NS_ABORT();
michael@0 2283
michael@0 2284 return NS_OK;
michael@0 2285 }
michael@0 2286
michael@0 2287 nsresult
michael@0 2288 nsXULPopupManager::KeyUp(nsIDOMKeyEvent* aKeyEvent)
michael@0 2289 {
michael@0 2290 // don't do anything if a menu isn't open or a menubar isn't active
michael@0 2291 if (!mActiveMenuBar) {
michael@0 2292 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 2293 if (!item || item->PopupType() != ePopupTypeMenu)
michael@0 2294 return NS_OK;
michael@0 2295 }
michael@0 2296
michael@0 2297 aKeyEvent->StopPropagation();
michael@0 2298 aKeyEvent->PreventDefault();
michael@0 2299
michael@0 2300 return NS_OK; // I am consuming event
michael@0 2301 }
michael@0 2302
michael@0 2303 nsresult
michael@0 2304 nsXULPopupManager::KeyDown(nsIDOMKeyEvent* aKeyEvent)
michael@0 2305 {
michael@0 2306 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 2307 if (item && item->Frame()->IsMenuLocked())
michael@0 2308 return NS_OK;
michael@0 2309
michael@0 2310 if (HandleKeyboardEventWithKeyCode(aKeyEvent, item)) {
michael@0 2311 return NS_OK;
michael@0 2312 }
michael@0 2313
michael@0 2314 // don't do anything if a menu isn't open or a menubar isn't active
michael@0 2315 if (!mActiveMenuBar && (!item || item->PopupType() != ePopupTypeMenu))
michael@0 2316 return NS_OK;
michael@0 2317
michael@0 2318 int32_t menuAccessKey = -1;
michael@0 2319
michael@0 2320 // If the key just pressed is the access key (usually Alt),
michael@0 2321 // dismiss and unfocus the menu.
michael@0 2322
michael@0 2323 nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
michael@0 2324 if (menuAccessKey) {
michael@0 2325 uint32_t theChar;
michael@0 2326 aKeyEvent->GetKeyCode(&theChar);
michael@0 2327
michael@0 2328 if (theChar == (uint32_t)menuAccessKey) {
michael@0 2329 bool ctrl = false;
michael@0 2330 if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_CONTROL)
michael@0 2331 aKeyEvent->GetCtrlKey(&ctrl);
michael@0 2332 bool alt=false;
michael@0 2333 if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_ALT)
michael@0 2334 aKeyEvent->GetAltKey(&alt);
michael@0 2335 bool shift=false;
michael@0 2336 if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_SHIFT)
michael@0 2337 aKeyEvent->GetShiftKey(&shift);
michael@0 2338 bool meta=false;
michael@0 2339 if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_META)
michael@0 2340 aKeyEvent->GetMetaKey(&meta);
michael@0 2341 if (!(ctrl || alt || shift || meta)) {
michael@0 2342 // The access key just went down and no other
michael@0 2343 // modifiers are already down.
michael@0 2344 if (mPopups)
michael@0 2345 Rollup(0, nullptr, nullptr);
michael@0 2346 else if (mActiveMenuBar)
michael@0 2347 mActiveMenuBar->MenuClosed();
michael@0 2348 }
michael@0 2349 aKeyEvent->PreventDefault();
michael@0 2350 }
michael@0 2351 }
michael@0 2352
michael@0 2353 // Since a menu was open, stop propagation of the event to keep other event
michael@0 2354 // listeners from becoming confused.
michael@0 2355 aKeyEvent->StopPropagation();
michael@0 2356 return NS_OK;
michael@0 2357 }
michael@0 2358
michael@0 2359 nsresult
michael@0 2360 nsXULPopupManager::KeyPress(nsIDOMKeyEvent* aKeyEvent)
michael@0 2361 {
michael@0 2362 // Don't check prevent default flag -- menus always get first shot at key events.
michael@0 2363
michael@0 2364 nsMenuChainItem* item = GetTopVisibleMenu();
michael@0 2365 if (item &&
michael@0 2366 (item->Frame()->IsMenuLocked() || item->PopupType() != ePopupTypeMenu)) {
michael@0 2367 return NS_OK;
michael@0 2368 }
michael@0 2369
michael@0 2370 nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
michael@0 2371 NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
michael@0 2372 // if a menu is open or a menubar is active, it consumes the key event
michael@0 2373 bool consume = (mPopups || mActiveMenuBar);
michael@0 2374 HandleShortcutNavigation(keyEvent, nullptr);
michael@0 2375 if (consume) {
michael@0 2376 aKeyEvent->StopPropagation();
michael@0 2377 aKeyEvent->PreventDefault();
michael@0 2378 }
michael@0 2379
michael@0 2380 return NS_OK; // I am consuming event
michael@0 2381 }
michael@0 2382
michael@0 2383 NS_IMETHODIMP
michael@0 2384 nsXULPopupShowingEvent::Run()
michael@0 2385 {
michael@0 2386 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 2387 if (pm) {
michael@0 2388 pm->FirePopupShowingEvent(mPopup, mIsContextMenu, mSelectFirstItem);
michael@0 2389 }
michael@0 2390
michael@0 2391 return NS_OK;
michael@0 2392 }
michael@0 2393
michael@0 2394 NS_IMETHODIMP
michael@0 2395 nsXULPopupHidingEvent::Run()
michael@0 2396 {
michael@0 2397 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 2398
michael@0 2399 nsIDocument *document = mPopup->GetCurrentDoc();
michael@0 2400 if (pm && document) {
michael@0 2401 nsIPresShell* presShell = document->GetShell();
michael@0 2402 if (presShell) {
michael@0 2403 nsPresContext* context = presShell->GetPresContext();
michael@0 2404 if (context) {
michael@0 2405 pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup,
michael@0 2406 context, mPopupType, mDeselectMenu, mIsRollup);
michael@0 2407 }
michael@0 2408 }
michael@0 2409 }
michael@0 2410
michael@0 2411 return NS_OK;
michael@0 2412 }
michael@0 2413
michael@0 2414 NS_IMETHODIMP
michael@0 2415 nsXULMenuCommandEvent::Run()
michael@0 2416 {
michael@0 2417 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
michael@0 2418 if (!pm)
michael@0 2419 return NS_OK;
michael@0 2420
michael@0 2421 // The order of the nsViewManager and nsIPresShell COM pointers is
michael@0 2422 // important below. We want the pres shell to get released before the
michael@0 2423 // associated view manager on exit from this function.
michael@0 2424 // See bug 54233.
michael@0 2425 // XXXndeakin is this still needed?
michael@0 2426
michael@0 2427 nsCOMPtr<nsIContent> popup;
michael@0 2428 nsMenuFrame* menuFrame = do_QueryFrame(mMenu->GetPrimaryFrame());
michael@0 2429 nsWeakFrame weakFrame(menuFrame);
michael@0 2430 if (menuFrame && mFlipChecked) {
michael@0 2431 if (menuFrame->IsChecked()) {
michael@0 2432 mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true);
michael@0 2433 } else {
michael@0 2434 mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
michael@0 2435 NS_LITERAL_STRING("true"), true);
michael@0 2436 }
michael@0 2437 }
michael@0 2438
michael@0 2439 if (menuFrame && weakFrame.IsAlive()) {
michael@0 2440 // Find the popup that the menu is inside. Below, this popup will
michael@0 2441 // need to be hidden.
michael@0 2442 nsIFrame* frame = menuFrame->GetParent();
michael@0 2443 while (frame) {
michael@0 2444 nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
michael@0 2445 if (popupFrame) {
michael@0 2446 popup = popupFrame->GetContent();
michael@0 2447 break;
michael@0 2448 }
michael@0 2449 frame = frame->GetParent();
michael@0 2450 }
michael@0 2451
michael@0 2452 nsPresContext* presContext = menuFrame->PresContext();
michael@0 2453 nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
michael@0 2454 nsRefPtr<nsViewManager> kungFuDeathGrip = shell->GetViewManager();
michael@0 2455
michael@0 2456 // Deselect ourselves.
michael@0 2457 if (mCloseMenuMode != CloseMenuMode_None)
michael@0 2458 menuFrame->SelectMenu(false);
michael@0 2459
michael@0 2460 AutoHandlingUserInputStatePusher userInpStatePusher(mUserInput, nullptr,
michael@0 2461 shell->GetDocument());
michael@0 2462 nsContentUtils::DispatchXULCommand(mMenu, mIsTrusted, nullptr, shell,
michael@0 2463 mControl, mAlt, mShift, mMeta);
michael@0 2464 }
michael@0 2465
michael@0 2466 if (popup && mCloseMenuMode != CloseMenuMode_None)
michael@0 2467 pm->HidePopup(popup, mCloseMenuMode == CloseMenuMode_Auto, true, false, false);
michael@0 2468
michael@0 2469 return NS_OK;
michael@0 2470 }

mercurial