nsXULPopupManager.cpp (107986B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsXULPopupManager.h" 8 9 #include "PopupQueue.h" 10 #include "WindowRenderer.h" 11 #include "XULButtonElement.h" 12 #include "mozilla/AnimationUtils.h" 13 #include "mozilla/Assertions.h" 14 #include "mozilla/Attributes.h" 15 #include "mozilla/AutoRestore.h" 16 #include "mozilla/EventDispatcher.h" 17 #include "mozilla/EventStateManager.h" 18 #include "mozilla/FlushType.h" 19 #include "mozilla/LookAndFeel.h" 20 #include "mozilla/MouseEvents.h" 21 #include "mozilla/PointerLockManager.h" 22 #include "mozilla/PresShell.h" 23 #include "mozilla/ScopeExit.h" 24 #include "mozilla/Services.h" 25 #include "mozilla/StaticPrefs_ui.h" 26 #include "mozilla/UniquePtr.h" 27 #include "mozilla/dom/Document.h" 28 #include "mozilla/dom/DocumentInlines.h" 29 #include "mozilla/dom/Element.h" 30 #include "mozilla/dom/Event.h" // for Event 31 #include "mozilla/dom/HTMLSlotElement.h" 32 #include "mozilla/dom/KeyboardEvent.h" 33 #include "mozilla/dom/KeyboardEventBinding.h" 34 #include "mozilla/dom/MouseEvent.h" 35 #include "mozilla/dom/PopupPositionedEvent.h" 36 #include "mozilla/dom/PopupPositionedEventBinding.h" 37 #include "mozilla/dom/UIEvent.h" 38 #include "mozilla/dom/UserActivation.h" 39 #include "mozilla/dom/XULCommandEvent.h" 40 #include "mozilla/dom/XULMenuBarElement.h" 41 #include "mozilla/dom/XULMenuElement.h" 42 #include "mozilla/dom/XULPopupElement.h" 43 #include "mozilla/widget/NativeMenuSupport.h" 44 #include "mozilla/widget/nsAutoRollup.h" 45 #include "nsCSSFrameConstructor.h" 46 #include "nsCaret.h" 47 #include "nsContentUtils.h" 48 #include "nsFocusManager.h" 49 #include "nsFrameManager.h" 50 #include "nsGkAtoms.h" 51 #include "nsGlobalWindowOuter.h" 52 #include "nsIBaseWindow.h" 53 #include "nsIContentInlines.h" 54 #include "nsIDOMXULCommandDispatcher.h" 55 #include "nsIDocShell.h" 56 #include "nsIInterfaceRequestorUtils.h" 57 #include "nsIObserverService.h" 58 #include "nsISound.h" 59 #include "nsITimer.h" 60 #include "nsLayoutUtils.h" 61 #include "nsMenuPopupFrame.h" 62 #include "nsPIDOMWindow.h" 63 #include "nsPIWindowRoot.h" 64 #include "nsPresContextInlines.h" 65 #include "nsXULElement.h" 66 67 using namespace mozilla; 68 using namespace mozilla::dom; 69 using mozilla::widget::NativeMenu; 70 71 static_assert(KeyboardEvent_Binding::DOM_VK_HOME == 72 KeyboardEvent_Binding::DOM_VK_END + 1 && 73 KeyboardEvent_Binding::DOM_VK_LEFT == 74 KeyboardEvent_Binding::DOM_VK_END + 2 && 75 KeyboardEvent_Binding::DOM_VK_UP == 76 KeyboardEvent_Binding::DOM_VK_END + 3 && 77 KeyboardEvent_Binding::DOM_VK_RIGHT == 78 KeyboardEvent_Binding::DOM_VK_END + 4 && 79 KeyboardEvent_Binding::DOM_VK_DOWN == 80 KeyboardEvent_Binding::DOM_VK_END + 5, 81 "nsXULPopupManager assumes some keyCode values are consecutive"); 82 83 #define NS_DIRECTION_IS_INLINE(dir) \ 84 (dir == eNavigationDirection_Start || dir == eNavigationDirection_End) 85 #define NS_DIRECTION_IS_BLOCK(dir) \ 86 (dir == eNavigationDirection_Before || dir == eNavigationDirection_After) 87 #define NS_DIRECTION_IS_BLOCK_TO_EDGE(dir) \ 88 (dir == eNavigationDirection_First || dir == eNavigationDirection_Last) 89 90 static_assert(static_cast<uint8_t>(mozilla::StyleDirection::Ltr) == 0 && 91 static_cast<uint8_t>(mozilla::StyleDirection::Rtl) == 1, 92 "Left to Right should be 0 and Right to Left should be 1"); 93 94 const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = { 95 { 96 eNavigationDirection_Last, // KeyboardEvent_Binding::DOM_VK_END 97 eNavigationDirection_First, // KeyboardEvent_Binding::DOM_VK_HOME 98 eNavigationDirection_Start, // KeyboardEvent_Binding::DOM_VK_LEFT 99 eNavigationDirection_Before, // KeyboardEvent_Binding::DOM_VK_UP 100 eNavigationDirection_End, // KeyboardEvent_Binding::DOM_VK_RIGHT 101 eNavigationDirection_After // KeyboardEvent_Binding::DOM_VK_DOWN 102 }, 103 { 104 eNavigationDirection_Last, // KeyboardEvent_Binding::DOM_VK_END 105 eNavigationDirection_First, // KeyboardEvent_Binding::DOM_VK_HOME 106 eNavigationDirection_End, // KeyboardEvent_Binding::DOM_VK_LEFT 107 eNavigationDirection_Before, // KeyboardEvent_Binding::DOM_VK_UP 108 eNavigationDirection_Start, // KeyboardEvent_Binding::DOM_VK_RIGHT 109 eNavigationDirection_After // KeyboardEvent_Binding::DOM_VK_DOWN 110 }}; 111 112 nsXULPopupManager* nsXULPopupManager::sInstance = nullptr; 113 114 PendingPopup::PendingPopup(Element* aPopup, mozilla::dom::Event* aEvent) 115 : mPopup(aPopup), mEvent(aEvent), mModifiers(0) { 116 InitMousePoint(); 117 } 118 119 namespace { 120 121 bool PopupQueueable(Element* aPopup) { 122 MOZ_ASSERT(aPopup); 123 return aPopup->GetBoolAttr(nsGkAtoms::queue); 124 } 125 126 } // namespace 127 128 void PendingPopup::InitMousePoint() { 129 // get the event coordinates relative to the root frame of the document 130 // containing the popup. 131 if (!mEvent) { 132 return; 133 } 134 135 WidgetEvent* event = mEvent->WidgetEventPtr(); 136 WidgetInputEvent* inputEvent = event->AsInputEvent(); 137 if (inputEvent) { 138 mModifiers = inputEvent->mModifiers; 139 } 140 Document* doc = mPopup->GetUncomposedDoc(); 141 if (!doc) { 142 return; 143 } 144 145 PresShell* presShell = doc->GetPresShell(); 146 nsPresContext* presContext; 147 if (presShell && (presContext = presShell->GetPresContext())) { 148 nsPresContext* rootDocPresContext = presContext->GetRootPresContext(); 149 if (!rootDocPresContext) { 150 return; 151 } 152 153 nsIFrame* rootDocumentRootFrame = 154 rootDocPresContext->PresShell()->GetRootFrame(); 155 if ((event->IsMouseEventClassOrHasClickRelatedPointerEvent() || 156 event->mClass == eMouseScrollEventClass || 157 event->mClass == eWheelEventClass) && 158 !event->AsGUIEvent()->mWidget) { 159 // no widget, so just use the client point if available 160 MouseEvent* mouseEvent = mEvent->AsMouseEvent(); 161 const CSSIntPoint clientPt(RoundedToInt(mouseEvent->ClientPoint())); 162 163 // XXX this doesn't handle IFRAMEs in transforms 164 nsPoint thisDocToRootDocOffset = 165 presShell->GetRootFrame()->GetOffsetToCrossDoc(rootDocumentRootFrame); 166 // convert to device pixels 167 mMousePoint.x = presContext->AppUnitsToDevPixels( 168 nsPresContext::CSSPixelsToAppUnits(clientPt.x) + 169 thisDocToRootDocOffset.x); 170 mMousePoint.y = presContext->AppUnitsToDevPixels( 171 nsPresContext::CSSPixelsToAppUnits(clientPt.y) + 172 thisDocToRootDocOffset.y); 173 } else if (rootDocumentRootFrame) { 174 nsPoint pnt = nsLayoutUtils::GetEventCoordinatesRelativeTo( 175 event, RelativeTo{rootDocumentRootFrame}); 176 mMousePoint = 177 LayoutDeviceIntPoint(rootDocPresContext->AppUnitsToDevPixels(pnt.x), 178 rootDocPresContext->AppUnitsToDevPixels(pnt.y)); 179 } 180 } 181 } 182 183 already_AddRefed<nsIContent> PendingPopup::GetTriggerContent() const { 184 nsCOMPtr<nsIContent> target = 185 do_QueryInterface(mEvent ? mEvent->GetTarget() : nullptr); 186 return target.forget(); 187 } 188 189 uint16_t PendingPopup::MouseInputSource() const { 190 if (mEvent) { 191 mozilla::WidgetMouseEventBase* mouseEvent = 192 mEvent->WidgetEventPtr()->AsMouseEventBase(); 193 if (mouseEvent) { 194 return mouseEvent->mInputSource; 195 } 196 197 RefPtr<XULCommandEvent> commandEvent = mEvent->AsXULCommandEvent(); 198 if (commandEvent) { 199 return commandEvent->InputSource(); 200 } 201 } 202 203 return MouseEvent_Binding::MOZ_SOURCE_UNKNOWN; 204 } 205 206 XULPopupElement* nsMenuChainItem::Element() { return &mFrame->PopupElement(); } 207 208 void nsMenuChainItem::SetParent(UniquePtr<nsMenuChainItem> aParent) { 209 MOZ_ASSERT_IF(aParent, !aParent->mChild); 210 auto oldParent = Detach(); 211 mParent = std::move(aParent); 212 if (mParent) { 213 mParent->mChild = this; 214 } 215 } 216 217 UniquePtr<nsMenuChainItem> nsMenuChainItem::Detach() { 218 if (mParent) { 219 MOZ_ASSERT(mParent->mChild == this, 220 "Unexpected - parent's child not set to this"); 221 mParent->mChild = nullptr; 222 } 223 return std::move(mParent); 224 } 225 226 void nsXULPopupManager::AddMenuChainItem(UniquePtr<nsMenuChainItem> aItem) { 227 auto* frame = aItem->Frame(); 228 PopupType popupType = frame->GetPopupType(); 229 if (StaticPrefs::layout_cursor_disable_for_popups() && 230 popupType != PopupType::Tooltip) { 231 if (auto* rootPc = frame->PresContext()->GetRootPresContext()) { 232 if (nsCOMPtr<nsIWidget> rootWidget = rootPc->GetRootWidget()) { 233 rootWidget->SetCustomCursorAllowed(false); 234 } 235 } 236 } 237 238 // popups normally hide when an outside click occurs. Panels may use 239 // the noautohide attribute to disable this behaviour. It is expected 240 // that the application will hide these popups manually. The tooltip 241 // listener will handle closing the tooltip also. 242 nsIContent* oldmenu = nullptr; 243 if (mPopups) { 244 oldmenu = mPopups->Element(); 245 } 246 aItem->SetParent(std::move(mPopups)); 247 mPopups = std::move(aItem); 248 SetCaptureState(oldmenu); 249 } 250 251 void nsXULPopupManager::RemoveMenuChainItem(nsMenuChainItem* aItem) { 252 if (mPopupQueue) { 253 mPopupQueue->NotifyDismissed(aItem->Element()); 254 } 255 256 nsPresContext* rootPC = aItem->Frame()->PresContext()->GetRootPresContext(); 257 auto matcher = [&](nsMenuChainItem* aChainItem) -> bool { 258 return aChainItem != aItem && 259 rootPC == aChainItem->Frame()->PresContext()->GetRootPresContext(); 260 }; 261 if (rootPC && !FirstMatchingPopup(matcher)) { 262 if (nsCOMPtr<nsIWidget> rootWidget = rootPC->GetRootWidget()) { 263 rootWidget->SetCustomCursorAllowed(true); 264 } 265 } 266 267 auto parent = aItem->Detach(); 268 if (auto* child = aItem->GetChild()) { 269 MOZ_ASSERT(aItem != mPopups, 270 "Unexpected - popup with child at end of chain"); 271 // This will kill aItem by changing child's mParent pointer. 272 child->SetParent(std::move(parent)); 273 } else { 274 // An item without a child should be the first item in the chain, so set 275 // the first item pointer, pointed to by aRoot, to the parent. 276 MOZ_ASSERT(aItem == mPopups, 277 "Unexpected - popup with no child not at end of chain"); 278 mPopups = std::move(parent); 279 } 280 } 281 282 nsMenuChainItem* nsXULPopupManager::FirstMatchingPopup( 283 mozilla::FunctionRef<bool(nsMenuChainItem*)> aMatcher) const { 284 for (nsMenuChainItem* popup = mPopups.get(); popup; 285 popup = popup->GetParent()) { 286 if (aMatcher(popup)) { 287 return popup; 288 } 289 } 290 return nullptr; 291 } 292 293 void nsMenuChainItem::UpdateFollowAnchor() { 294 mFollowAnchor = mFrame->ShouldFollowAnchor(mCurrentRect); 295 } 296 297 void nsMenuChainItem::CheckForAnchorChange() { 298 if (mFollowAnchor) { 299 mFrame->CheckForAnchorChange(mCurrentRect); 300 } 301 } 302 303 NS_IMPL_ISUPPORTS(nsXULPopupManager, nsIDOMEventListener, nsIObserver) 304 305 nsXULPopupManager::nsXULPopupManager() 306 : mActiveMenuBar(nullptr), mPopups(nullptr), mPendingPopup(nullptr) { 307 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 308 if (obs) { 309 obs->AddObserver(this, "xpcom-shutdown", false); 310 } 311 312 mPopupQueue = new PopupQueue(); 313 } 314 315 nsXULPopupManager::~nsXULPopupManager() { 316 NS_ASSERTION(!mPopups, "XUL popups still open"); 317 318 if (mNativeMenu) { 319 mNativeMenu->RemoveObserver(this); 320 } 321 } 322 323 nsresult nsXULPopupManager::Init() { 324 sInstance = new nsXULPopupManager(); 325 NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY); 326 NS_ADDREF(sInstance); 327 return NS_OK; 328 } 329 330 void nsXULPopupManager::Shutdown() { NS_IF_RELEASE(sInstance); } 331 332 NS_IMETHODIMP 333 nsXULPopupManager::Observe(nsISupports* aSubject, const char* aTopic, 334 const char16_t* aData) { 335 if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) { 336 mPopupQueue = nullptr; 337 338 if (mKeyListener) { 339 mKeyListener->RemoveEventListener(u"keypress"_ns, this, true); 340 mKeyListener->RemoveEventListener(u"keydown"_ns, this, true); 341 mKeyListener->RemoveEventListener(u"keyup"_ns, this, true); 342 mKeyListener = nullptr; 343 } 344 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 345 if (obs) { 346 obs->RemoveObserver(this, "xpcom-shutdown"); 347 } 348 } 349 350 return NS_OK; 351 } 352 353 nsXULPopupManager* nsXULPopupManager::GetInstance() { 354 MOZ_ASSERT(sInstance); 355 return sInstance; 356 } 357 358 bool nsXULPopupManager::RollupTooltips() { 359 const RollupOptions options{0, nullptr, AllowAnimations::No}; 360 return RollupInternal(RollupKind::Tooltip, options, nullptr); 361 } 362 363 bool nsXULPopupManager::Rollup(const RollupOptions& aOptions, 364 nsIContent** aLastRolledUp) { 365 return RollupInternal(RollupKind::Menu, aOptions, aLastRolledUp); 366 } 367 368 bool nsXULPopupManager::RollupNativeMenu() { 369 if (mNativeMenu) { 370 RefPtr<NativeMenu> menu = mNativeMenu; 371 return menu->Close(); 372 } 373 return false; 374 } 375 376 bool nsXULPopupManager::RollupInternal(RollupKind aKind, 377 const RollupOptions& aOptions, 378 nsIContent** aLastRolledUp) { 379 if (aLastRolledUp) { 380 *aLastRolledUp = nullptr; 381 } 382 383 // We can disable the autohide behavior via a pref to ease debugging. 384 if (StaticPrefs::ui_popup_disable_autohide()) { 385 // Required on linux to allow events to work on other targets. 386 if (mWidget) { 387 mWidget->CaptureRollupEvents(false); 388 } 389 return false; 390 } 391 392 nsMenuChainItem* item = GetRollupItem(aKind); 393 if (!item) { 394 return false; 395 } 396 if (aLastRolledUp) { 397 // We need to get the popup that will be closed last, so that widget can 398 // keep track of it so it doesn't reopen if a mousedown event is going to 399 // processed. Keep going up the menu chain to get the first level menu of 400 // the same type. If a different type is encountered it means we have, 401 // for example, a menulist or context menu inside a panel, and we want to 402 // treat these as distinct. It's possible that this menu doesn't end up 403 // closing because the popuphiding event was cancelled, but in that case 404 // we don't need to deal with the menu reopening as it will already still 405 // be open. 406 nsMenuChainItem* first = item; 407 while (first->GetParent()) { 408 nsMenuChainItem* parent = first->GetParent(); 409 if (first->Frame()->GetPopupType() != parent->Frame()->GetPopupType() || 410 first->IsContextMenu() != parent->IsContextMenu()) { 411 break; 412 } 413 first = parent; 414 } 415 416 *aLastRolledUp = first->Element(); 417 } 418 419 ConsumeOutsideClicksResult consumeResult = 420 item->Frame()->ConsumeOutsideClicks(); 421 bool consume = consumeResult == ConsumeOutsideClicks_True; 422 bool rollup = true; 423 424 // If norolluponanchor is true, then don't rollup when clicking the anchor. 425 // This would be used to allow adjusting the caret position in an 426 // autocomplete field without hiding the popup for example. 427 bool noRollupOnAnchor = 428 (!consume && aOptions.mPoint && 429 item->Frame()->GetContent()->AsElement()->AttrValueIs( 430 kNameSpaceID_None, nsGkAtoms::norolluponanchor, nsGkAtoms::_true, 431 eCaseMatters)); 432 433 // When ConsumeOutsideClicks_ParentOnly is used, always consume the click 434 // when the click was over the anchor. This way, clicking on a menu doesn't 435 // reopen the menu. 436 if ((consumeResult == ConsumeOutsideClicks_ParentOnly || noRollupOnAnchor) && 437 aOptions.mPoint) { 438 nsMenuPopupFrame* popupFrame = item->Frame(); 439 CSSIntRect anchorRect = [&] { 440 if (popupFrame->IsAnchored()) { 441 // Check if the popup has an anchor rectangle set. If not, get the 442 // rectangle from the anchor element. 443 auto r = popupFrame->GetScreenAnchorRect(); 444 if (r.x != -1 && r.y != -1) { 445 // Prefer the untransformed anchor rect, so as to account for Wayland 446 // properly. Note we still need to check GetScreenAnchorRect() tho, so 447 // as to detect whether the anchor came from the popup opening call, 448 // or from an element (in which case we want to take the code-path 449 // below).. 450 auto untransformed = popupFrame->GetUntransformedAnchorRect(); 451 if (!untransformed.IsEmpty()) { 452 return CSSIntRect::FromAppUnitsRounded(untransformed); 453 } 454 return r; 455 } 456 } 457 458 auto* anchor = Element::FromNodeOrNull(popupFrame->GetAnchor()); 459 if (!anchor) { 460 return CSSIntRect(); 461 } 462 463 // Check if the anchor has indicated another node to use for checking 464 // for roll-up. That way, we can anchor a popup on anonymous content 465 // or an individual icon, while clicking elsewhere within a button or 466 // other container doesn't result in us re-opening the popup. 467 nsAutoString consumeAnchor; 468 anchor->GetAttr(nsGkAtoms::consumeanchor, consumeAnchor); 469 if (!consumeAnchor.IsEmpty()) { 470 if (Element* newAnchor = 471 anchor->OwnerDoc()->GetElementById(consumeAnchor)) { 472 anchor = newAnchor; 473 } 474 } 475 476 nsIFrame* f = anchor->GetPrimaryFrame(); 477 if (!f) { 478 return CSSIntRect(); 479 } 480 return f->GetScreenRect(); 481 }(); 482 483 // It's possible that some other element is above the anchor at the same 484 // position, but the only thing that would happen is that the mouse 485 // event will get consumed, so here only a quick coordinates check is 486 // done rather than a slower complete check of what is at that location. 487 nsPresContext* presContext = item->Frame()->PresContext(); 488 CSSIntPoint posCSSPixels = 489 presContext->DevPixelsToIntCSSPixels(*aOptions.mPoint); 490 if (anchorRect.Contains(posCSSPixels)) { 491 if (consumeResult == ConsumeOutsideClicks_ParentOnly) { 492 consume = true; 493 } 494 495 if (noRollupOnAnchor) { 496 rollup = false; 497 } 498 } 499 } 500 501 if (!rollup) { 502 return false; 503 } 504 505 // If a number of popups to close has been specified, determine the last 506 // popup to close. 507 Element* lastPopup = nullptr; 508 uint32_t count = aOptions.mCount; 509 if (count && count != UINT32_MAX) { 510 nsMenuChainItem* last = item; 511 while (--count && last->GetParent()) { 512 last = last->GetParent(); 513 } 514 if (last) { 515 lastPopup = last->Element(); 516 } 517 } 518 519 HidePopupOptions options{HidePopupOption::HideChain, 520 HidePopupOption::DeselectMenu, 521 HidePopupOption::IsRollup}; 522 if (aOptions.mAllowAnimations == AllowAnimations::No) { 523 options += HidePopupOption::DisableAnimations; 524 } 525 526 HidePopup(item->Element(), options, lastPopup); 527 528 return consume; 529 } 530 531 //////////////////////////////////////////////////////////////////////// 532 bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent() { 533 // should rollup only for autocomplete widgets 534 // XXXndeakin this should really be something the popup has more control over 535 536 nsMenuChainItem* item = GetTopVisibleMenu(); 537 if (!item) { 538 return false; 539 } 540 541 nsIContent* content = item->Frame()->GetContent(); 542 if (!content || !content->IsElement()) { 543 return false; 544 } 545 546 Element* element = content->AsElement(); 547 if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel, 548 nsGkAtoms::_true, eCaseMatters)) { 549 return true; 550 } 551 552 if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel, 553 nsGkAtoms::_false, eCaseMatters)) { 554 return false; 555 } 556 557 nsAutoString value; 558 element->GetAttr(nsGkAtoms::type, value); 559 return StringBeginsWith(value, u"autocomplete"_ns); 560 } 561 562 bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent() { 563 nsMenuChainItem* item = GetTopVisibleMenu(); 564 if (!item) { 565 return false; 566 } 567 568 nsMenuPopupFrame* frame = item->Frame(); 569 if (frame->GetPopupType() != PopupType::Panel) { 570 return true; 571 } 572 573 return !frame->GetContent()->AsElement()->AttrValueIs( 574 kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::arrow, eCaseMatters); 575 } 576 577 // a menu should not roll up if activated by a mouse activate message (eg. 578 // X-mouse) 579 bool nsXULPopupManager::ShouldRollupOnMouseActivate() { return false; } 580 581 uint32_t nsXULPopupManager::GetSubmenuWidgetChain( 582 nsTArray<nsIWidget*>* aWidgetChain) { 583 // this method is used by the widget code to determine the list of popups 584 // that are open. If a mouse click occurs outside one of these popups, the 585 // panels will roll up. If the click is inside a popup, they will not roll up 586 uint32_t count = 0, sameTypeCount = 0; 587 588 NS_ASSERTION(aWidgetChain, "null parameter"); 589 nsMenuChainItem* item = GetTopVisibleMenu(); 590 while (item) { 591 nsMenuChainItem* parent = item->GetParent(); 592 if (!item->IsNoAutoHide()) { 593 nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget(); 594 NS_ASSERTION(widget, "open popup has no widget"); 595 if (widget) { 596 aWidgetChain->AppendElement(widget.get()); 597 // In the case when a menulist inside a panel is open, clicking in the 598 // panel should still roll up the menu, so if a different type is found, 599 // stop scanning. 600 if (!sameTypeCount) { 601 count++; 602 if (!parent || 603 item->Frame()->GetPopupType() != 604 parent->Frame()->GetPopupType() || 605 item->IsContextMenu() != parent->IsContextMenu()) { 606 sameTypeCount = count; 607 } 608 } 609 } 610 } 611 item = parent; 612 } 613 614 return sameTypeCount; 615 } 616 617 nsIWidget* nsXULPopupManager::GetRollupWidget() { 618 nsMenuChainItem* item = GetTopVisibleMenu(); 619 return item ? item->Frame()->GetWidget() : nullptr; 620 } 621 622 void nsXULPopupManager::AdjustPopupsOnWindowChange( 623 nsPIDOMWindowOuter* aWindow) { 624 // When the parent window is moved, adjust any child popups. Dismissable 625 // menus and panels are expected to roll up when a window is moved, so there 626 // is no need to check these popups, only the noautohide popups. 627 628 // The items are added to a list so that they can be adjusted bottom to top. 629 nsTArray<nsMenuPopupFrame*> list; 630 631 for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) { 632 // only move popups that are within the same window and where auto 633 // positioning has not been disabled 634 if (!item->IsNoAutoHide()) { 635 continue; 636 } 637 nsMenuPopupFrame* frame = item->Frame(); 638 nsIContent* popup = frame->GetContent(); 639 if (!popup) { 640 continue; 641 } 642 Document* document = popup->GetUncomposedDoc(); 643 if (!document) { 644 continue; 645 } 646 nsPIDOMWindowOuter* window = document->GetWindow(); 647 if (!window) { 648 continue; 649 } 650 window = window->GetPrivateRoot(); 651 if (window == aWindow) { 652 list.AppendElement(frame); 653 } 654 } 655 656 for (int32_t l = list.Length() - 1; l >= 0; l--) { 657 list[l]->SetPopupPosition(true); 658 } 659 } 660 661 void nsXULPopupManager::AdjustPopupsOnWindowChange(PresShell* aPresShell) { 662 if (aPresShell->GetDocument()) { 663 AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow()); 664 } 665 } 666 667 nsMenuPopupFrame* nsXULPopupManager::GetPopupFrameForContent( 668 nsIContent* aContent, bool aShouldFlush) { 669 if (aShouldFlush) { 670 Document* document = aContent->GetUncomposedDoc(); 671 if (document) { 672 if (RefPtr<PresShell> presShell = document->GetPresShell()) { 673 presShell->FlushPendingNotifications(FlushType::Layout); 674 } 675 } 676 } 677 678 return do_QueryFrame(aContent->GetPrimaryFrame()); 679 } 680 681 nsMenuChainItem* nsXULPopupManager::GetRollupItem(RollupKind aKind) { 682 for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) { 683 if (item->Frame()->PopupState() == ePopupInvisible) { 684 continue; 685 } 686 MOZ_ASSERT_IF(item->Frame()->GetPopupType() == PopupType::Tooltip, 687 item->IsNoAutoHide()); 688 const bool valid = aKind == RollupKind::Tooltip 689 ? item->Frame()->GetPopupType() == PopupType::Tooltip 690 : !item->IsNoAutoHide(); 691 if (valid) { 692 return item; 693 } 694 } 695 return nullptr; 696 } 697 698 void nsXULPopupManager::SetActiveMenuBar(XULMenuBarElement* aMenuBar, 699 bool aActivate) { 700 if (aActivate) { 701 mActiveMenuBar = aMenuBar; 702 } else if (mActiveMenuBar == aMenuBar) { 703 mActiveMenuBar = nullptr; 704 } 705 UpdateKeyboardListeners(); 706 } 707 708 static CloseMenuMode GetCloseMenuMode(nsIContent* aMenu) { 709 if (!aMenu->IsElement()) { 710 return CloseMenuMode_Auto; 711 } 712 713 static Element::AttrValuesArray strings[] = {nsGkAtoms::none, 714 nsGkAtoms::single, nullptr}; 715 switch (aMenu->AsElement()->FindAttrValueIn( 716 kNameSpaceID_None, nsGkAtoms::closemenu, strings, eCaseMatters)) { 717 case 0: 718 return CloseMenuMode_None; 719 case 1: 720 return CloseMenuMode_Single; 721 default: 722 return CloseMenuMode_Auto; 723 } 724 } 725 726 auto nsXULPopupManager::MayShowMenu(nsIContent* aMenu) -> MayShowMenuResult { 727 if (mNativeMenu && aMenu->IsElement() && 728 mNativeMenu->Element()->Contains(aMenu)) { 729 return {true}; 730 } 731 732 auto* menu = XULButtonElement::FromNode(aMenu); 733 if (!menu) { 734 return {}; 735 } 736 737 nsMenuPopupFrame* popupFrame = menu->GetMenuPopup(FlushType::None); 738 if (!popupFrame || !MayShowPopup(popupFrame)) { 739 return {}; 740 } 741 return {false, menu, popupFrame}; 742 } 743 744 void nsXULPopupManager::ShowMenu(nsIContent* aMenu, bool aSelectFirstItem) { 745 auto mayShowResult = MayShowMenu(aMenu); 746 if (NS_WARN_IF(!mayShowResult)) { 747 return; 748 } 749 750 if (mayShowResult.mIsNative) { 751 mNativeMenu->OpenSubmenu(aMenu->AsElement()); 752 return; 753 } 754 755 nsMenuPopupFrame* popupFrame = mayShowResult.mMenuPopupFrame; 756 757 // inherit whether or not we're a context menu from the parent 758 const bool onMenuBar = mayShowResult.mMenuButton->IsOnMenuBar(); 759 const bool onmenu = mayShowResult.mMenuButton->IsOnMenu(); 760 const bool parentIsContextMenu = mayShowResult.mMenuButton->IsOnContextMenu(); 761 762 nsAutoString position; 763 764 #ifdef XP_MACOSX 765 if (aMenu->IsXULElement(nsGkAtoms::menulist)) { 766 position.AssignLiteral("selection"); 767 } else 768 #endif 769 770 if (onMenuBar || !onmenu) { 771 position.AssignLiteral("after_start"); 772 } else { 773 position.AssignLiteral("end_before"); 774 } 775 776 // there is no trigger event for menus 777 popupFrame->InitializePopup(aMenu, nullptr, position, 0, 0, 778 MenuPopupAnchorType::Node, true); 779 PendingPopup pendingPopup(&popupFrame->PopupElement(), nullptr); 780 BeginShowingPopup(pendingPopup, parentIsContextMenu, aSelectFirstItem); 781 } 782 783 static bool ShouldUseNativeContextMenus() { 784 #ifdef HAS_NATIVE_MENU_SUPPORT 785 return mozilla::widget::NativeMenuSupport::ShouldUseNativeContextMenus(); 786 #else 787 return false; 788 #endif 789 } 790 791 void nsXULPopupManager::ShowPopup(Element* aPopup, nsIContent* aAnchorContent, 792 const nsAString& aPosition, int32_t aXPos, 793 int32_t aYPos, bool aIsContextMenu, 794 bool aAttributesOverride, 795 bool aSelectFirstItem, Event* aTriggerEvent) { 796 auto callback = [aAnchorContent = RefPtr{aAnchorContent}, 797 aPosition = nsString(aPosition), aXPos, aYPos, 798 aIsContextMenu, aAttributesOverride, aSelectFirstItem, 799 aTriggerEvent = RefPtr{aTriggerEvent}](Element* aPopup) { 800 auto self = sInstance; 801 if (!self) { 802 return; 803 } 804 805 auto scopeExit = MakeScopeExit([&]() { 806 if (self->mPopupQueue) { 807 self->mPopupQueue->NotifyDismissed(aPopup); 808 } 809 }); 810 811 #ifdef XP_MACOSX 812 // On Mac, use a native menu if possible since the non-native menu looks out 813 // of place. Native menus for anchored popups are not currently implemented, 814 // so fall back to the non-native path below if `aAnchorContent` is given. 815 // We also fall back if the position string is not empty so we don't break 816 // tests that either themselves call or test app features that call 817 // `openPopup(null, "position")`. 818 if (!aAnchorContent && aPosition.IsEmpty() && 819 ShouldUseNativeContextMenus() && 820 aPopup->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menupopup) && 821 self->ShowPopupAsNativeMenu(aPopup, aXPos, aYPos, aIsContextMenu, 822 aTriggerEvent)) { 823 return; 824 } 825 #endif 826 827 nsMenuPopupFrame* popupFrame = self->GetPopupFrameForContent(aPopup, true); 828 if (!popupFrame || !self->MayShowPopup(popupFrame)) { 829 return; 830 } 831 832 PendingPopup pendingPopup(aPopup, aTriggerEvent); 833 nsCOMPtr<nsIContent> triggerContent = pendingPopup.GetTriggerContent(); 834 835 popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition, 836 aXPos, aYPos, MenuPopupAnchorType::Node, 837 aAttributesOverride); 838 839 if (!self->BeginShowingPopup(pendingPopup, aIsContextMenu, 840 aSelectFirstItem)) { 841 return; 842 } 843 844 scopeExit.release(); 845 }; 846 847 if (!mPopupQueue) { 848 callback(aPopup); 849 return; 850 } 851 852 if (PopupQueueable(aPopup)) { 853 mPopupQueue->Enqueue(aPopup, callback); 854 } else { 855 DismissQueueableShownPopups(); 856 mPopupQueue->Show(aPopup, callback); 857 } 858 } 859 860 void nsXULPopupManager::ShowPopupAtScreen(Element* aPopup, int32_t aXPos, 861 int32_t aYPos, bool aIsContextMenu, 862 Event* aTriggerEvent) { 863 auto callback = [aXPos, aYPos, aIsContextMenu, 864 aTriggerEvent = RefPtr{aTriggerEvent}](Element* aPopup) { 865 auto self = sInstance; 866 if (!self) { 867 return; 868 } 869 870 auto scopeExit = MakeScopeExit([&]() { 871 if (self->mPopupQueue) { 872 self->mPopupQueue->NotifyDismissed(aPopup); 873 } 874 }); 875 876 if (aIsContextMenu && ShouldUseNativeContextMenus() && 877 self->ShowPopupAsNativeMenu(aPopup, aXPos, aYPos, aIsContextMenu, 878 aTriggerEvent)) { 879 return; 880 } 881 882 nsMenuPopupFrame* popupFrame = self->GetPopupFrameForContent(aPopup, true); 883 if (!popupFrame || !self->MayShowPopup(popupFrame)) { 884 return; 885 } 886 887 PendingPopup pendingPopup(aPopup, aTriggerEvent); 888 nsCOMPtr<nsIContent> triggerContent = pendingPopup.GetTriggerContent(); 889 890 popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos, 891 aIsContextMenu); 892 if (!self->BeginShowingPopup(pendingPopup, aIsContextMenu, false)) { 893 return; 894 } 895 896 scopeExit.release(); 897 }; 898 899 if (!mPopupQueue) { 900 callback(aPopup); 901 return; 902 } 903 904 if (PopupQueueable(aPopup)) { 905 mPopupQueue->Enqueue(aPopup, callback); 906 } else { 907 DismissQueueableShownPopups(); 908 mPopupQueue->Show(aPopup, callback); 909 } 910 } 911 912 void ToggleTouchMode(const PendingPopup& aPopup) { 913 aPopup.mPopup->SetBoolAttr( 914 nsGkAtoms::touchmode, 915 aPopup.MouseInputSource() == MouseEvent_Binding::MOZ_SOURCE_TOUCH); 916 } 917 918 bool nsXULPopupManager::ShowPopupAsNativeMenu(Element* aPopup, int32_t aXPos, 919 int32_t aYPos, 920 bool aIsContextMenu, 921 Event* aTriggerEvent) { 922 if (mNativeMenu) { 923 NS_WARNING("Native menu still open when trying to open another"); 924 RefPtr<NativeMenu> menu = mNativeMenu; 925 (void)menu->Close(); 926 menu->RemoveObserver(this); 927 mNativeMenu = nullptr; 928 } 929 930 RefPtr<NativeMenu> menu; 931 #ifdef HAS_NATIVE_MENU_SUPPORT 932 menu = mozilla::widget::NativeMenuSupport::CreateNativeContextMenu(aPopup); 933 #endif 934 935 if (!menu) { 936 return false; 937 } 938 939 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); 940 if (!popupFrame) { 941 return true; 942 } 943 944 // Hide the menu from our accessibility code so that we don't dispatch custom 945 // accessibility notifications which would conflict with the system ones. 946 aPopup->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_hidden, u"true"_ns, true); 947 948 PendingPopup pendingPopup(aPopup, aTriggerEvent); 949 nsCOMPtr<nsIContent> triggerContent = pendingPopup.GetTriggerContent(); 950 951 popupFrame->InitializePopupAsNativeContextMenu(triggerContent, aXPos, aYPos); 952 953 RefPtr<nsPresContext> presContext = popupFrame->PresContext(); 954 nsEventStatus status = FirePopupShowingEvent(pendingPopup, presContext); 955 956 // if the event was cancelled, don't open the popup, reset its state back 957 // to closed and clear its trigger content. 958 if (status == nsEventStatus_eConsumeNoDefault) { 959 if ((popupFrame = GetPopupFrameForContent(aPopup, true))) { 960 popupFrame->SetPopupState(ePopupClosed); 961 popupFrame->ClearTriggerContent(); 962 } 963 return true; 964 } 965 966 mNativeMenu = menu; 967 mNativeMenu->AddObserver(this); 968 nsIFrame* frame = presContext->PresShell()->GetCurrentEventFrame(); 969 if (!frame) { 970 frame = presContext->PresShell()->GetRootFrame(); 971 } 972 mNativeMenu->ShowAsContextMenu(frame, CSSIntPoint(aXPos, aYPos), 973 aIsContextMenu); 974 975 // While the native menu is open, it consumes mouseup events. 976 // Clear any :active state, mouse capture state and drag tracking now. 977 EventStateManager* activeESM = static_cast<EventStateManager*>( 978 EventStateManager::GetActiveEventStateManager()); 979 if (activeESM) { 980 EventStateManager::ClearGlobalActiveContent(activeESM); 981 activeESM->StopTrackingDragGesture(true); 982 } 983 PointerLockManager::Unlock("ShowPopupAsNativeMenu"); 984 PresShell::ReleaseCapturingContent(); 985 986 return true; 987 } 988 989 void nsXULPopupManager::OnNativeMenuOpened() { 990 if (!mNativeMenu) { 991 return; 992 } 993 994 RefPtr<nsXULPopupManager> kungFuDeathGrip(this); 995 996 nsCOMPtr<nsIContent> popup = mNativeMenu->Element(); 997 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(popup, true); 998 if (popupFrame) { 999 popupFrame->SetPopupState(ePopupShown); 1000 } 1001 } 1002 1003 void nsXULPopupManager::OnNativeMenuClosed() { 1004 if (!mNativeMenu) { 1005 return; 1006 } 1007 1008 RefPtr<nsXULPopupManager> kungFuDeathGrip(this); 1009 1010 bool shouldHideChain = 1011 mNativeMenuActivatedItemCloseMenuMode == Some(CloseMenuMode_Auto); 1012 1013 nsCOMPtr<nsIContent> popup = mNativeMenu->Element(); 1014 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(popup, true); 1015 if (popupFrame) { 1016 popupFrame->ClearTriggerContentIncludingDocument(); 1017 popupFrame->SetPopupState(ePopupClosed); 1018 } 1019 mNativeMenu->RemoveObserver(this); 1020 mNativeMenu = nullptr; 1021 mNativeMenuActivatedItemCloseMenuMode = Nothing(); 1022 mNativeMenuSubmenuStates.Clear(); 1023 1024 // Stop hiding the menu from accessibility code, in case it gets opened as a 1025 // non-native menu in the future. 1026 popup->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::aria_hidden, 1027 true); 1028 1029 if (shouldHideChain && mPopups && 1030 mPopups->GetPopupType() == PopupType::Menu) { 1031 // A menu item was activated before this menu closed, and the item requested 1032 // the entire popup chain to be closed, which includes any open non-native 1033 // menus. 1034 // Close the non-native menus now. This matches the HidePopup call in 1035 // nsXULMenuCommandEvent::Run. 1036 HidePopup(mPopups->Element(), {HidePopupOption::HideChain}); 1037 } 1038 } 1039 1040 void nsXULPopupManager::OnNativeSubMenuWillOpen( 1041 mozilla::dom::Element* aPopupElement) { 1042 mNativeMenuSubmenuStates.InsertOrUpdate(aPopupElement, ePopupShowing); 1043 } 1044 1045 void nsXULPopupManager::OnNativeSubMenuDidOpen( 1046 mozilla::dom::Element* aPopupElement) { 1047 mNativeMenuSubmenuStates.InsertOrUpdate(aPopupElement, ePopupShown); 1048 } 1049 1050 void nsXULPopupManager::OnNativeSubMenuClosed( 1051 mozilla::dom::Element* aPopupElement) { 1052 mNativeMenuSubmenuStates.Remove(aPopupElement); 1053 } 1054 1055 void nsXULPopupManager::OnNativeMenuWillActivateItem( 1056 mozilla::dom::Element* aMenuItemElement) { 1057 if (!mNativeMenu) { 1058 return; 1059 } 1060 1061 CloseMenuMode cmm = GetCloseMenuMode(aMenuItemElement); 1062 mNativeMenuActivatedItemCloseMenuMode = Some(cmm); 1063 1064 if (cmm == CloseMenuMode_Auto) { 1065 // If any non-native menus are visible (for example because the context menu 1066 // was opened on a non-native menu item, e.g. in a bookmarks folder), hide 1067 // the non-native menus before executing the item. 1068 HideOpenMenusBeforeExecutingMenu(CloseMenuMode_Auto); 1069 } 1070 } 1071 1072 void nsXULPopupManager::ShowPopupAtScreenRect( 1073 Element* aPopup, const nsAString& aPosition, const nsIntRect& aRect, 1074 bool aIsContextMenu, bool aAttributesOverride, Event* aTriggerEvent) { 1075 auto callback = [aPosition = nsString(aPosition), aRect, aIsContextMenu, 1076 aAttributesOverride, 1077 aTriggerEvent = RefPtr{aTriggerEvent}](Element* aPopup) { 1078 auto self = sInstance; 1079 if (!self) { 1080 return; 1081 } 1082 1083 auto scopeExit = MakeScopeExit([&]() { 1084 if (self->mPopupQueue) { 1085 self->mPopupQueue->NotifyDismissed(aPopup); 1086 } 1087 }); 1088 1089 nsMenuPopupFrame* popupFrame = self->GetPopupFrameForContent(aPopup, true); 1090 if (!popupFrame || !self->MayShowPopup(popupFrame)) { 1091 return; 1092 } 1093 1094 PendingPopup pendingPopup(aPopup, aTriggerEvent); 1095 nsCOMPtr<nsIContent> triggerContent = pendingPopup.GetTriggerContent(); 1096 1097 popupFrame->InitializePopupAtRect(triggerContent, aPosition, aRect, 1098 aAttributesOverride); 1099 1100 if (!self->BeginShowingPopup(pendingPopup, aIsContextMenu, false)) { 1101 return; 1102 } 1103 1104 scopeExit.release(); 1105 }; 1106 1107 if (!mPopupQueue) { 1108 callback(aPopup); 1109 return; 1110 } 1111 1112 if (PopupQueueable(aPopup)) { 1113 mPopupQueue->Enqueue(aPopup, callback); 1114 } else { 1115 DismissQueueableShownPopups(); 1116 mPopupQueue->Show(aPopup, callback); 1117 } 1118 } 1119 1120 void nsXULPopupManager::ShowTooltipAtScreen( 1121 Element* aPopup, nsIContent* aTriggerContent, 1122 const LayoutDeviceIntPoint& aScreenPoint) { 1123 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); 1124 if (!popupFrame || !MayShowPopup(popupFrame)) { 1125 return; 1126 } 1127 1128 PendingPopup pendingPopup(aPopup, nullptr); 1129 1130 nsPresContext* pc = popupFrame->PresContext(); 1131 pendingPopup.SetMousePoint([&] { 1132 // Event coordinates are relative to the root widget 1133 if (nsPresContext* rootPresContext = pc->GetRootPresContext()) { 1134 if (nsCOMPtr<nsIWidget> rootWidget = rootPresContext->GetRootWidget()) { 1135 return aScreenPoint - rootWidget->WidgetToScreenOffset(); 1136 } 1137 } 1138 return aScreenPoint; 1139 }()); 1140 1141 auto screenCSSPoint = 1142 CSSIntPoint::Round(aScreenPoint / pc->CSSToDevPixelScale()); 1143 popupFrame->InitializePopupAtScreen(aTriggerContent, screenCSSPoint.x, 1144 screenCSSPoint.y, false); 1145 1146 BeginShowingPopup(pendingPopup, false, false); 1147 } 1148 1149 static void CheckCaretDrawingState() { 1150 // There is 1 caret per document, we need to find the focused 1151 // document and erase its caret. 1152 nsFocusManager* fm = nsFocusManager::GetFocusManager(); 1153 if (fm) { 1154 nsCOMPtr<mozIDOMWindowProxy> window; 1155 fm->GetFocusedWindow(getter_AddRefs(window)); 1156 if (!window) { 1157 return; 1158 } 1159 1160 auto* piWindow = nsPIDOMWindowOuter::From(window); 1161 MOZ_ASSERT(piWindow); 1162 1163 nsCOMPtr<Document> focusedDoc = piWindow->GetDoc(); 1164 if (!focusedDoc) { 1165 return; 1166 } 1167 1168 PresShell* presShell = focusedDoc->GetPresShell(); 1169 if (!presShell) { 1170 return; 1171 } 1172 1173 RefPtr<nsCaret> caret = presShell->GetCaret(); 1174 if (!caret) { 1175 return; 1176 } 1177 caret->SchedulePaint(); 1178 } 1179 } 1180 1181 void nsXULPopupManager::ShowPopupCallback(Element* aPopup, 1182 nsMenuPopupFrame* aPopupFrame, 1183 bool aIsContextMenu, 1184 bool aSelectFirstItem) { 1185 PopupType popupType = aPopupFrame->GetPopupType(); 1186 const bool isMenu = popupType == PopupType::Menu; 1187 1188 // Popups normally hide when an outside click occurs. Panels may use 1189 // the noautohide attribute to disable this behaviour. It is expected 1190 // that the application will hide these popups manually. The tooltip 1191 // listener will handle closing the tooltip also. 1192 bool isNoAutoHide = 1193 aPopupFrame->IsNoAutoHide() || popupType == PopupType::Tooltip; 1194 1195 auto item = MakeUnique<nsMenuChainItem>(aPopupFrame, isNoAutoHide, 1196 aIsContextMenu, popupType); 1197 1198 // install keyboard event listeners for navigating menus. For panels, the 1199 // escape key may be used to close the panel. However, the ignorekeys 1200 // attribute may be used to disable adding these event listeners for popups 1201 // that want to handle their own keyboard events. 1202 nsAutoString ignorekeys; 1203 aPopup->GetAttr(nsGkAtoms::ignorekeys, ignorekeys); 1204 if (ignorekeys.EqualsLiteral("true")) { 1205 item->SetIgnoreKeys(eIgnoreKeys_True); 1206 } else if (ignorekeys.EqualsLiteral("shortcuts")) { 1207 item->SetIgnoreKeys(eIgnoreKeys_Shortcuts); 1208 } 1209 1210 if (isMenu) { 1211 // if the menu is on a menubar, use the menubar's listener instead 1212 if (auto* menu = aPopupFrame->PopupElement().GetContainingMenu()) { 1213 item->SetOnMenuBar(menu->IsOnMenuBar()); 1214 } 1215 } 1216 1217 // use a weak frame as the popup will set an open attribute if it is a menu 1218 AutoWeakFrame weakFrame(aPopupFrame); 1219 aPopupFrame->ShowPopup(aIsContextMenu); 1220 NS_ENSURE_TRUE_VOID(weakFrame.IsAlive()); 1221 1222 item->UpdateFollowAnchor(); 1223 1224 AddMenuChainItem(std::move(item)); 1225 NS_ENSURE_TRUE_VOID(weakFrame.IsAlive()); 1226 1227 RefPtr popup = &aPopupFrame->PopupElement(); 1228 popup->PopupOpened(aSelectFirstItem); 1229 1230 if (isMenu) { 1231 UpdateMenuItems(aPopup); 1232 } 1233 1234 // Caret visibility may have been affected, ensure that 1235 // the caret isn't now drawn when it shouldn't be. 1236 CheckCaretDrawingState(); 1237 1238 if (popupType != PopupType::Tooltip) { 1239 PointerLockManager::Unlock("ShowPopupCallback"); 1240 } 1241 } 1242 1243 nsMenuChainItem* nsXULPopupManager::FindPopup(Element* aPopup) const { 1244 auto matcher = [&](nsMenuChainItem* aItem) -> bool { 1245 return aItem->Frame()->GetContent() == aPopup; 1246 }; 1247 return FirstMatchingPopup(matcher); 1248 } 1249 1250 void nsXULPopupManager::HidePopup(Element* aPopup, HidePopupOptions aOptions, 1251 Element* aLastPopup) { 1252 if (mPopupQueue) { 1253 mPopupQueue->NotifyDismissed(aPopup); 1254 } 1255 1256 if (mNativeMenu && mNativeMenu->Element() == aPopup) { 1257 RefPtr<NativeMenu> menu = mNativeMenu; 1258 (void)menu->Close(); 1259 return; 1260 } 1261 1262 nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); 1263 if (!popupFrame) { 1264 return; 1265 } 1266 1267 nsMenuChainItem* foundPopup = FindPopup(aPopup); 1268 1269 RefPtr<Element> popupToHide, nextPopup, lastPopup; 1270 1271 if (foundPopup) { 1272 if (foundPopup->IsNoAutoHide()) { 1273 // If this is a noautohide panel, remove it but don't close any other 1274 // panels. 1275 popupToHide = aPopup; 1276 // XXX This preserves behavior but why is it the right thing to do? 1277 aOptions -= HidePopupOption::DeselectMenu; 1278 } else { 1279 // At this point, foundPopup will be set to the found item in the list. If 1280 // foundPopup is the topmost menu, the one to remove, then there are no 1281 // other popups to hide. If foundPopup is not the topmost menu, then there 1282 // may be open submenus below it. In this case, we need to make sure that 1283 // those submenus are closed up first. To do this, we scan up the menu 1284 // list to find the topmost popup with only menus between it and 1285 // foundPopup and close that menu first. In synchronous mode, the 1286 // FirePopupHidingEvent method will be called which in turn calls 1287 // HidePopupCallback to close up the next popup in the chain. These two 1288 // methods will be called in sequence recursively to close up all the 1289 // necessary popups. In asynchronous mode, a similar process occurs except 1290 // that the FirePopupHidingEvent method is called asynchronously. In 1291 // either case, nextPopup is set to the content node of the next popup to 1292 // close, and lastPopup is set to the last popup in the chain to close, 1293 // which will be aPopup, or null to close up all menus. 1294 1295 nsMenuChainItem* topMenu = foundPopup; 1296 // Use IsMenu to ensure that foundPopup is a menu and scan down the child 1297 // list until a non-menu is found. If foundPopup isn't a menu at all, 1298 // don't scan and just close up this menu. 1299 if (foundPopup->IsMenu()) { 1300 nsMenuChainItem* child = foundPopup->GetChild(); 1301 while (child && child->IsMenu()) { 1302 topMenu = child; 1303 child = child->GetChild(); 1304 } 1305 } 1306 1307 popupToHide = topMenu->Element(); 1308 popupFrame = topMenu->Frame(); 1309 1310 const bool hideChain = aOptions.contains(HidePopupOption::HideChain); 1311 1312 // Close up another popup if there is one, and we are either hiding the 1313 // entire chain or the item to hide isn't the topmost popup. 1314 nsMenuChainItem* parent = topMenu->GetParent(); 1315 if (parent && (hideChain || topMenu != foundPopup)) { 1316 while (parent && parent->IsNoAutoHide()) { 1317 parent = parent->GetParent(); 1318 } 1319 1320 if (parent) { 1321 nextPopup = parent->Element(); 1322 } 1323 } 1324 1325 lastPopup = aLastPopup ? aLastPopup : (hideChain ? nullptr : aPopup); 1326 } 1327 } else if (popupFrame->PopupState() == ePopupPositioning) { 1328 // When the popup is in the popuppositioning state, it will not be in the 1329 // mPopups list. We need another way to find it and make sure it does not 1330 // continue the popup showing process. 1331 popupToHide = aPopup; 1332 } 1333 1334 if (!popupToHide) { 1335 return; 1336 } 1337 1338 nsPopupState state = popupFrame->PopupState(); 1339 if (state == ePopupHiding) { 1340 // If the popup is already being hidden, don't fire another popuphiding 1341 // event. But finish hiding it sync if we need to. 1342 if (aOptions.contains(HidePopupOption::DisableAnimations) && 1343 !aOptions.contains(HidePopupOption::Async)) { 1344 HidePopupCallback(popupToHide, popupFrame, nullptr, nullptr, 1345 popupFrame->GetPopupType(), aOptions); 1346 } 1347 return; 1348 } 1349 1350 // Change the popup state to hiding. Don't set the hiding state if the 1351 // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will 1352 // run again. In the invisible state, we just want the events to fire. 1353 if (state != ePopupInvisible) { 1354 popupFrame->SetPopupState(ePopupHiding); 1355 } 1356 1357 // For menus, popupToHide is always the frontmost item in the list to hide. 1358 if (aOptions.contains(HidePopupOption::Async)) { 1359 nsCOMPtr<nsIRunnable> event = 1360 new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup, 1361 popupFrame->GetPopupType(), aOptions); 1362 aPopup->OwnerDoc()->Dispatch(event.forget()); 1363 } else { 1364 RefPtr<nsPresContext> presContext = popupFrame->PresContext(); 1365 FirePopupHidingEvent(popupToHide, nextPopup, lastPopup, presContext, 1366 popupFrame->GetPopupType(), aOptions); 1367 } 1368 } 1369 1370 void nsXULPopupManager::HideMenu(nsIContent* aMenu) { 1371 if (mNativeMenu && aMenu->IsElement() && 1372 mNativeMenu->Element()->Contains(aMenu)) { 1373 mNativeMenu->CloseSubmenu(aMenu->AsElement()); 1374 return; 1375 } 1376 1377 auto* button = XULButtonElement::FromNode(aMenu); 1378 if (!button || !button->IsMenu()) { 1379 return; 1380 } 1381 auto* popup = button->GetMenuPopupContent(); 1382 if (!popup) { 1383 return; 1384 } 1385 HidePopup(popup, {HidePopupOption::DeselectMenu}); 1386 } 1387 1388 // This is used to hide the popup after a transition finishes. 1389 class TransitionEnder final : public nsIDOMEventListener { 1390 private: 1391 // Effectively const but is cycle collected 1392 MOZ_KNOWN_LIVE RefPtr<Element> mElement; 1393 1394 protected: 1395 virtual ~TransitionEnder() = default; 1396 1397 public: 1398 HidePopupOptions mOptions; 1399 1400 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 1401 NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder) 1402 1403 TransitionEnder(Element* aElement, HidePopupOptions aOptions) 1404 : mElement(aElement), mOptions(aOptions) {} 1405 1406 MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override { 1407 mElement->RemoveSystemEventListener(u"transitionend"_ns, this, false); 1408 mElement->RemoveSystemEventListener(u"transitioncancel"_ns, this, false); 1409 1410 nsMenuPopupFrame* popupFrame = do_QueryFrame(mElement->GetPrimaryFrame()); 1411 if (!popupFrame || popupFrame->PopupState() != ePopupHiding) { 1412 return NS_OK; 1413 } 1414 1415 // Now hide the popup. There could be other properties transitioning, but 1416 // we'll assume they all end at the same time and just hide the popup upon 1417 // the first one ending. 1418 if (RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance()) { 1419 pm->HidePopupCallback(mElement, popupFrame, nullptr, nullptr, 1420 popupFrame->GetPopupType(), mOptions); 1421 } 1422 1423 return NS_OK; 1424 } 1425 }; 1426 1427 NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder) 1428 NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder) 1429 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder) 1430 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) 1431 NS_INTERFACE_MAP_ENTRY(nsISupports) 1432 NS_INTERFACE_MAP_END 1433 1434 NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mElement); 1435 void nsXULPopupManager::HidePopupCallback( 1436 Element* aPopup, nsMenuPopupFrame* aPopupFrame, Element* aNextPopup, 1437 Element* aLastPopup, PopupType aPopupType, HidePopupOptions aOptions) { 1438 if (mCloseTimer && mTimerMenu == aPopupFrame) { 1439 mCloseTimer->Cancel(); 1440 mCloseTimer = nullptr; 1441 mTimerMenu = nullptr; 1442 } 1443 1444 // The popup to hide is aPopup. Search the list again to find the item that 1445 // corresponds to the popup to hide aPopup. This is done because it's 1446 // possible someone added another item (attempted to open another popup) 1447 // or removed a popup frame during the event processing so the item isn't at 1448 // the front anymore. 1449 for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) { 1450 if (item->Element() == aPopup) { 1451 RemoveMenuChainItem(item); 1452 SetCaptureState(aPopup); 1453 break; 1454 } 1455 } 1456 1457 AutoWeakFrame weakFrame(aPopupFrame); 1458 aPopupFrame->HidePopup(aOptions.contains(HidePopupOption::DeselectMenu), 1459 ePopupClosed); 1460 NS_ENSURE_TRUE_VOID(weakFrame.IsAlive()); 1461 1462 // send the popuphidden event synchronously. This event has no default 1463 // behaviour. 1464 nsEventStatus status = nsEventStatus_eIgnore; 1465 WidgetMouseEvent event(true, eXULPopupHidden, nullptr, 1466 WidgetMouseEvent::eReal); 1467 RefPtr<nsPresContext> presContext = aPopupFrame->PresContext(); 1468 EventDispatcher::Dispatch(aPopup, presContext, &event, nullptr, &status); 1469 NS_ENSURE_TRUE_VOID(weakFrame.IsAlive()); 1470 1471 // Force any popups that might be anchored on elements within this popup to 1472 // update. 1473 UpdatePopupPositions(presContext->RefreshDriver()); 1474 1475 // if there are more popups to close, look for the next one 1476 if (aNextPopup && aPopup != aLastPopup) { 1477 nsMenuChainItem* foundMenu = FindPopup(aNextPopup); 1478 1479 // continue hiding the chain of popups until the last popup aLastPopup 1480 // is reached, or until a popup of a different type is reached. This 1481 // last check is needed so that a menulist inside a non-menu panel only 1482 // closes the menu and not the panel as well. 1483 if (foundMenu && (aLastPopup || aPopupType == foundMenu->GetPopupType())) { 1484 nsCOMPtr<Element> popupToHide = foundMenu->Element(); 1485 nsMenuChainItem* parent = foundMenu->GetParent(); 1486 1487 nsCOMPtr<Element> nextPopup; 1488 if (parent && popupToHide != aLastPopup) { 1489 nextPopup = parent->Element(); 1490 } 1491 1492 nsMenuPopupFrame* popupFrame = foundMenu->Frame(); 1493 nsPopupState state = popupFrame->PopupState(); 1494 if (state == ePopupHiding) { 1495 return; 1496 } 1497 if (state != ePopupInvisible) { 1498 popupFrame->SetPopupState(ePopupHiding); 1499 } 1500 1501 RefPtr<nsPresContext> presContext = popupFrame->PresContext(); 1502 FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup, presContext, 1503 foundMenu->GetPopupType(), aOptions); 1504 } 1505 } 1506 } 1507 1508 void nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup, 1509 int32_t aDelay) { 1510 // Don't close up immediately. 1511 // Kick off a close timer. 1512 KillMenuTimer(); 1513 1514 // Kick off the timer. 1515 nsIEventTarget* target = GetMainThreadSerialEventTarget(); 1516 NS_NewTimerWithFuncCallback( 1517 getter_AddRefs(mCloseTimer), 1518 [](nsITimer* aTimer, void* aClosure) { 1519 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { 1520 pm->KillMenuTimer(); 1521 } 1522 }, 1523 nullptr, aDelay, nsITimer::TYPE_ONE_SHOT, "KillMenuTimer"_ns, target); 1524 // the popup will call PopupDestroyed if it is destroyed, which checks if it 1525 // is set to mTimerMenu, so it should be safe to keep a reference to it 1526 mTimerMenu = aPopup; 1527 } 1528 1529 void nsXULPopupManager::HidePopupsInList( 1530 const nsTArray<nsMenuPopupFrame*>& aFrames) { 1531 // Create a weak frame list. This is done in a separate array with the 1532 // right capacity predetermined to avoid multiple allocations. 1533 nsTArray<WeakFrame> weakPopups(aFrames.Length()); 1534 uint32_t f; 1535 for (f = 0; f < aFrames.Length(); f++) { 1536 WeakFrame* wframe = weakPopups.AppendElement(); 1537 if (wframe) { 1538 *wframe = aFrames[f]; 1539 } 1540 } 1541 1542 for (f = 0; f < weakPopups.Length(); f++) { 1543 // check to ensure that the frame is still alive before hiding it. 1544 if (weakPopups[f].IsAlive()) { 1545 auto* frame = static_cast<nsMenuPopupFrame*>(weakPopups[f].GetFrame()); 1546 frame->HidePopup(true, ePopupInvisible); 1547 } 1548 } 1549 1550 SetCaptureState(nullptr); 1551 } 1552 1553 bool nsXULPopupManager::IsChildOfDocShell(Document* aDoc, 1554 nsIDocShellTreeItem* aExpected) { 1555 nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell()); 1556 while (docShellItem) { 1557 if (docShellItem == aExpected) { 1558 return true; 1559 } 1560 1561 nsCOMPtr<nsIDocShellTreeItem> parent; 1562 docShellItem->GetInProcessParent(getter_AddRefs(parent)); 1563 docShellItem = parent; 1564 } 1565 1566 return false; 1567 } 1568 1569 void nsXULPopupManager::HidePopupsInDocShell( 1570 nsIDocShellTreeItem* aDocShellToHide) { 1571 nsTArray<nsMenuPopupFrame*> popupsToHide; 1572 1573 // Iterate to get the set of popup frames to hide 1574 nsMenuChainItem* item = mPopups.get(); 1575 while (item) { 1576 // Get the parent before calling detach so that we can keep iterating. 1577 nsMenuChainItem* parent = item->GetParent(); 1578 if (item->Frame()->PopupState() != ePopupInvisible && 1579 IsChildOfDocShell(item->Element()->OwnerDoc(), aDocShellToHide)) { 1580 nsMenuPopupFrame* frame = item->Frame(); 1581 RemoveMenuChainItem(item); 1582 popupsToHide.AppendElement(frame); 1583 } 1584 item = parent; 1585 } 1586 1587 HidePopupsInList(popupsToHide); 1588 } 1589 1590 void nsXULPopupManager::UpdatePopupPositions(nsRefreshDriver* aRefreshDriver) { 1591 for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) { 1592 nsMenuPopupFrame* frame = item->Frame(); 1593 if (frame->PresContext()->RefreshDriver() != aRefreshDriver) { 1594 continue; 1595 } 1596 item->CheckForAnchorChange(); 1597 } 1598 } 1599 1600 void nsXULPopupManager::PaintPopups(nsRefreshDriver* aRefreshDriver) { 1601 if (!mPopups) { 1602 return; 1603 } 1604 1605 AutoTArray<std::pair<RefPtr<nsIWidget>, WeakFrame>, 32> visiblePopups; 1606 for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) { 1607 nsMenuPopupFrame* frame = item->Frame(); 1608 if (!frame->IsVisible() || 1609 frame->PresContext()->GetRootPresContext()->RefreshDriver() != 1610 aRefreshDriver) { 1611 continue; 1612 } 1613 if (nsIWidget* widget = frame->GetWidget()) { 1614 visiblePopups.AppendElement(std::make_pair(widget, frame)); 1615 } 1616 } 1617 1618 for (const auto& visiblePopup : Reversed(visiblePopups)) { 1619 nsIWidget* widget = visiblePopup.first; 1620 nsMenuPopupFrame* frame = do_QueryFrame(visiblePopup.second.GetFrame()); 1621 if (!frame) { 1622 continue; 1623 } 1624 if (frame->PendingWidgetMoveResize()) { 1625 frame->ClearPendingWidgetMoveResize(); 1626 1627 LayoutDeviceIntRect curBounds = widget->GetClientBounds(); 1628 auto newBounds = frame->CalcWidgetBounds(); 1629 widget->ConstrainSize(&newBounds.width, &newBounds.height); 1630 const bool changedPos = curBounds.TopLeft() != newBounds.TopLeft(); 1631 const bool changedSize = curBounds.Size() != newBounds.Size(); 1632 1633 if (changedPos || changedSize) { 1634 DesktopToLayoutDeviceScale scale = widget->GetDesktopToDeviceScale(); 1635 DesktopRect deskRect = newBounds / scale; 1636 if (changedPos) { 1637 if (changedSize) { 1638 widget->ResizeClient(deskRect, true); 1639 } else { 1640 widget->MoveClient(deskRect.TopLeft()); 1641 } 1642 } else if (changedSize) { 1643 widget->ResizeClient(deskRect.Size(), true); 1644 } 1645 } 1646 } 1647 if (!widget->IsVisible()) { 1648 widget->Show(true); 1649 } 1650 if (!visiblePopup.second.IsAlive() || !widget->NeedsPaint()) { 1651 continue; 1652 } 1653 nsAutoScriptBlocker scriptBlocker; 1654 RefPtr<PresShell> ps = frame->PresShell(); 1655 RefPtr<WindowRenderer> renderer = widget->GetWindowRenderer(); 1656 if (renderer->AsFallback()) { 1657 // FIXME: A bit of a hack. This matches what PaintAndRequestComposite 1658 // does for views (eventually). 1659 widget->Invalidate(LayoutDeviceIntRect({}, widget->GetBounds().Size())); 1660 } else { 1661 ps->PaintAndRequestComposite(frame, renderer, PaintFlags::None); 1662 } 1663 } 1664 } 1665 1666 void nsXULPopupManager::UpdateFollowAnchor(nsMenuPopupFrame* aPopup) { 1667 for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) { 1668 if (item->Frame() == aPopup) { 1669 item->UpdateFollowAnchor(); 1670 break; 1671 } 1672 } 1673 } 1674 1675 void nsXULPopupManager::HideOpenMenusBeforeExecutingMenu(CloseMenuMode aMode) { 1676 if (aMode == CloseMenuMode_None) { 1677 return; 1678 } 1679 1680 // When a menuitem is selected to be executed, first hide all the open 1681 // popups, but don't remove them yet. This is needed when a menu command 1682 // opens a modal dialog. The views associated with the popups needed to be 1683 // hidden and the accesibility events fired before the command executes, but 1684 // the popuphiding/popuphidden events are fired afterwards. 1685 nsTArray<nsMenuPopupFrame*> popupsToHide; 1686 nsMenuChainItem* item = GetTopVisibleMenu(); 1687 while (item) { 1688 // if it isn't a <menupopup>, don't close it automatically 1689 if (!item->IsMenu()) { 1690 break; 1691 } 1692 1693 nsMenuChainItem* next = item->GetParent(); 1694 popupsToHide.AppendElement(item->Frame()); 1695 if (aMode == CloseMenuMode_Single) { 1696 // only close one level of menu 1697 break; 1698 } 1699 item = next; 1700 } 1701 1702 // Now hide the popups. If the closemenu mode is auto, deselect the menu, 1703 // otherwise only one popup is closing, so keep the parent menu selected. 1704 HidePopupsInList(popupsToHide); 1705 } 1706 1707 void nsXULPopupManager::ExecuteMenu(nsIContent* aMenu, 1708 nsXULMenuCommandEvent* aEvent) { 1709 CloseMenuMode cmm = GetCloseMenuMode(aMenu); 1710 HideOpenMenusBeforeExecutingMenu(cmm); 1711 aEvent->SetCloseMenuMode(cmm); 1712 nsCOMPtr<nsIRunnable> event = aEvent; 1713 aMenu->OwnerDoc()->Dispatch(event.forget()); 1714 } 1715 1716 bool nsXULPopupManager::ActivateNativeMenuItem(nsIContent* aItem, 1717 mozilla::Modifiers aModifiers, 1718 int16_t aButton, 1719 mozilla::ErrorResult& aRv) { 1720 if (mNativeMenu && aItem->IsElement() && 1721 mNativeMenu->Element()->Contains(aItem)) { 1722 mNativeMenu->ActivateItem(aItem->AsElement(), aModifiers, aButton, aRv); 1723 return true; 1724 } 1725 return false; 1726 } 1727 1728 nsEventStatus nsXULPopupManager::FirePopupShowingEvent( 1729 const PendingPopup& aPendingPopup, nsPresContext* aPresContext) { 1730 // Cache the pending popup so that the trigger node and other properties can 1731 // be retrieved during the popupshowing event. It will be cleared below after 1732 // the event has fired. 1733 AutoRestore<const PendingPopup*> restorePendingPopup(mPendingPopup); 1734 mPendingPopup = &aPendingPopup; 1735 1736 nsEventStatus status = nsEventStatus_eIgnore; 1737 WidgetMouseEvent event(true, eXULPopupShowing, nullptr, 1738 WidgetMouseEvent::eReal); 1739 1740 // coordinates are relative to the root widget 1741 nsPresContext* rootPresContext = aPresContext->GetRootPresContext(); 1742 if (rootPresContext) { 1743 event.mWidget = rootPresContext->GetRootWidget(); 1744 } else { 1745 event.mWidget = nullptr; 1746 } 1747 1748 event.mInputSource = aPendingPopup.MouseInputSource(); 1749 event.mRefPoint = aPendingPopup.mMousePoint; 1750 event.mModifiers = aPendingPopup.mModifiers; 1751 if (aPendingPopup.mEvent) { 1752 event.mTriggerEvent = aPendingPopup.mEvent; 1753 } 1754 RefPtr<nsIContent> popup = aPendingPopup.mPopup; 1755 EventDispatcher::Dispatch(popup, aPresContext, &event, nullptr, &status); 1756 1757 return status; 1758 } 1759 1760 bool nsXULPopupManager::BeginShowingPopup(const PendingPopup& aPendingPopup, 1761 bool aIsContextMenu, 1762 bool aSelectFirstItem) { 1763 RefPtr<Element> popup = aPendingPopup.mPopup; 1764 1765 nsMenuPopupFrame* popupFrame = do_QueryFrame(popup->GetPrimaryFrame()); 1766 if (NS_WARN_IF(!popupFrame)) { 1767 return false; 1768 } 1769 1770 RefPtr<nsPresContext> presContext = popupFrame->PresContext(); 1771 RefPtr<PresShell> presShell = presContext->PresShell(); 1772 presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::FrameAndAncestors, 1773 NS_FRAME_IS_DIRTY); 1774 1775 PopupType popupType = popupFrame->GetPopupType(); 1776 1777 ToggleTouchMode(aPendingPopup); 1778 1779 nsEventStatus status = FirePopupShowingEvent(aPendingPopup, presContext); 1780 1781 // if a panel, blur whatever has focus so that the panel can take the focus. 1782 // This is done after the popupshowing event in case that event is cancelled. 1783 // Using noautofocus="true" will disable this behaviour, which is needed for 1784 // the autocomplete widget as it manages focus itself. 1785 if (popupType == PopupType::Panel && 1786 !popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus, 1787 nsGkAtoms::_true, eCaseMatters)) { 1788 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { 1789 Document* doc = popup->GetUncomposedDoc(); 1790 1791 // Only remove the focus if the currently focused item is ouside the 1792 // popup. It isn't a big deal if the current focus is in a child popup 1793 // inside the popup as that shouldn't be visible. This check ensures that 1794 // a node inside the popup that is focused during a popupshowing event 1795 // remains focused. 1796 RefPtr<Element> currentFocus = fm->GetFocusedElement(); 1797 if (doc && currentFocus && 1798 !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) { 1799 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = doc->GetWindow(); 1800 fm->ClearFocus(outerWindow); 1801 } 1802 } 1803 } 1804 1805 popup->OwnerDoc()->FlushPendingNotifications(FlushType::Frames); 1806 1807 // get the frame again in case it went away 1808 popupFrame = do_QueryFrame(popup->GetPrimaryFrame()); 1809 if (!popupFrame) { 1810 return false; 1811 } 1812 // if the event was cancelled or the popup was closed in the mean time, don't 1813 // open the popup, reset its state back to closed and clear its trigger 1814 // content. 1815 if (popupFrame->PopupState() == ePopupClosed || 1816 status == nsEventStatus_eConsumeNoDefault) { 1817 popupFrame->SetPopupState(ePopupClosed); 1818 popupFrame->ClearTriggerContent(); 1819 return false; 1820 } 1821 // Now check if we need to fire the popuppositioned event. If not, call 1822 // ShowPopupCallback directly. 1823 // The popuppositioned event only fires on arrow panels for now. 1824 if (popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::arrow, 1825 eCaseMatters)) { 1826 popupFrame->ShowWithPositionedEvent(); 1827 presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::FrameAndAncestors, 1828 NS_FRAME_HAS_DIRTY_CHILDREN); 1829 } else { 1830 ShowPopupCallback(popup, popupFrame, aIsContextMenu, aSelectFirstItem); 1831 } 1832 1833 return true; 1834 } 1835 1836 void nsXULPopupManager::FirePopupHidingEvent(Element* aPopup, 1837 Element* aNextPopup, 1838 Element* aLastPopup, 1839 nsPresContext* aPresContext, 1840 PopupType aPopupType, 1841 HidePopupOptions aOptions) { 1842 nsCOMPtr<nsIContent> popup = aPopup; 1843 RefPtr<PresShell> presShell = aPresContext->PresShell(); 1844 (void)presShell; // This presShell may be keeping things alive 1845 // on non GTK platforms 1846 1847 nsEventStatus status = nsEventStatus_eIgnore; 1848 WidgetMouseEvent event(true, eXULPopupHiding, nullptr, 1849 WidgetMouseEvent::eReal); 1850 EventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status); 1851 1852 // when a panel is closed, blur whatever has focus inside the popup 1853 if (aPopupType == PopupType::Panel && 1854 (!aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus, 1855 nsGkAtoms::_true, eCaseMatters))) { 1856 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { 1857 Document* doc = aPopup->GetUncomposedDoc(); 1858 1859 // Remove the focus from the focused node only if it is inside the popup. 1860 RefPtr<Element> currentFocus = fm->GetFocusedElement(); 1861 if (doc && currentFocus && 1862 nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) { 1863 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = doc->GetWindow(); 1864 fm->ClearFocus(outerWindow); 1865 } 1866 } 1867 } 1868 1869 aPopup->OwnerDoc()->FlushPendingNotifications(FlushType::Frames); 1870 1871 // get frame again in case it went away 1872 nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); 1873 if (!popupFrame) { 1874 return; 1875 } 1876 1877 // If the event was cancelled, don't hide the popup, and reset its 1878 // state back to open. Only popups in chrome shells can prevent a popup 1879 // from hiding. 1880 if (status == nsEventStatus_eConsumeNoDefault && 1881 !popupFrame->IsInContentShell()) { 1882 // XXXndeakin 1883 // If an attempt was made to hide this popup before the popupshown event 1884 // fired, then ePopupShown is set here even though it should be 1885 // ePopupVisible. This probably isn't worth the hassle of handling. 1886 popupFrame->SetPopupState(ePopupShown); 1887 return; 1888 } 1889 1890 const bool shouldAnimate = [&] { 1891 if (!LookAndFeel::GetInt(LookAndFeel::IntID::PanelAnimations)) { 1892 // Animations are not supported by the platform, avoid transitioning. 1893 return false; 1894 } 1895 if (aOptions.contains(HidePopupOption::DisableAnimations)) { 1896 // Animations are not allowed by our caller. 1897 return false; 1898 } 1899 if (aNextPopup) { 1900 // If there is a next popup, indicating that mutliple popups are rolling 1901 // up, don't wait and hide the popup right away since the effect would 1902 // likely be undesirable. 1903 return false; 1904 } 1905 nsAutoString animate; 1906 if (!aPopup->GetAttr(nsGkAtoms::animate, animate)) { 1907 return false; 1908 } 1909 // If animate="false" then don't transition at all. 1910 if (animate.EqualsLiteral("false")) { 1911 return false; 1912 } 1913 // If animate="cancel", only show the transition if cancelling the popup 1914 // or rolling up. 1915 if (animate.EqualsLiteral("cancel") && 1916 !aOptions.contains(HidePopupOption::IsRollup)) { 1917 return false; 1918 } 1919 return true; 1920 }(); 1921 // If we should animate the popup, check if it has a closing transition 1922 // and wait for it to finish. 1923 // The transition would still occur either way, but if we don't wait the 1924 // view will be hidden and you won't be able to see it. 1925 if (shouldAnimate && AnimationUtils::HasCurrentTransitions(aPopup)) { 1926 RefPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aOptions); 1927 aPopup->AddSystemEventListener(u"transitionend"_ns, ender, false, false); 1928 aPopup->AddSystemEventListener(u"transitioncancel"_ns, ender, false, false); 1929 return; 1930 } 1931 1932 HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup, aPopupType, 1933 aOptions); 1934 } 1935 1936 bool nsXULPopupManager::IsPopupOpen(Element* aPopup) { 1937 if (mNativeMenu && mNativeMenu->Element() == aPopup) { 1938 return true; 1939 } 1940 1941 // a popup is open if it is in the open list. The assertions ensure that the 1942 // frame is in the correct state. If the popup is in the hiding or invisible 1943 // state, it will still be in the open popup list until it is closed. 1944 if (nsMenuChainItem* item = FindPopup(aPopup)) { 1945 NS_ASSERTION(item->Frame()->IsOpen() || 1946 item->Frame()->PopupState() == ePopupHiding || 1947 item->Frame()->PopupState() == ePopupInvisible, 1948 "popup in open list not actually open"); 1949 (void)item; 1950 return true; 1951 } 1952 return false; 1953 } 1954 1955 nsIFrame* nsXULPopupManager::GetTopPopup(PopupType aType) { 1956 for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) { 1957 if (item->Frame()->IsVisible() && 1958 (item->GetPopupType() == aType || aType == PopupType::Any)) { 1959 return item->Frame(); 1960 } 1961 } 1962 return nullptr; 1963 } 1964 1965 nsIContent* nsXULPopupManager::GetTopActiveMenuItemContent() { 1966 for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) { 1967 if (!item->Frame()->IsVisible()) { 1968 continue; 1969 } 1970 if (auto* content = item->Frame()->PopupElement().GetActiveMenuChild()) { 1971 return content; 1972 } 1973 } 1974 return nullptr; 1975 } 1976 1977 void nsXULPopupManager::GetVisiblePopups(nsTArray<nsMenuPopupFrame*>& aPopups, 1978 bool aIncludeNativeMenu) { 1979 aPopups.Clear(); 1980 if (aIncludeNativeMenu && mNativeMenu) { 1981 nsCOMPtr<nsIContent> popup = mNativeMenu->Element(); 1982 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(popup, true); 1983 if (popupFrame && popupFrame->IsVisible() && 1984 !popupFrame->IsMouseTransparent()) { 1985 aPopups.AppendElement(popupFrame); 1986 } 1987 } 1988 for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) { 1989 // Skip panels which are not visible as well as popups that are transparent 1990 // to mouse events. 1991 if (item->Frame()->IsVisible() && !item->Frame()->IsMouseTransparent()) { 1992 aPopups.AppendElement(item->Frame()); 1993 } 1994 } 1995 } 1996 1997 already_AddRefed<nsINode> nsXULPopupManager::GetLastTriggerNode( 1998 Document* aDocument, bool aIsTooltip) { 1999 if (!aDocument) { 2000 return nullptr; 2001 } 2002 2003 RefPtr<nsINode> node; 2004 2005 // If a pending popup is set, it means that a popupshowing event is being 2006 // fired. In this case, just use the cached node, as the popup is not yet in 2007 // the list of open popups. 2008 RefPtr<nsIContent> openingPopup = 2009 mPendingPopup ? mPendingPopup->mPopup : nullptr; 2010 if (openingPopup && openingPopup->GetUncomposedDoc() == aDocument && 2011 aIsTooltip == openingPopup->IsXULElement(nsGkAtoms::tooltip)) { 2012 node = nsMenuPopupFrame::GetTriggerContent( 2013 GetPopupFrameForContent(openingPopup, false)); 2014 } else if (mNativeMenu && !aIsTooltip) { 2015 RefPtr<dom::Element> popup = mNativeMenu->Element(); 2016 if (popup->GetUncomposedDoc() == aDocument) { 2017 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(popup, false); 2018 node = nsMenuPopupFrame::GetTriggerContent(popupFrame); 2019 } 2020 } else { 2021 for (nsMenuChainItem* item = mPopups.get(); item; 2022 item = item->GetParent()) { 2023 // look for a popup of the same type and document. 2024 if ((item->GetPopupType() == PopupType::Tooltip) == aIsTooltip && 2025 item->Element()->GetUncomposedDoc() == aDocument) { 2026 node = nsMenuPopupFrame::GetTriggerContent(item->Frame()); 2027 if (node) { 2028 break; 2029 } 2030 } 2031 } 2032 } 2033 2034 return node.forget(); 2035 } 2036 2037 bool nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup) { 2038 // if a popup's IsOpen method returns true, then the popup must always be in 2039 // the popup chain scanned in IsPopupOpen. 2040 NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(&aPopup->PopupElement()), 2041 "popup frame state doesn't match XULPopupManager open state"); 2042 2043 nsPopupState state = aPopup->PopupState(); 2044 2045 // if the popup is not in the open popup chain, then it must have a state that 2046 // is either closed, in the process of being shown, or invisible. 2047 NS_ASSERTION(IsPopupOpen(&aPopup->PopupElement()) || state == ePopupClosed || 2048 state == ePopupShowing || state == ePopupPositioning || 2049 state == ePopupInvisible, 2050 "popup not in XULPopupManager open list is open"); 2051 2052 // don't show popups unless they are closed or invisible 2053 if (state != ePopupClosed && state != ePopupInvisible) { 2054 return false; 2055 } 2056 2057 // Don't show popups that we already have in our popup chain 2058 if (IsPopupOpen(&aPopup->PopupElement())) { 2059 NS_WARNING("Refusing to show duplicate popup"); 2060 return false; 2061 } 2062 2063 // if the popup was just rolled up, don't reopen it 2064 if (mozilla::widget::nsAutoRollup::GetLastRollup() == aPopup->GetContent()) { 2065 return false; 2066 } 2067 2068 nsCOMPtr<nsIDocShell> docShell = aPopup->PresContext()->GetDocShell(); 2069 2070 nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(docShell); 2071 if (!baseWin) { 2072 return false; 2073 } 2074 2075 nsCOMPtr<nsIDocShellTreeItem> root; 2076 docShell->GetInProcessRootTreeItem(getter_AddRefs(root)); 2077 if (!root) { 2078 return false; 2079 } 2080 2081 nsCOMPtr<nsPIDOMWindowOuter> rootWin = root->GetWindow(); 2082 2083 MOZ_RELEASE_ASSERT(XRE_IsParentProcess(), 2084 "Cannot have XUL in content process showing popups."); 2085 2086 // chrome shells can always open popups, but other types of shells can only 2087 // open popups when they are focused and visible 2088 if (docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { 2089 // only allow popups in active windows 2090 nsFocusManager* fm = nsFocusManager::GetFocusManager(); 2091 if (!fm || !rootWin) { 2092 return false; 2093 } 2094 2095 nsCOMPtr<nsPIDOMWindowOuter> activeWindow = fm->GetActiveWindow(); 2096 if (activeWindow != rootWin) { 2097 return false; 2098 } 2099 2100 // only allow popups in visible frames 2101 // TODO: This visibility check should be replaced with a check of 2102 // bc->IsActive(). It is okay for now since this is only called 2103 // in the parent process. Bug 1698533. 2104 bool visible; 2105 baseWin->GetVisibility(&visible); 2106 if (!visible) { 2107 return false; 2108 } 2109 } 2110 2111 // platforms respond differently when an popup is opened in a minimized 2112 // window, so this is always disabled. 2113 nsCOMPtr<nsIWidget> mainWidget = baseWin->GetMainWidget(); 2114 if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) { 2115 return false; 2116 } 2117 2118 #ifdef XP_MACOSX 2119 if (rootWin) { 2120 auto globalWin = nsGlobalWindowOuter::Cast(rootWin.get()); 2121 if (globalWin->IsInModalState()) { 2122 return false; 2123 } 2124 } 2125 #endif 2126 2127 // cannot open a popup that is a submenu of a menupopup that isn't open. 2128 if (auto* menu = aPopup->PopupElement().GetContainingMenu()) { 2129 if (auto* parent = XULPopupElement::FromNodeOrNull(menu->GetMenuParent())) { 2130 nsMenuPopupFrame* f = do_QueryFrame(parent->GetPrimaryFrame()); 2131 if (f && !f->IsOpen()) { 2132 return false; 2133 } 2134 } 2135 } 2136 2137 return true; 2138 } 2139 2140 void nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup) { 2141 if (mPopupQueue) { 2142 mPopupQueue->NotifyDismissed(&aPopup->PopupElement(), true); 2143 } 2144 2145 // when a popup frame is destroyed, just unhook it from the list of popups 2146 CancelMenuTimer(aPopup); 2147 2148 nsMenuChainItem* item = FindPopup(&aPopup->PopupElement()); 2149 if (!item) { 2150 return; 2151 } 2152 2153 nsTArray<nsMenuPopupFrame*> popupsToHide; 2154 // XXXndeakin shouldn't this only happen for menus? 2155 if (!item->IsNoAutoHide() && item->Frame()->PopupState() != ePopupInvisible) { 2156 // Iterate through any child menus and hide them as well, since the 2157 // parent is going away. We won't remove them from the list yet, just 2158 // hide them, as they will be removed from the list when this function 2159 // gets called for that child frame. 2160 for (auto* child = item->GetChild(); child; child = child->GetChild()) { 2161 // If the popup is a child frame of the menu that was destroyed, add it 2162 // to the list of popups to hide. Don't bother with the events since the 2163 // frames are going away. If the child menu is not a child frame, for 2164 // example, a context menu, use HidePopup instead, but call it 2165 // asynchronously since we are in the middle of frame destruction. 2166 if (nsLayoutUtils::IsProperAncestorFrame(item->Frame(), child->Frame())) { 2167 popupsToHide.AppendElement(child->Frame()); 2168 } else { 2169 // HidePopup will take care of hiding any of its children, so 2170 // break out afterwards 2171 HidePopup(child->Element(), {HidePopupOption::Async}); 2172 break; 2173 } 2174 } 2175 } 2176 2177 RemoveMenuChainItem(item); 2178 HidePopupsInList(popupsToHide); 2179 } 2180 2181 bool nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup) { 2182 nsMenuChainItem* item = GetTopVisibleMenu(); 2183 while (item && item->Frame() != aPopup) { 2184 if (item->IsContextMenu()) { 2185 return true; 2186 } 2187 item = item->GetParent(); 2188 } 2189 2190 return false; 2191 } 2192 2193 void nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup) { 2194 nsMenuChainItem* item = GetTopVisibleMenu(); 2195 if (item && aOldPopup == item->Element()) { 2196 return; 2197 } 2198 2199 if (mWidget) { 2200 mWidget->CaptureRollupEvents(false); 2201 mWidget = nullptr; 2202 } 2203 2204 if (item) { 2205 nsMenuPopupFrame* popup = item->Frame(); 2206 mWidget = popup->GetWidget(); 2207 if (mWidget) { 2208 mWidget->CaptureRollupEvents(true); 2209 } 2210 } 2211 2212 UpdateKeyboardListeners(); 2213 } 2214 2215 void nsXULPopupManager::UpdateKeyboardListeners() { 2216 nsCOMPtr<EventTarget> newTarget; 2217 bool isForMenu = false; 2218 if (nsMenuChainItem* item = GetTopVisibleMenu()) { 2219 if (item->IgnoreKeys() != eIgnoreKeys_True) { 2220 newTarget = item->Element()->GetComposedDoc(); 2221 } 2222 isForMenu = item->GetPopupType() == PopupType::Menu; 2223 } else if (mActiveMenuBar && mActiveMenuBar->IsActiveByKeyboard()) { 2224 // Only listen for key events iff menubar is activated via key, see 2225 // bug 1818241. 2226 newTarget = mActiveMenuBar->GetComposedDoc(); 2227 isForMenu = true; 2228 } 2229 2230 if (mKeyListener != newTarget) { 2231 OwningNonNull<nsXULPopupManager> kungFuDeathGrip(*this); 2232 if (mKeyListener) { 2233 mKeyListener->RemoveEventListener(u"keypress"_ns, this, true); 2234 mKeyListener->RemoveEventListener(u"keydown"_ns, this, true); 2235 mKeyListener->RemoveEventListener(u"keyup"_ns, this, true); 2236 mKeyListener = nullptr; 2237 nsContentUtils::NotifyInstalledMenuKeyboardListener(false); 2238 } 2239 2240 if (newTarget) { 2241 newTarget->AddEventListener(u"keypress"_ns, this, true); 2242 newTarget->AddEventListener(u"keydown"_ns, this, true); 2243 newTarget->AddEventListener(u"keyup"_ns, this, true); 2244 nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu); 2245 mKeyListener = newTarget; 2246 } 2247 } 2248 } 2249 2250 void nsXULPopupManager::UpdateMenuItems(Element* aPopup) { 2251 // Walk all of the menu's children, checking to see if any of them has a 2252 // command attribute. If so, then several attributes must potentially be 2253 // updated. 2254 2255 nsCOMPtr<Document> document = aPopup->GetUncomposedDoc(); 2256 if (!document) { 2257 return; 2258 } 2259 2260 // When a menu is opened, make sure that command updating is unlocked first. 2261 nsCOMPtr<nsIDOMXULCommandDispatcher> commandDispatcher = 2262 document->GetCommandDispatcher(); 2263 if (commandDispatcher) { 2264 commandDispatcher->Unlock(); 2265 } 2266 2267 for (nsCOMPtr<nsIContent> grandChild = aPopup->GetFirstChild(); grandChild; 2268 grandChild = grandChild->GetNextSibling()) { 2269 if (grandChild->IsXULElement(nsGkAtoms::menugroup)) { 2270 if (grandChild->GetChildCount() == 0) { 2271 continue; 2272 } 2273 grandChild = grandChild->GetFirstChild(); 2274 } 2275 if (grandChild->IsXULElement(nsGkAtoms::menuitem)) { 2276 // See if we have a command attribute. 2277 Element* grandChildElement = grandChild->AsElement(); 2278 nsAutoString command; 2279 grandChildElement->GetAttr(nsGkAtoms::command, command); 2280 if (!command.IsEmpty()) { 2281 // We do! Look it up in our document 2282 RefPtr<dom::Element> commandElement = document->GetElementById(command); 2283 if (commandElement) { 2284 nsAutoString commandValue; 2285 // The menu's disabled state needs to be updated to match the command. 2286 if (commandElement->GetAttr(nsGkAtoms::disabled, commandValue)) { 2287 grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, 2288 commandValue, true); 2289 } else { 2290 grandChildElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, 2291 true); 2292 } 2293 2294 // The menu's label, accesskey checked and hidden states need to be 2295 // updated to match the command. Note that unlike the disabled state 2296 // if the command has *no* value, we assume the menu is supplying its 2297 // own. 2298 if (commandElement->GetAttr(nsGkAtoms::label, commandValue)) { 2299 grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::label, 2300 commandValue, true); 2301 } 2302 2303 if (commandElement->GetAttr(nsGkAtoms::accesskey, commandValue)) { 2304 grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, 2305 commandValue, true); 2306 } 2307 2308 if (commandElement->GetAttr(nsGkAtoms::checked, commandValue)) { 2309 grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, 2310 commandValue, true); 2311 } else { 2312 grandChildElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, 2313 true); 2314 } 2315 2316 if (commandElement->GetAttr(nsGkAtoms::hidden, commandValue)) { 2317 grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, 2318 commandValue, true); 2319 } 2320 } 2321 } 2322 } 2323 if (!grandChild->GetNextSibling() && 2324 grandChild->GetParent()->IsXULElement(nsGkAtoms::menugroup)) { 2325 grandChild = grandChild->GetParent(); 2326 } 2327 } 2328 } 2329 2330 // Notify 2331 // 2332 // The item selection timer has fired, we might have to readjust the 2333 // selected item. There are two cases here that we are trying to deal with: 2334 // (1) diagonal movement from a parent menu to a submenu passing briefly over 2335 // other items, and 2336 // (2) moving out from a submenu to a parent or grandparent menu. 2337 // In both cases, |mTimerMenu| is the menu item that might have an open submenu 2338 // and the first item in |mPopups| is the item the mouse is currently over, 2339 // which could be none of them. 2340 // 2341 // case (1): 2342 // As the mouse moves from the parent item of a submenu (we'll call 'A') 2343 // diagonally into the submenu, it probably passes through one or more 2344 // sibilings (B). As the mouse passes through B, it becomes the current menu 2345 // item and the timer is set and mTimerMenu is set to A. Before the timer 2346 // fires, the mouse leaves the menu containing A and B and enters the submenus. 2347 // Now when the timer fires, |mPopups| is null (!= |mTimerMenu|) so we have to 2348 // see if anything in A's children is selected (recall that even disabled items 2349 // are selected, the style just doesn't show it). If that is the case, we need 2350 // to set the selected item back to A. 2351 // 2352 // case (2); 2353 // Item A has an open submenu, and in it there is an item (B) which also has an 2354 // open submenu (so there are 3 menus displayed right now). The mouse then 2355 // leaves B's child submenu and selects an item that is a sibling of A, call it 2356 // C. When the mouse enters C, the timer is set and |mTimerMenu| is A and 2357 // |mPopups| is C. As the timer fires, the mouse is still within C. The correct 2358 // behavior is to set the current item to C and close up the chain parented at 2359 // A. 2360 // 2361 // This brings up the question of is the logic of case (1) enough? The answer 2362 // is no, and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu 2363 // has a selected child, and if it does, set the selected item to A. Because B 2364 // has a submenu open, it is selected and as a result, A is set to be the 2365 // selected item even though the mouse rests in C -- very wrong. 2366 // 2367 // The solution is to use the same idea, but instead of only checking one 2368 // level, drill all the way down to the deepest open submenu and check if it 2369 // has something selected. Since the mouse is in a grandparent, it won't, and 2370 // we know that we can safely close up A and all its children. 2371 // 2372 // The code below melds the two cases together. 2373 // 2374 void nsXULPopupManager::KillMenuTimer() { 2375 if (mCloseTimer && mTimerMenu) { 2376 mCloseTimer->Cancel(); 2377 mCloseTimer = nullptr; 2378 2379 if (mTimerMenu->IsOpen()) { 2380 HidePopup(&mTimerMenu->PopupElement(), {HidePopupOption::Async}); 2381 } 2382 } 2383 2384 mTimerMenu = nullptr; 2385 } 2386 2387 void nsXULPopupManager::CancelMenuTimer(nsMenuPopupFrame* aMenu) { 2388 if (mCloseTimer && mTimerMenu == aMenu) { 2389 mCloseTimer->Cancel(); 2390 mCloseTimer = nullptr; 2391 mTimerMenu = nullptr; 2392 } 2393 } 2394 2395 bool nsXULPopupManager::HandleShortcutNavigation(KeyboardEvent& aKeyEvent, 2396 nsMenuPopupFrame* aFrame) { 2397 // On Windows, don't check shortcuts when the accelerator key is down. 2398 #ifdef XP_WIN 2399 WidgetInputEvent* evt = aKeyEvent.WidgetEventPtr()->AsInputEvent(); 2400 if (evt && evt->IsAccel()) { 2401 return false; 2402 } 2403 #endif 2404 2405 if (!aFrame) { 2406 if (nsMenuChainItem* item = GetTopVisibleMenu()) { 2407 aFrame = item->Frame(); 2408 } 2409 } 2410 2411 if (aFrame) { 2412 bool action = false; 2413 RefPtr result = aFrame->FindMenuWithShortcut(aKeyEvent, action); 2414 if (!result) { 2415 return false; 2416 } 2417 RefPtr popup = &aFrame->PopupElement(); 2418 popup->SetActiveMenuChild(result, XULMenuParentElement::ByKey::Yes); 2419 if (action) { 2420 WidgetEvent* evt = aKeyEvent.WidgetEventPtr(); 2421 result->HandleEnterKeyPress(*evt); 2422 } 2423 return true; 2424 } 2425 2426 // Only do shortcut navigation when the menubar is activated via keyboard. 2427 if (mActiveMenuBar) { 2428 RefPtr menubar = mActiveMenuBar; 2429 if (RefPtr result = menubar->FindMenuWithShortcut(aKeyEvent)) { 2430 result->OpenMenuPopup(true); 2431 return true; 2432 } 2433 #ifdef XP_WIN 2434 // Behavior on Windows - this item is on the menu bar, beep and deactivate 2435 // the menu bar. 2436 // TODO(emilio): This is rather odd, and I cannot get the beep to work, 2437 // but this matches what old code was doing... 2438 if (nsCOMPtr<nsISound> sound = do_GetService("@mozilla.org/sound;1")) { 2439 sound->Beep(); 2440 } 2441 menubar->SetActive(false); 2442 #endif 2443 } 2444 return false; 2445 } 2446 2447 bool nsXULPopupManager::HandleKeyboardNavigation(uint32_t aKeyCode) { 2448 if (nsMenuChainItem* nextitem = GetTopVisibleMenu()) { 2449 nextitem->Element()->OwnerDoc()->FlushPendingNotifications( 2450 FlushType::Frames); 2451 } 2452 2453 // navigate up through the open menus, looking for the topmost one 2454 // in the same hierarchy 2455 nsMenuChainItem* item = nullptr; 2456 nsMenuChainItem* nextitem = GetTopVisibleMenu(); 2457 while (nextitem) { 2458 item = nextitem; 2459 nextitem = item->GetParent(); 2460 2461 if (!nextitem) { 2462 break; 2463 } 2464 // stop if the parent isn't a menu 2465 if (!nextitem->IsMenu()) { 2466 break; 2467 } 2468 2469 // Check to make sure that the parent is actually the parent menu. It won't 2470 // be if the parent is in a different frame hierarchy, for example, for a 2471 // context menu opened on another menu. 2472 XULPopupElement& expectedParent = nextitem->Frame()->PopupElement(); 2473 auto* menu = item->Frame()->PopupElement().GetContainingMenu(); 2474 if (!menu || menu->GetMenuParent() != &expectedParent) { 2475 break; 2476 } 2477 } 2478 2479 nsIFrame* itemFrame; 2480 if (item) { 2481 itemFrame = item->Frame(); 2482 } else if (mActiveMenuBar) { 2483 itemFrame = mActiveMenuBar->GetPrimaryFrame(); 2484 if (!itemFrame) { 2485 return false; 2486 } 2487 } else { 2488 return false; 2489 } 2490 2491 nsNavigationDirection theDirection; 2492 NS_ASSERTION(aKeyCode >= KeyboardEvent_Binding::DOM_VK_END && 2493 aKeyCode <= KeyboardEvent_Binding::DOM_VK_DOWN, 2494 "Illegal key code"); 2495 theDirection = NS_DIRECTION_FROM_KEY_CODE(itemFrame, aKeyCode); 2496 2497 bool selectFirstItem = true; 2498 #ifdef MOZ_WIDGET_GTK 2499 { 2500 XULButtonElement* currentItem = nullptr; 2501 if (item && mActiveMenuBar && NS_DIRECTION_IS_INLINE(theDirection)) { 2502 currentItem = item->Frame()->PopupElement().GetActiveMenuChild(); 2503 // If nothing is selected in the menu and we have a menubar, let it 2504 // handle the movement not to steal focus from it. 2505 if (!currentItem) { 2506 item = nullptr; 2507 } 2508 } 2509 // On menu change, only select first item if an item is already selected. 2510 selectFirstItem = !!currentItem; 2511 } 2512 #endif 2513 2514 // if a popup is open, first check for navigation within the popup 2515 if (item && HandleKeyboardNavigationInPopup(item, theDirection)) { 2516 return true; 2517 } 2518 2519 // no popup handled the key, so check the active menubar, if any 2520 if (!mActiveMenuBar) { 2521 return false; 2522 } 2523 RefPtr menubar = mActiveMenuBar; 2524 if (NS_DIRECTION_IS_INLINE(theDirection)) { 2525 RefPtr prevActiveItem = menubar->GetActiveMenuChild(); 2526 const bool open = prevActiveItem && prevActiveItem->IsMenuPopupOpen(); 2527 RefPtr nextItem = theDirection == eNavigationDirection_End 2528 ? menubar->GetNextMenuItem() 2529 : menubar->GetPrevMenuItem(); 2530 menubar->SetActiveMenuChild(nextItem, XULMenuParentElement::ByKey::Yes); 2531 if (open && nextItem) { 2532 nextItem->OpenMenuPopup(selectFirstItem); 2533 } 2534 return true; 2535 } 2536 if (NS_DIRECTION_IS_BLOCK(theDirection)) { 2537 // Open the menu and select its first item. 2538 if (RefPtr currentMenu = menubar->GetActiveMenuChild()) { 2539 ShowMenu(currentMenu, selectFirstItem); 2540 } 2541 return true; 2542 } 2543 return false; 2544 } 2545 2546 bool nsXULPopupManager::HandleKeyboardNavigationInPopup( 2547 nsMenuChainItem* item, nsMenuPopupFrame* aFrame, 2548 nsNavigationDirection aDir) { 2549 NS_ASSERTION(aFrame, "aFrame is null"); 2550 NS_ASSERTION(!item || item->Frame() == aFrame, 2551 "aFrame is expected to be equal to item->Frame()"); 2552 2553 using Wrap = XULMenuParentElement::Wrap; 2554 RefPtr<XULPopupElement> menu = &aFrame->PopupElement(); 2555 2556 aFrame->ClearIncrementalString(); 2557 RefPtr currentItem = aFrame->GetCurrentMenuItem(); 2558 2559 // This method only gets called if we're open. 2560 if (!currentItem && NS_DIRECTION_IS_INLINE(aDir)) { 2561 // We've been opened, but we haven't had anything selected. 2562 // We can handle End, but our parent handles Start. 2563 if (aDir == eNavigationDirection_End) { 2564 if (RefPtr nextItem = menu->GetNextMenuItem(Wrap::No)) { 2565 menu->SetActiveMenuChild(nextItem, XULMenuParentElement::ByKey::Yes); 2566 return true; 2567 } 2568 } 2569 return false; 2570 } 2571 2572 const bool isContainer = currentItem && !currentItem->IsMenuItem(); 2573 const bool isOpen = currentItem && currentItem->IsMenuPopupOpen(); 2574 if (isOpen) { 2575 // For an open popup, have the child process the event 2576 nsMenuChainItem* child = item ? item->GetChild() : nullptr; 2577 if (child && HandleKeyboardNavigationInPopup(child, aDir)) { 2578 return true; 2579 } 2580 } else if (aDir == eNavigationDirection_End && isContainer && 2581 !currentItem->IsDisabled()) { 2582 currentItem->OpenMenuPopup(true); 2583 return true; 2584 } 2585 2586 // For block progression, we can move in either direction 2587 if (NS_DIRECTION_IS_BLOCK(aDir) || NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir)) { 2588 RefPtr<XULButtonElement> nextItem = nullptr; 2589 2590 if (aDir == eNavigationDirection_Before || 2591 aDir == eNavigationDirection_After) { 2592 // Cursor navigation does not wrap on Mac or for menulists on Windows. 2593 auto wrap = 2594 #ifdef XP_WIN 2595 aFrame->IsMenuList() ? Wrap::No : Wrap::Yes; 2596 #elif defined XP_MACOSX 2597 Wrap::No; 2598 #else 2599 Wrap::Yes; 2600 #endif 2601 2602 if (aDir == eNavigationDirection_Before) { 2603 nextItem = menu->GetPrevMenuItem(wrap); 2604 } else { 2605 nextItem = menu->GetNextMenuItem(wrap); 2606 } 2607 } else if (aDir == eNavigationDirection_First) { 2608 nextItem = menu->GetFirstMenuItem(); 2609 } else { 2610 nextItem = menu->GetLastMenuItem(); 2611 } 2612 2613 if (nextItem) { 2614 menu->SetActiveMenuChild(nextItem, XULMenuParentElement::ByKey::Yes); 2615 return true; 2616 } 2617 } else if (currentItem && isOpen && aDir == eNavigationDirection_Start) { 2618 // close a submenu when Left is pressed 2619 if (nsMenuPopupFrame* popupFrame = 2620 currentItem->GetMenuPopup(FlushType::None)) { 2621 HidePopup(&popupFrame->PopupElement(), {}); 2622 } 2623 return true; 2624 } 2625 2626 return false; 2627 } 2628 2629 bool nsXULPopupManager::HandleKeyboardEventWithKeyCode( 2630 KeyboardEvent* aKeyEvent, nsMenuChainItem* aTopVisibleMenuItem) { 2631 uint32_t keyCode = aKeyEvent->KeyCode(); 2632 2633 // Escape should close panels, but the other keys should have no effect. 2634 if (aTopVisibleMenuItem && 2635 aTopVisibleMenuItem->GetPopupType() != PopupType::Menu) { 2636 if (keyCode == KeyboardEvent_Binding::DOM_VK_ESCAPE) { 2637 HidePopup(aTopVisibleMenuItem->Element(), {HidePopupOption::IsRollup}); 2638 aKeyEvent->StopPropagation(); 2639 aKeyEvent->StopCrossProcessForwarding(); 2640 aKeyEvent->PreventDefault(); 2641 } 2642 return true; 2643 } 2644 2645 bool consume = (aTopVisibleMenuItem || mActiveMenuBar); 2646 switch (keyCode) { 2647 case KeyboardEvent_Binding::DOM_VK_UP: 2648 case KeyboardEvent_Binding::DOM_VK_DOWN: 2649 #ifndef XP_MACOSX 2650 // roll up the popup when alt+up/down are pressed within a menulist. 2651 if (aKeyEvent->AltKey() && aTopVisibleMenuItem && 2652 aTopVisibleMenuItem->Frame()->IsMenuList()) { 2653 Rollup({}); 2654 break; 2655 } 2656 [[fallthrough]]; 2657 #endif 2658 2659 case KeyboardEvent_Binding::DOM_VK_LEFT: 2660 case KeyboardEvent_Binding::DOM_VK_RIGHT: 2661 case KeyboardEvent_Binding::DOM_VK_HOME: 2662 case KeyboardEvent_Binding::DOM_VK_END: 2663 HandleKeyboardNavigation(keyCode); 2664 break; 2665 2666 case KeyboardEvent_Binding::DOM_VK_PAGE_DOWN: 2667 case KeyboardEvent_Binding::DOM_VK_PAGE_UP: 2668 if (aTopVisibleMenuItem) { 2669 aTopVisibleMenuItem->Frame()->ChangeByPage( 2670 keyCode == KeyboardEvent_Binding::DOM_VK_PAGE_UP); 2671 } 2672 break; 2673 2674 case KeyboardEvent_Binding::DOM_VK_ESCAPE: 2675 // Pressing Escape hides one level of menus only. If no menu is open, 2676 // check if a menubar is active and inform it that a menu closed. Even 2677 // though in this latter case, a menu didn't actually close, the effect 2678 // ends up being the same. Similar for the tab key below. 2679 if (aTopVisibleMenuItem) { 2680 HidePopup(aTopVisibleMenuItem->Element(), {HidePopupOption::IsRollup}); 2681 } else if (mActiveMenuBar) { 2682 RefPtr menubar = mActiveMenuBar; 2683 menubar->SetActive(false); 2684 } 2685 break; 2686 2687 case KeyboardEvent_Binding::DOM_VK_TAB: 2688 #ifndef XP_MACOSX 2689 case KeyboardEvent_Binding::DOM_VK_F10: 2690 #endif 2691 if (aTopVisibleMenuItem && 2692 !aTopVisibleMenuItem->Frame()->PopupElement().AttrValueIs( 2693 kNameSpaceID_None, nsGkAtoms::activateontab, nsGkAtoms::_true, 2694 eCaseMatters)) { 2695 // Close popups or deactivate menubar when Tab or F10 are pressed 2696 Rollup({}); 2697 break; 2698 } else if (mActiveMenuBar) { 2699 RefPtr menubar = mActiveMenuBar; 2700 menubar->SetActive(false); 2701 break; 2702 } 2703 // Intentional fall-through to RETURN case 2704 [[fallthrough]]; 2705 2706 case KeyboardEvent_Binding::DOM_VK_RETURN: { 2707 // If there is a popup open, check if the current item needs to be opened. 2708 // Otherwise, tell the active menubar, if any, to activate the menu. The 2709 // Enter method will return a menu if one needs to be opened as a result. 2710 WidgetEvent* event = aKeyEvent->WidgetEventPtr(); 2711 if (aTopVisibleMenuItem) { 2712 aTopVisibleMenuItem->Frame()->HandleEnterKeyPress(*event); 2713 } else if (mActiveMenuBar) { 2714 RefPtr menubar = mActiveMenuBar; 2715 menubar->HandleEnterKeyPress(*event); 2716 } 2717 break; 2718 } 2719 2720 default: 2721 return false; 2722 } 2723 2724 if (consume) { 2725 aKeyEvent->StopPropagation(); 2726 aKeyEvent->StopCrossProcessForwarding(); 2727 aKeyEvent->PreventDefault(); 2728 } 2729 return true; 2730 } 2731 2732 nsresult nsXULPopupManager::HandleEvent(Event* aEvent) { 2733 RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent(); 2734 NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED); 2735 2736 // handlers shouldn't be triggered by non-trusted events. 2737 if (!keyEvent->IsTrusted()) { 2738 return NS_OK; 2739 } 2740 2741 nsAutoString eventType; 2742 keyEvent->GetType(eventType); 2743 if (eventType.EqualsLiteral("keyup")) { 2744 return KeyUp(keyEvent); 2745 } 2746 if (eventType.EqualsLiteral("keydown")) { 2747 return KeyDown(keyEvent); 2748 } 2749 if (eventType.EqualsLiteral("keypress")) { 2750 return KeyPress(keyEvent); 2751 } 2752 2753 MOZ_ASSERT_UNREACHABLE("Unexpected eventType"); 2754 return NS_OK; 2755 } 2756 2757 nsresult nsXULPopupManager::UpdateIgnoreKeys(bool aIgnoreKeys) { 2758 nsMenuChainItem* item = GetTopVisibleMenu(); 2759 if (item) { 2760 item->SetIgnoreKeys(aIgnoreKeys ? eIgnoreKeys_True : eIgnoreKeys_Shortcuts); 2761 } 2762 UpdateKeyboardListeners(); 2763 return NS_OK; 2764 } 2765 2766 nsPopupState nsXULPopupManager::GetPopupState(Element* aPopupElement) { 2767 if (mNativeMenu && mNativeMenu->Element()->Contains(aPopupElement)) { 2768 if (aPopupElement != mNativeMenu->Element()) { 2769 // Submenu state is stored in mNativeMenuSubmenuStates. 2770 return mNativeMenuSubmenuStates.MaybeGet(aPopupElement) 2771 .valueOr(ePopupClosed); 2772 } 2773 // mNativeMenu->Element()'s state is stored in its nsMenuPopupFrame. 2774 } 2775 2776 nsMenuPopupFrame* menuPopupFrame = 2777 do_QueryFrame(aPopupElement->GetPrimaryFrame()); 2778 if (menuPopupFrame) { 2779 return menuPopupFrame->PopupState(); 2780 } 2781 return ePopupClosed; 2782 } 2783 2784 nsresult nsXULPopupManager::KeyUp(KeyboardEvent* aKeyEvent) { 2785 // don't do anything if a menu isn't open or a menubar isn't active 2786 if (!mActiveMenuBar) { 2787 nsMenuChainItem* item = GetTopVisibleMenu(); 2788 if (!item || item->GetPopupType() != PopupType::Menu) { 2789 return NS_OK; 2790 } 2791 2792 if (item->IgnoreKeys() == eIgnoreKeys_Shortcuts) { 2793 aKeyEvent->StopCrossProcessForwarding(); 2794 return NS_OK; 2795 } 2796 } 2797 2798 aKeyEvent->StopPropagation(); 2799 aKeyEvent->StopCrossProcessForwarding(); 2800 aKeyEvent->PreventDefault(); 2801 2802 return NS_OK; // I am consuming event 2803 } 2804 2805 nsresult nsXULPopupManager::KeyDown(KeyboardEvent* aKeyEvent) { 2806 nsMenuChainItem* item = GetTopVisibleMenu(); 2807 if (item && item->Frame()->PopupElement().IsLocked()) { 2808 return NS_OK; 2809 } 2810 2811 if (HandleKeyboardEventWithKeyCode(aKeyEvent, item)) { 2812 return NS_OK; 2813 } 2814 2815 // don't do anything if a menu isn't open or a menubar isn't active 2816 if (!mActiveMenuBar && (!item || item->GetPopupType() != PopupType::Menu)) { 2817 return NS_OK; 2818 } 2819 2820 // Since a menu was open, stop propagation of the event to keep other event 2821 // listeners from becoming confused. 2822 if (!item || item->IgnoreKeys() != eIgnoreKeys_Shortcuts) { 2823 aKeyEvent->StopPropagation(); 2824 } 2825 2826 // If the key just pressed is the access key (usually Alt), 2827 // dismiss and unfocus the menu. 2828 uint32_t menuAccessKey = LookAndFeel::GetMenuAccessKey(); 2829 if (menuAccessKey) { 2830 uint32_t theChar = aKeyEvent->KeyCode(); 2831 2832 if (theChar == menuAccessKey) { 2833 bool ctrl = (menuAccessKey != KeyboardEvent_Binding::DOM_VK_CONTROL && 2834 aKeyEvent->CtrlKey()); 2835 bool alt = (menuAccessKey != KeyboardEvent_Binding::DOM_VK_ALT && 2836 aKeyEvent->AltKey()); 2837 bool shift = (menuAccessKey != KeyboardEvent_Binding::DOM_VK_SHIFT && 2838 aKeyEvent->ShiftKey()); 2839 bool meta = (menuAccessKey != KeyboardEvent_Binding::DOM_VK_META && 2840 aKeyEvent->MetaKey()); 2841 if (!(ctrl || alt || shift || meta)) { 2842 // The access key just went down and no other 2843 // modifiers are already down. 2844 nsMenuChainItem* item = GetTopVisibleMenu(); 2845 if (item && !item->Frame()->IsMenuList()) { 2846 Rollup({}); 2847 } else if (mActiveMenuBar) { 2848 RefPtr menubar = mActiveMenuBar; 2849 menubar->SetActive(false); 2850 } 2851 2852 // Clear the item to avoid bugs as it may have been deleted during 2853 // rollup. 2854 item = nullptr; 2855 } 2856 aKeyEvent->StopPropagation(); 2857 aKeyEvent->PreventDefault(); 2858 } 2859 } 2860 2861 aKeyEvent->StopCrossProcessForwarding(); 2862 return NS_OK; 2863 } 2864 2865 nsresult nsXULPopupManager::KeyPress(KeyboardEvent* aKeyEvent) { 2866 // Don't check prevent default flag -- menus always get first shot at key 2867 // events. 2868 2869 nsMenuChainItem* item = GetTopVisibleMenu(); 2870 if (item && (item->Frame()->PopupElement().IsLocked() || 2871 item->GetPopupType() != PopupType::Menu)) { 2872 return NS_OK; 2873 } 2874 2875 // if a menu is open or a menubar is active, it consumes the key event 2876 bool consume = (item || mActiveMenuBar); 2877 2878 WidgetInputEvent* evt = aKeyEvent->WidgetEventPtr()->AsInputEvent(); 2879 bool isAccel = evt && evt->IsAccel(); 2880 2881 // When ignorekeys="shortcuts" is used, we don't call preventDefault on the 2882 // key event when the accelerator key is pressed. This allows another 2883 // listener to handle keys. For instance, this allows global shortcuts to 2884 // still apply while a menu is open. 2885 if (item && item->IgnoreKeys() == eIgnoreKeys_Shortcuts && isAccel) { 2886 consume = false; 2887 } 2888 2889 HandleShortcutNavigation(*aKeyEvent, nullptr); 2890 2891 aKeyEvent->StopCrossProcessForwarding(); 2892 if (consume) { 2893 aKeyEvent->StopPropagation(); 2894 aKeyEvent->PreventDefault(); 2895 } 2896 2897 return NS_OK; // I am consuming event 2898 } 2899 2900 void nsXULPopupManager::DismissQueueableShownPopups() { 2901 if (!mPopupQueue) { 2902 return; 2903 } 2904 2905 RefPtr<Element> popup = mPopupQueue->RetrieveQueueableShownPopup(); 2906 if (popup) { 2907 HidePopup(popup, {HidePopupOption::IsRollup}); 2908 } 2909 } 2910 2911 NS_IMETHODIMP 2912 nsXULPopupHidingEvent::Run() { 2913 RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance(); 2914 Document* document = mPopup->GetUncomposedDoc(); 2915 if (pm && document) { 2916 if (RefPtr<nsPresContext> presContext = document->GetPresContext()) { 2917 nsCOMPtr<Element> popup = mPopup; 2918 nsCOMPtr<Element> nextPopup = mNextPopup; 2919 nsCOMPtr<Element> lastPopup = mLastPopup; 2920 pm->FirePopupHidingEvent(popup, nextPopup, lastPopup, presContext, 2921 mPopupType, mOptions); 2922 } 2923 } 2924 return NS_OK; 2925 } 2926 2927 bool nsXULPopupPositionedEvent::DispatchIfNeeded(Element* aPopup) { 2928 // The popuppositioned event only fires on arrow panels for now. 2929 if (aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::arrow, 2930 eCaseMatters)) { 2931 nsCOMPtr<nsIRunnable> event = new nsXULPopupPositionedEvent(aPopup); 2932 aPopup->OwnerDoc()->Dispatch(event.forget()); 2933 return true; 2934 } 2935 2936 return false; 2937 } 2938 2939 static void AlignmentPositionToString(nsMenuPopupFrame* aFrame, 2940 nsAString& aString) { 2941 aString.Truncate(); 2942 int8_t position = aFrame->GetAlignmentPosition(); 2943 switch (position) { 2944 case POPUPPOSITION_AFTERSTART: 2945 return aString.AssignLiteral("after_start"); 2946 case POPUPPOSITION_AFTEREND: 2947 return aString.AssignLiteral("after_end"); 2948 case POPUPPOSITION_BEFORESTART: 2949 return aString.AssignLiteral("before_start"); 2950 case POPUPPOSITION_BEFOREEND: 2951 return aString.AssignLiteral("before_end"); 2952 case POPUPPOSITION_STARTBEFORE: 2953 return aString.AssignLiteral("start_before"); 2954 case POPUPPOSITION_ENDBEFORE: 2955 return aString.AssignLiteral("end_before"); 2956 case POPUPPOSITION_STARTAFTER: 2957 return aString.AssignLiteral("start_after"); 2958 case POPUPPOSITION_ENDAFTER: 2959 return aString.AssignLiteral("end_after"); 2960 case POPUPPOSITION_OVERLAP: 2961 return aString.AssignLiteral("overlap"); 2962 case POPUPPOSITION_AFTERPOINTER: 2963 return aString.AssignLiteral("after_pointer"); 2964 case POPUPPOSITION_SELECTION: 2965 return aString.AssignLiteral("selection"); 2966 default: 2967 // Leave as an empty string. 2968 break; 2969 } 2970 } 2971 2972 static void PopupAlignmentToString(nsMenuPopupFrame* aFrame, 2973 nsAString& aString) { 2974 aString.Truncate(); 2975 int alignment = aFrame->GetPopupAlignment(); 2976 switch (alignment) { 2977 case POPUPALIGNMENT_TOPLEFT: 2978 return aString.AssignLiteral("topleft"); 2979 case POPUPALIGNMENT_TOPRIGHT: 2980 return aString.AssignLiteral("topright"); 2981 case POPUPALIGNMENT_BOTTOMLEFT: 2982 return aString.AssignLiteral("bottomleft"); 2983 case POPUPALIGNMENT_BOTTOMRIGHT: 2984 return aString.AssignLiteral("bottomright"); 2985 case POPUPALIGNMENT_LEFTCENTER: 2986 return aString.AssignLiteral("leftcenter"); 2987 case POPUPALIGNMENT_RIGHTCENTER: 2988 return aString.AssignLiteral("rightcenter"); 2989 case POPUPALIGNMENT_TOPCENTER: 2990 return aString.AssignLiteral("topcenter"); 2991 case POPUPALIGNMENT_BOTTOMCENTER: 2992 return aString.AssignLiteral("bottomcenter"); 2993 default: 2994 // Leave as an empty string. 2995 break; 2996 } 2997 } 2998 2999 NS_IMETHODIMP 3000 MOZ_CAN_RUN_SCRIPT_BOUNDARY 3001 nsXULPopupPositionedEvent::Run() { 3002 RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance(); 3003 if (!pm) { 3004 return NS_OK; 3005 } 3006 nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame()); 3007 if (!popupFrame) { 3008 return NS_OK; 3009 } 3010 3011 popupFrame->WillDispatchPopupPositioned(); 3012 3013 // At this point, hidePopup may have been called but it currently has no 3014 // way to stop this event. However, if hidePopup was called, the popup 3015 // will now be in the hiding or closed state. If we are in the shown or 3016 // positioning state instead, we can assume that we are still clear to 3017 // open/move the popup 3018 nsPopupState state = popupFrame->PopupState(); 3019 if (state != ePopupPositioning && state != ePopupShown) { 3020 return NS_OK; 3021 } 3022 3023 // Note that the offset might be along either the X or Y axis, but for the 3024 // sake of simplicity we use a point with only the X axis set so we can 3025 // use ToNearestPixels(). 3026 int32_t popupOffset = nsPoint(popupFrame->GetAlignmentOffset(), 0) 3027 .ToNearestPixels(AppUnitsPerCSSPixel()) 3028 .x; 3029 3030 PopupPositionedEventInit init; 3031 init.mComposed = true; 3032 init.mIsAnchored = popupFrame->IsAnchored(); 3033 init.mAlignmentOffset = popupOffset; 3034 AlignmentPositionToString(popupFrame, init.mAlignmentPosition); 3035 PopupAlignmentToString(popupFrame, init.mPopupAlignment); 3036 RefPtr<PopupPositionedEvent> event = 3037 PopupPositionedEvent::Constructor(mPopup, u"popuppositioned"_ns, init); 3038 event->SetTrusted(true); 3039 3040 mPopup->DispatchEvent(*event); 3041 3042 // Get the popup frame and make sure it is still in the positioning 3043 // state. If it isn't, someone may have tried to reshow or hide it 3044 // during the popuppositioned event. 3045 // Alternately, this event may have been fired in reponse to moving the 3046 // popup rather than opening it. In that case, we are done. 3047 popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame()); 3048 if (popupFrame && popupFrame->PopupState() == ePopupPositioning) { 3049 pm->ShowPopupCallback(mPopup, popupFrame, false, false); 3050 } 3051 3052 return NS_OK; 3053 } 3054 3055 NS_IMETHODIMP 3056 nsXULMenuCommandEvent::Run() { 3057 nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 3058 if (!pm) { 3059 return NS_OK; 3060 } 3061 3062 RefPtr menu = XULButtonElement::FromNode(mMenu); 3063 MOZ_ASSERT(menu); 3064 if (mFlipChecked) { 3065 menu->SetBoolAttr(nsGkAtoms::checked, 3066 !menu->GetBoolAttr(nsGkAtoms::checked)); 3067 } 3068 3069 RefPtr<nsPresContext> presContext = menu->OwnerDoc()->GetPresContext(); 3070 RefPtr<PresShell> presShell = 3071 presContext ? presContext->PresShell() : nullptr; 3072 3073 // Deselect ourselves. 3074 if (mCloseMenuMode != CloseMenuMode_None) { 3075 if (RefPtr parent = menu->GetMenuParent()) { 3076 if (parent->GetActiveMenuChild() == menu) { 3077 parent->SetActiveMenuChild(nullptr); 3078 } 3079 } 3080 } 3081 3082 AutoHandlingUserInputStatePusher userInpStatePusher(mUserInput); 3083 nsContentUtils::DispatchXULCommand( 3084 menu, mIsTrusted, nullptr, presShell, mModifiers & MODIFIER_CONTROL, 3085 mModifiers & MODIFIER_ALT, mModifiers & MODIFIER_SHIFT, 3086 mModifiers & MODIFIER_META, 0, mButton); 3087 3088 if (mCloseMenuMode != CloseMenuMode_None) { 3089 if (RefPtr popup = menu->GetContainingPopupElement()) { 3090 HidePopupOptions options{HidePopupOption::DeselectMenu}; 3091 if (mCloseMenuMode == CloseMenuMode_Auto) { 3092 options += HidePopupOption::HideChain; 3093 } 3094 pm->HidePopup(popup, options); 3095 } 3096 } 3097 3098 return NS_OK; 3099 }