1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/xul/nsXULPopupManager.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2470 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsGkAtoms.h" 1.10 +#include "nsXULPopupManager.h" 1.11 +#include "nsMenuFrame.h" 1.12 +#include "nsMenuPopupFrame.h" 1.13 +#include "nsMenuBarFrame.h" 1.14 +#include "nsIPopupBoxObject.h" 1.15 +#include "nsMenuBarListener.h" 1.16 +#include "nsContentUtils.h" 1.17 +#include "nsIDOMDocument.h" 1.18 +#include "nsIDOMEvent.h" 1.19 +#include "nsIDOMXULElement.h" 1.20 +#include "nsIXULDocument.h" 1.21 +#include "nsIXULTemplateBuilder.h" 1.22 +#include "nsCSSFrameConstructor.h" 1.23 +#include "nsLayoutUtils.h" 1.24 +#include "nsViewManager.h" 1.25 +#include "nsIComponentManager.h" 1.26 +#include "nsITimer.h" 1.27 +#include "nsFocusManager.h" 1.28 +#include "nsIDocShell.h" 1.29 +#include "nsPIDOMWindow.h" 1.30 +#include "nsIInterfaceRequestorUtils.h" 1.31 +#include "nsIBaseWindow.h" 1.32 +#include "nsIDOMKeyEvent.h" 1.33 +#include "nsIDOMMouseEvent.h" 1.34 +#include "nsCaret.h" 1.35 +#include "nsIDocument.h" 1.36 +#include "nsPIWindowRoot.h" 1.37 +#include "nsFrameManager.h" 1.38 +#include "nsIObserverService.h" 1.39 +#include "mozilla/dom/Element.h" 1.40 +#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() 1.41 +#include "mozilla/EventDispatcher.h" 1.42 +#include "mozilla/EventStateManager.h" 1.43 +#include "mozilla/LookAndFeel.h" 1.44 +#include "mozilla/MouseEvents.h" 1.45 +#include "mozilla/Services.h" 1.46 + 1.47 +using namespace mozilla; 1.48 +using namespace mozilla::dom; 1.49 + 1.50 +static_assert(nsIDOMKeyEvent::DOM_VK_HOME == nsIDOMKeyEvent::DOM_VK_END + 1 && 1.51 + nsIDOMKeyEvent::DOM_VK_LEFT == nsIDOMKeyEvent::DOM_VK_END + 2 && 1.52 + nsIDOMKeyEvent::DOM_VK_UP == nsIDOMKeyEvent::DOM_VK_END + 3 && 1.53 + nsIDOMKeyEvent::DOM_VK_RIGHT == nsIDOMKeyEvent::DOM_VK_END + 4 && 1.54 + nsIDOMKeyEvent::DOM_VK_DOWN == nsIDOMKeyEvent::DOM_VK_END + 5, 1.55 + "nsXULPopupManager assumes some keyCode values are consecutive"); 1.56 + 1.57 +const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = { 1.58 + { 1.59 + eNavigationDirection_Last, // nsIDOMKeyEvent::DOM_VK_END 1.60 + eNavigationDirection_First, // nsIDOMKeyEvent::DOM_VK_HOME 1.61 + eNavigationDirection_Start, // nsIDOMKeyEvent::DOM_VK_LEFT 1.62 + eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP 1.63 + eNavigationDirection_End, // nsIDOMKeyEvent::DOM_VK_RIGHT 1.64 + eNavigationDirection_After // nsIDOMKeyEvent::DOM_VK_DOWN 1.65 + }, 1.66 + { 1.67 + eNavigationDirection_Last, // nsIDOMKeyEvent::DOM_VK_END 1.68 + eNavigationDirection_First, // nsIDOMKeyEvent::DOM_VK_HOME 1.69 + eNavigationDirection_End, // nsIDOMKeyEvent::DOM_VK_LEFT 1.70 + eNavigationDirection_Before, // nsIDOMKeyEvent::DOM_VK_UP 1.71 + eNavigationDirection_Start, // nsIDOMKeyEvent::DOM_VK_RIGHT 1.72 + eNavigationDirection_After // nsIDOMKeyEvent::DOM_VK_DOWN 1.73 + } 1.74 +}; 1.75 + 1.76 +nsXULPopupManager* nsXULPopupManager::sInstance = nullptr; 1.77 + 1.78 +nsIContent* nsMenuChainItem::Content() 1.79 +{ 1.80 + return mFrame->GetContent(); 1.81 +} 1.82 + 1.83 +void nsMenuChainItem::SetParent(nsMenuChainItem* aParent) 1.84 +{ 1.85 + if (mParent) { 1.86 + NS_ASSERTION(mParent->mChild == this, "Unexpected - parent's child not set to this"); 1.87 + mParent->mChild = nullptr; 1.88 + } 1.89 + mParent = aParent; 1.90 + if (mParent) { 1.91 + if (mParent->mChild) 1.92 + mParent->mChild->mParent = nullptr; 1.93 + mParent->mChild = this; 1.94 + } 1.95 +} 1.96 + 1.97 +void nsMenuChainItem::Detach(nsMenuChainItem** aRoot) 1.98 +{ 1.99 + // If the item has a child, set the child's parent to this item's parent, 1.100 + // effectively removing the item from the chain. If the item has no child, 1.101 + // just set the parent to null. 1.102 + if (mChild) { 1.103 + NS_ASSERTION(this != *aRoot, "Unexpected - popup with child at end of chain"); 1.104 + mChild->SetParent(mParent); 1.105 + } 1.106 + else { 1.107 + // An item without a child should be the first item in the chain, so set 1.108 + // the first item pointer, pointed to by aRoot, to the parent. 1.109 + NS_ASSERTION(this == *aRoot, "Unexpected - popup with no child not at end of chain"); 1.110 + *aRoot = mParent; 1.111 + SetParent(nullptr); 1.112 + } 1.113 +} 1.114 + 1.115 +NS_IMPL_ISUPPORTS(nsXULPopupManager, 1.116 + nsIDOMEventListener, 1.117 + nsITimerCallback, 1.118 + nsIObserver) 1.119 + 1.120 +nsXULPopupManager::nsXULPopupManager() : 1.121 + mRangeOffset(0), 1.122 + mCachedMousePoint(0, 0), 1.123 + mCachedModifiers(0), 1.124 + mActiveMenuBar(nullptr), 1.125 + mPopups(nullptr), 1.126 + mNoHidePanels(nullptr), 1.127 + mTimerMenu(nullptr) 1.128 +{ 1.129 + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1.130 + if (obs) { 1.131 + obs->AddObserver(this, "xpcom-shutdown", false); 1.132 + } 1.133 +} 1.134 + 1.135 +nsXULPopupManager::~nsXULPopupManager() 1.136 +{ 1.137 + NS_ASSERTION(!mPopups && !mNoHidePanels, "XUL popups still open"); 1.138 +} 1.139 + 1.140 +nsresult 1.141 +nsXULPopupManager::Init() 1.142 +{ 1.143 + sInstance = new nsXULPopupManager(); 1.144 + NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY); 1.145 + NS_ADDREF(sInstance); 1.146 + return NS_OK; 1.147 +} 1.148 + 1.149 +void 1.150 +nsXULPopupManager::Shutdown() 1.151 +{ 1.152 + NS_IF_RELEASE(sInstance); 1.153 +} 1.154 + 1.155 +NS_IMETHODIMP 1.156 +nsXULPopupManager::Observe(nsISupports *aSubject, 1.157 + const char *aTopic, 1.158 + const char16_t *aData) 1.159 +{ 1.160 + if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) { 1.161 + if (mKeyListener) { 1.162 + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true); 1.163 + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true); 1.164 + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true); 1.165 + mKeyListener = nullptr; 1.166 + } 1.167 + mRangeParent = nullptr; 1.168 + // mOpeningPopup is cleared explicitly soon after using it. 1.169 + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1.170 + if (obs) { 1.171 + obs->RemoveObserver(this, "xpcom-shutdown"); 1.172 + } 1.173 + } 1.174 + 1.175 + return NS_OK; 1.176 +} 1.177 + 1.178 +nsXULPopupManager* 1.179 +nsXULPopupManager::GetInstance() 1.180 +{ 1.181 + MOZ_ASSERT(sInstance); 1.182 + return sInstance; 1.183 +} 1.184 + 1.185 +bool 1.186 +nsXULPopupManager::Rollup(uint32_t aCount, const nsIntPoint* pos, nsIContent** aLastRolledUp) 1.187 +{ 1.188 + bool consume = false; 1.189 + 1.190 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.191 + if (item) { 1.192 + if (aLastRolledUp) { 1.193 + // we need to get the popup that will be closed last, so that 1.194 + // widget can keep track of it so it doesn't reopen if a mouse 1.195 + // down event is going to processed. 1.196 + // Keep going up the menu chain to get the first level menu. This will 1.197 + // be the one that closes up last. It's possible that this menu doesn't 1.198 + // end up closing because the popuphiding event was cancelled, but in 1.199 + // that case we don't need to deal with the menu reopening as it will 1.200 + // already still be open. 1.201 + nsMenuChainItem* first = item; 1.202 + while (first->GetParent()) 1.203 + first = first->GetParent(); 1.204 + *aLastRolledUp = first->Content(); 1.205 + } 1.206 + 1.207 + consume = item->Frame()->ConsumeOutsideClicks(); 1.208 + // If the click was over the anchor, always consume the click. This way, 1.209 + // clicking on a menu doesn't reopen the menu. 1.210 + if (!consume && pos) { 1.211 + nsCOMPtr<nsIContent> anchor = item->Frame()->GetAnchor(); 1.212 + if (anchor && anchor->GetPrimaryFrame()) { 1.213 + // It's possible that some other element is above the anchor at the same 1.214 + // position, but the only thing that would happen is that the mouse 1.215 + // event will get consumed, so here only a quick coordinates check is 1.216 + // done rather than a slower complete check of what is at that location. 1.217 + if (anchor->GetPrimaryFrame()->GetScreenRect().Contains(*pos)) { 1.218 + consume = true; 1.219 + } 1.220 + } 1.221 + } 1.222 + 1.223 + // if a number of popups to close has been specified, determine the last 1.224 + // popup to close 1.225 + nsIContent* lastPopup = nullptr; 1.226 + if (aCount != UINT32_MAX) { 1.227 + nsMenuChainItem* last = item; 1.228 + while (--aCount && last->GetParent()) { 1.229 + last = last->GetParent(); 1.230 + } 1.231 + if (last) { 1.232 + lastPopup = last->Content(); 1.233 + } 1.234 + } 1.235 + 1.236 + HidePopup(item->Content(), true, true, false, true, lastPopup); 1.237 + } 1.238 + 1.239 + return consume; 1.240 +} 1.241 + 1.242 +//////////////////////////////////////////////////////////////////////// 1.243 +bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent() 1.244 +{ 1.245 + // should rollup only for autocomplete widgets 1.246 + // XXXndeakin this should really be something the popup has more control over 1.247 + 1.248 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.249 + if (!item) 1.250 + return false; 1.251 + 1.252 + nsIContent* content = item->Frame()->GetContent(); 1.253 + if (!content) 1.254 + return false; 1.255 + 1.256 + if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel, 1.257 + nsGkAtoms::_true, eCaseMatters)) 1.258 + return true; 1.259 + 1.260 + if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel, 1.261 + nsGkAtoms::_false, eCaseMatters)) 1.262 + return false; 1.263 + 1.264 + nsAutoString value; 1.265 + content->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value); 1.266 + return StringBeginsWith(value, NS_LITERAL_STRING("autocomplete")); 1.267 +} 1.268 + 1.269 +bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent() 1.270 +{ 1.271 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.272 + if (!item) 1.273 + return false; 1.274 + 1.275 + nsMenuPopupFrame* frame = item->Frame(); 1.276 + if (frame->PopupType() != ePopupTypePanel) 1.277 + return true; 1.278 + 1.279 + nsIContent* content = frame->GetContent(); 1.280 + return !(content && content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, 1.281 + nsGkAtoms::arrow, eCaseMatters)); 1.282 +} 1.283 + 1.284 +// a menu should not roll up if activated by a mouse activate message (eg. X-mouse) 1.285 +bool nsXULPopupManager::ShouldRollupOnMouseActivate() 1.286 +{ 1.287 + return false; 1.288 +} 1.289 + 1.290 +uint32_t 1.291 +nsXULPopupManager::GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain) 1.292 +{ 1.293 + // this method is used by the widget code to determine the list of popups 1.294 + // that are open. If a mouse click occurs outside one of these popups, the 1.295 + // panels will roll up. If the click is inside a popup, they will not roll up 1.296 + uint32_t count = 0, sameTypeCount = 0; 1.297 + 1.298 + NS_ASSERTION(aWidgetChain, "null parameter"); 1.299 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.300 + while (item) { 1.301 + nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget(); 1.302 + NS_ASSERTION(widget, "open popup has no widget"); 1.303 + aWidgetChain->AppendElement(widget.get()); 1.304 + // In the case when a menulist inside a panel is open, clicking in the 1.305 + // panel should still roll up the menu, so if a different type is found, 1.306 + // stop scanning. 1.307 + nsMenuChainItem* parent = item->GetParent(); 1.308 + if (!sameTypeCount) { 1.309 + count++; 1.310 + if (!parent || item->Frame()->PopupType() != parent->Frame()->PopupType() || 1.311 + item->IsContextMenu() != parent->IsContextMenu()) { 1.312 + sameTypeCount = count; 1.313 + } 1.314 + } 1.315 + item = parent; 1.316 + } 1.317 + 1.318 + return sameTypeCount; 1.319 +} 1.320 + 1.321 +nsIWidget* 1.322 +nsXULPopupManager::GetRollupWidget() 1.323 +{ 1.324 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.325 + return item ? item->Frame()->GetWidget() : nullptr; 1.326 +} 1.327 + 1.328 +void 1.329 +nsXULPopupManager::AdjustPopupsOnWindowChange(nsPIDOMWindow* aWindow) 1.330 +{ 1.331 + // When the parent window is moved, adjust any child popups. Dismissable 1.332 + // menus and panels are expected to roll up when a window is moved, so there 1.333 + // is no need to check these popups, only the noautohide popups. 1.334 + 1.335 + // The items are added to a list so that they can be adjusted bottom to top. 1.336 + nsTArray<nsMenuPopupFrame *> list; 1.337 + 1.338 + nsMenuChainItem* item = mNoHidePanels; 1.339 + while (item) { 1.340 + // only move popups that are within the same window and where auto 1.341 + // positioning has not been disabled 1.342 + nsMenuPopupFrame* frame = item->Frame(); 1.343 + if (frame->GetAutoPosition()) { 1.344 + nsIContent* popup = frame->GetContent(); 1.345 + if (popup) { 1.346 + nsIDocument* document = popup->GetCurrentDoc(); 1.347 + if (document) { 1.348 + nsPIDOMWindow* window = document->GetWindow(); 1.349 + if (window) { 1.350 + window = window->GetPrivateRoot(); 1.351 + if (window == aWindow) { 1.352 + list.AppendElement(frame); 1.353 + } 1.354 + } 1.355 + } 1.356 + } 1.357 + } 1.358 + 1.359 + item = item->GetParent(); 1.360 + } 1.361 + 1.362 + for (int32_t l = list.Length() - 1; l >= 0; l--) { 1.363 + list[l]->SetPopupPosition(nullptr, true, false); 1.364 + } 1.365 +} 1.366 + 1.367 +void nsXULPopupManager::AdjustPopupsOnWindowChange(nsIPresShell* aPresShell) 1.368 +{ 1.369 + if (aPresShell->GetDocument()) { 1.370 + AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow()); 1.371 + } 1.372 +} 1.373 + 1.374 +static 1.375 +nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame) 1.376 +{ 1.377 + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame); 1.378 + if (!menuPopupFrame) 1.379 + return nullptr; 1.380 + 1.381 + // no point moving or resizing hidden popups 1.382 + if (menuPopupFrame->PopupState() != ePopupOpenAndVisible) 1.383 + return nullptr; 1.384 + 1.385 + nsIWidget* widget = menuPopupFrame->GetWidget(); 1.386 + if (widget && !widget->IsVisible()) 1.387 + return nullptr; 1.388 + 1.389 + return menuPopupFrame; 1.390 +} 1.391 + 1.392 +void 1.393 +nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt) 1.394 +{ 1.395 + nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame); 1.396 + if (!menuPopupFrame) 1.397 + return; 1.398 + 1.399 + nsView* view = menuPopupFrame->GetView(); 1.400 + if (!view) 1.401 + return; 1.402 + 1.403 + // Don't do anything if the popup is already at the specified location. This 1.404 + // prevents recursive calls when a popup is positioned. 1.405 + nsIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup); 1.406 + nsIWidget* widget = menuPopupFrame->GetWidget(); 1.407 + if (curDevSize.x == aPnt.x && curDevSize.y == aPnt.y && 1.408 + (!widget || widget->GetClientOffset() == menuPopupFrame->GetLastClientOffset())) { 1.409 + return; 1.410 + } 1.411 + 1.412 + // Update the popup's position using SetPopupPosition if the popup is 1.413 + // anchored and at the parent level as these maintain their position 1.414 + // relative to the parent window. Otherwise, just update the popup to 1.415 + // the specified screen coordinates. 1.416 + if (menuPopupFrame->IsAnchored() && 1.417 + menuPopupFrame->PopupLevel() == ePopupLevelParent) { 1.418 + menuPopupFrame->SetPopupPosition(nullptr, true, false); 1.419 + } 1.420 + else { 1.421 + nsPresContext* presContext = menuPopupFrame->PresContext(); 1.422 + aPnt.x = presContext->DevPixelsToIntCSSPixels(aPnt.x); 1.423 + aPnt.y = presContext->DevPixelsToIntCSSPixels(aPnt.y); 1.424 + menuPopupFrame->MoveTo(aPnt.x, aPnt.y, false); 1.425 + } 1.426 +} 1.427 + 1.428 +void 1.429 +nsXULPopupManager::PopupResized(nsIFrame* aFrame, nsIntSize aSize) 1.430 +{ 1.431 + nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame); 1.432 + if (!menuPopupFrame) 1.433 + return; 1.434 + 1.435 + nsView* view = menuPopupFrame->GetView(); 1.436 + if (!view) 1.437 + return; 1.438 + 1.439 + nsIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup); 1.440 + // If the size is what we think it is, we have nothing to do. 1.441 + if (curDevSize.width == aSize.width && curDevSize.height == aSize.height) 1.442 + return; 1.443 + 1.444 + // The size is different. Convert the actual size to css pixels and store it 1.445 + // as 'width' and 'height' attributes on the popup. 1.446 + nsPresContext* presContext = menuPopupFrame->PresContext(); 1.447 + 1.448 + nsIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width), 1.449 + presContext->DevPixelsToIntCSSPixels(aSize.height)); 1.450 + 1.451 + nsIContent* popup = menuPopupFrame->GetContent(); 1.452 + nsAutoString width, height; 1.453 + width.AppendInt(newCSS.width); 1.454 + height.AppendInt(newCSS.height); 1.455 + popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false); 1.456 + popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true); 1.457 +} 1.458 + 1.459 +nsMenuPopupFrame* 1.460 +nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush) 1.461 +{ 1.462 + if (aShouldFlush) { 1.463 + nsIDocument *document = aContent->GetCurrentDoc(); 1.464 + if (document) { 1.465 + nsCOMPtr<nsIPresShell> presShell = document->GetShell(); 1.466 + if (presShell) 1.467 + presShell->FlushPendingNotifications(Flush_Layout); 1.468 + } 1.469 + } 1.470 + 1.471 + return do_QueryFrame(aContent->GetPrimaryFrame()); 1.472 +} 1.473 + 1.474 +nsMenuChainItem* 1.475 +nsXULPopupManager::GetTopVisibleMenu() 1.476 +{ 1.477 + nsMenuChainItem* item = mPopups; 1.478 + while (item && item->Frame()->PopupState() == ePopupInvisible) 1.479 + item = item->GetParent(); 1.480 + return item; 1.481 +} 1.482 + 1.483 +void 1.484 +nsXULPopupManager::GetMouseLocation(nsIDOMNode** aNode, int32_t* aOffset) 1.485 +{ 1.486 + *aNode = mRangeParent; 1.487 + NS_IF_ADDREF(*aNode); 1.488 + *aOffset = mRangeOffset; 1.489 +} 1.490 + 1.491 +void 1.492 +nsXULPopupManager::InitTriggerEvent(nsIDOMEvent* aEvent, nsIContent* aPopup, 1.493 + nsIContent** aTriggerContent) 1.494 +{ 1.495 + mCachedMousePoint = nsIntPoint(0, 0); 1.496 + 1.497 + if (aTriggerContent) { 1.498 + *aTriggerContent = nullptr; 1.499 + if (aEvent) { 1.500 + // get the trigger content from the event 1.501 + nsCOMPtr<nsIContent> target = do_QueryInterface( 1.502 + aEvent->InternalDOMEvent()->GetTarget()); 1.503 + target.forget(aTriggerContent); 1.504 + } 1.505 + } 1.506 + 1.507 + mCachedModifiers = 0; 1.508 + 1.509 + nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aEvent); 1.510 + if (uiEvent) { 1.511 + uiEvent->GetRangeParent(getter_AddRefs(mRangeParent)); 1.512 + uiEvent->GetRangeOffset(&mRangeOffset); 1.513 + 1.514 + // get the event coordinates relative to the root frame of the document 1.515 + // containing the popup. 1.516 + NS_ASSERTION(aPopup, "Expected a popup node"); 1.517 + WidgetEvent* event = aEvent->GetInternalNSEvent(); 1.518 + if (event) { 1.519 + WidgetInputEvent* inputEvent = event->AsInputEvent(); 1.520 + if (inputEvent) { 1.521 + mCachedModifiers = inputEvent->modifiers; 1.522 + } 1.523 + nsIDocument* doc = aPopup->GetCurrentDoc(); 1.524 + if (doc) { 1.525 + nsIPresShell* presShell = doc->GetShell(); 1.526 + nsPresContext* presContext; 1.527 + if (presShell && (presContext = presShell->GetPresContext())) { 1.528 + nsPresContext* rootDocPresContext = 1.529 + presContext->GetRootPresContext(); 1.530 + if (!rootDocPresContext) 1.531 + return; 1.532 + nsIFrame* rootDocumentRootFrame = rootDocPresContext-> 1.533 + PresShell()->FrameManager()->GetRootFrame(); 1.534 + if ((event->eventStructType == NS_MOUSE_EVENT || 1.535 + event->eventStructType == NS_MOUSE_SCROLL_EVENT || 1.536 + event->eventStructType == NS_WHEEL_EVENT) && 1.537 + !event->AsGUIEvent()->widget) { 1.538 + // no widget, so just use the client point if available 1.539 + nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent); 1.540 + nsIntPoint clientPt; 1.541 + mouseEvent->GetClientX(&clientPt.x); 1.542 + mouseEvent->GetClientY(&clientPt.y); 1.543 + 1.544 + // XXX this doesn't handle IFRAMEs in transforms 1.545 + nsPoint thisDocToRootDocOffset = presShell->FrameManager()-> 1.546 + GetRootFrame()->GetOffsetToCrossDoc(rootDocumentRootFrame); 1.547 + // convert to device pixels 1.548 + mCachedMousePoint.x = presContext->AppUnitsToDevPixels( 1.549 + nsPresContext::CSSPixelsToAppUnits(clientPt.x) + thisDocToRootDocOffset.x); 1.550 + mCachedMousePoint.y = presContext->AppUnitsToDevPixels( 1.551 + nsPresContext::CSSPixelsToAppUnits(clientPt.y) + thisDocToRootDocOffset.y); 1.552 + } 1.553 + else if (rootDocumentRootFrame) { 1.554 + nsPoint pnt = 1.555 + nsLayoutUtils::GetEventCoordinatesRelativeTo(event, rootDocumentRootFrame); 1.556 + mCachedMousePoint = nsIntPoint(rootDocPresContext->AppUnitsToDevPixels(pnt.x), 1.557 + rootDocPresContext->AppUnitsToDevPixels(pnt.y)); 1.558 + } 1.559 + } 1.560 + } 1.561 + } 1.562 + } 1.563 + else { 1.564 + mRangeParent = nullptr; 1.565 + mRangeOffset = 0; 1.566 + } 1.567 +} 1.568 + 1.569 +void 1.570 +nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate) 1.571 +{ 1.572 + if (aActivate) 1.573 + mActiveMenuBar = aMenuBar; 1.574 + else if (mActiveMenuBar == aMenuBar) 1.575 + mActiveMenuBar = nullptr; 1.576 + 1.577 + UpdateKeyboardListeners(); 1.578 +} 1.579 + 1.580 +void 1.581 +nsXULPopupManager::ShowMenu(nsIContent *aMenu, 1.582 + bool aSelectFirstItem, 1.583 + bool aAsynchronous) 1.584 +{ 1.585 + // generate any template content first. Otherwise, the menupopup may not 1.586 + // have been created yet. 1.587 + if (aMenu) { 1.588 + nsIContent* element = aMenu; 1.589 + do { 1.590 + nsCOMPtr<nsIDOMXULElement> xulelem = do_QueryInterface(element); 1.591 + if (xulelem) { 1.592 + nsCOMPtr<nsIXULTemplateBuilder> builder; 1.593 + xulelem->GetBuilder(getter_AddRefs(builder)); 1.594 + if (builder) { 1.595 + builder->CreateContents(aMenu, true); 1.596 + break; 1.597 + } 1.598 + } 1.599 + element = element->GetParent(); 1.600 + } while (element); 1.601 + } 1.602 + 1.603 + nsMenuFrame* menuFrame = do_QueryFrame(aMenu->GetPrimaryFrame()); 1.604 + if (!menuFrame || !menuFrame->IsMenu()) 1.605 + return; 1.606 + 1.607 + nsMenuPopupFrame* popupFrame = menuFrame->GetPopup(); 1.608 + if (!popupFrame || !MayShowPopup(popupFrame)) 1.609 + return; 1.610 + 1.611 + // inherit whether or not we're a context menu from the parent 1.612 + bool parentIsContextMenu = false; 1.613 + bool onMenuBar = false; 1.614 + bool onmenu = menuFrame->IsOnMenu(); 1.615 + 1.616 + nsMenuParent* parent = menuFrame->GetMenuParent(); 1.617 + if (parent && onmenu) { 1.618 + parentIsContextMenu = parent->IsContextMenu(); 1.619 + onMenuBar = parent->IsMenuBar(); 1.620 + } 1.621 + 1.622 + nsAutoString position; 1.623 + if (onMenuBar || !onmenu) 1.624 + position.AssignLiteral("after_start"); 1.625 + else 1.626 + position.AssignLiteral("end_before"); 1.627 + 1.628 + // there is no trigger event for menus 1.629 + InitTriggerEvent(nullptr, nullptr, nullptr); 1.630 + popupFrame->InitializePopup(menuFrame->GetAnchor(), nullptr, position, 0, 0, true); 1.631 + 1.632 + if (aAsynchronous) { 1.633 + nsCOMPtr<nsIRunnable> event = 1.634 + new nsXULPopupShowingEvent(popupFrame->GetContent(), 1.635 + parentIsContextMenu, aSelectFirstItem); 1.636 + NS_DispatchToCurrentThread(event); 1.637 + } 1.638 + else { 1.639 + nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent(); 1.640 + FirePopupShowingEvent(popupContent, parentIsContextMenu, aSelectFirstItem); 1.641 + } 1.642 +} 1.643 + 1.644 +void 1.645 +nsXULPopupManager::ShowPopup(nsIContent* aPopup, 1.646 + nsIContent* aAnchorContent, 1.647 + const nsAString& aPosition, 1.648 + int32_t aXPos, int32_t aYPos, 1.649 + bool aIsContextMenu, 1.650 + bool aAttributesOverride, 1.651 + bool aSelectFirstItem, 1.652 + nsIDOMEvent* aTriggerEvent) 1.653 +{ 1.654 + nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); 1.655 + if (!popupFrame || !MayShowPopup(popupFrame)) 1.656 + return; 1.657 + 1.658 + nsCOMPtr<nsIContent> triggerContent; 1.659 + InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent)); 1.660 + 1.661 + popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition, 1.662 + aXPos, aYPos, aAttributesOverride); 1.663 + 1.664 + FirePopupShowingEvent(aPopup, aIsContextMenu, aSelectFirstItem); 1.665 +} 1.666 + 1.667 +void 1.668 +nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup, 1.669 + int32_t aXPos, int32_t aYPos, 1.670 + bool aIsContextMenu, 1.671 + nsIDOMEvent* aTriggerEvent) 1.672 +{ 1.673 + nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); 1.674 + if (!popupFrame || !MayShowPopup(popupFrame)) 1.675 + return; 1.676 + 1.677 + nsCOMPtr<nsIContent> triggerContent; 1.678 + InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent)); 1.679 + 1.680 + popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos, aIsContextMenu); 1.681 + FirePopupShowingEvent(aPopup, aIsContextMenu, false); 1.682 +} 1.683 + 1.684 +void 1.685 +nsXULPopupManager::ShowTooltipAtScreen(nsIContent* aPopup, 1.686 + nsIContent* aTriggerContent, 1.687 + int32_t aXPos, int32_t aYPos) 1.688 +{ 1.689 + nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); 1.690 + if (!popupFrame || !MayShowPopup(popupFrame)) 1.691 + return; 1.692 + 1.693 + InitTriggerEvent(nullptr, nullptr, nullptr); 1.694 + 1.695 + nsPresContext* pc = popupFrame->PresContext(); 1.696 + mCachedMousePoint = nsIntPoint(pc->CSSPixelsToDevPixels(aXPos), 1.697 + pc->CSSPixelsToDevPixels(aYPos)); 1.698 + 1.699 + // coordinates are relative to the root widget 1.700 + nsPresContext* rootPresContext = pc->GetRootPresContext(); 1.701 + if (rootPresContext) { 1.702 + nsIWidget *rootWidget = rootPresContext->GetRootWidget(); 1.703 + if (rootWidget) { 1.704 + mCachedMousePoint -= rootWidget->WidgetToScreenOffset(); 1.705 + } 1.706 + } 1.707 + 1.708 + popupFrame->InitializePopupAtScreen(aTriggerContent, aXPos, aYPos, false); 1.709 + 1.710 + FirePopupShowingEvent(aPopup, false, false); 1.711 +} 1.712 + 1.713 +void 1.714 +nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup, 1.715 + nsIContent* aAnchorContent, 1.716 + nsAString& aAnchor, 1.717 + nsAString& aAlign, 1.718 + int32_t aXPos, int32_t aYPos, 1.719 + bool aIsContextMenu) 1.720 +{ 1.721 + nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true); 1.722 + if (!popupFrame || !MayShowPopup(popupFrame)) 1.723 + return; 1.724 + 1.725 + InitTriggerEvent(nullptr, nullptr, nullptr); 1.726 + 1.727 + popupFrame->InitializePopupWithAnchorAlign(aAnchorContent, aAnchor, 1.728 + aAlign, aXPos, aYPos); 1.729 + FirePopupShowingEvent(aPopup, aIsContextMenu, false); 1.730 +} 1.731 + 1.732 +static void 1.733 +CheckCaretDrawingState() { 1.734 + 1.735 + // There is 1 caret per document, we need to find the focused 1.736 + // document and erase its caret. 1.737 + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); 1.738 + if (fm) { 1.739 + nsCOMPtr<nsIDOMWindow> window; 1.740 + fm->GetFocusedWindow(getter_AddRefs(window)); 1.741 + if (!window) 1.742 + return; 1.743 + 1.744 + nsCOMPtr<nsIDOMDocument> domDoc; 1.745 + nsCOMPtr<nsIDocument> focusedDoc; 1.746 + window->GetDocument(getter_AddRefs(domDoc)); 1.747 + focusedDoc = do_QueryInterface(domDoc); 1.748 + if (!focusedDoc) 1.749 + return; 1.750 + 1.751 + nsIPresShell* presShell = focusedDoc->GetShell(); 1.752 + if (!presShell) 1.753 + return; 1.754 + 1.755 + nsRefPtr<nsCaret> caret = presShell->GetCaret(); 1.756 + if (!caret) 1.757 + return; 1.758 + caret->CheckCaretDrawingState(); 1.759 + } 1.760 +} 1.761 + 1.762 +void 1.763 +nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup, 1.764 + nsMenuPopupFrame* aPopupFrame, 1.765 + bool aIsContextMenu, 1.766 + bool aSelectFirstItem) 1.767 +{ 1.768 + nsPopupType popupType = aPopupFrame->PopupType(); 1.769 + bool ismenu = (popupType == ePopupTypeMenu); 1.770 + 1.771 + nsMenuChainItem* item = 1.772 + new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType); 1.773 + if (!item) 1.774 + return; 1.775 + 1.776 + // install keyboard event listeners for navigating menus. For panels, the 1.777 + // escape key may be used to close the panel. However, the ignorekeys 1.778 + // attribute may be used to disable adding these event listeners for popups 1.779 + // that want to handle their own keyboard events. 1.780 + if (aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ignorekeys, 1.781 + nsGkAtoms::_true, eCaseMatters)) 1.782 + item->SetIgnoreKeys(true); 1.783 + 1.784 + if (ismenu) { 1.785 + // if the menu is on a menubar, use the menubar's listener instead 1.786 + nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent()); 1.787 + if (menuFrame) { 1.788 + item->SetOnMenuBar(menuFrame->IsOnMenuBar()); 1.789 + } 1.790 + } 1.791 + 1.792 + // use a weak frame as the popup will set an open attribute if it is a menu 1.793 + nsWeakFrame weakFrame(aPopupFrame); 1.794 + aPopupFrame->ShowPopup(aIsContextMenu, aSelectFirstItem); 1.795 + ENSURE_TRUE(weakFrame.IsAlive()); 1.796 + 1.797 + // popups normally hide when an outside click occurs. Panels may use 1.798 + // the noautohide attribute to disable this behaviour. It is expected 1.799 + // that the application will hide these popups manually. The tooltip 1.800 + // listener will handle closing the tooltip also. 1.801 + if (aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip) { 1.802 + item->SetParent(mNoHidePanels); 1.803 + mNoHidePanels = item; 1.804 + } 1.805 + else { 1.806 + nsIContent* oldmenu = nullptr; 1.807 + if (mPopups) 1.808 + oldmenu = mPopups->Content(); 1.809 + item->SetParent(mPopups); 1.810 + mPopups = item; 1.811 + SetCaptureState(oldmenu); 1.812 + } 1.813 + 1.814 + if (aSelectFirstItem) { 1.815 + nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true); 1.816 + aPopupFrame->SetCurrentMenuItem(next); 1.817 + } 1.818 + 1.819 + if (ismenu) 1.820 + UpdateMenuItems(aPopup); 1.821 + 1.822 + // Caret visibility may have been affected, ensure that 1.823 + // the caret isn't now drawn when it shouldn't be. 1.824 + CheckCaretDrawingState(); 1.825 +} 1.826 + 1.827 +void 1.828 +nsXULPopupManager::HidePopup(nsIContent* aPopup, 1.829 + bool aHideChain, 1.830 + bool aDeselectMenu, 1.831 + bool aAsynchronous, 1.832 + bool aIsRollup, 1.833 + nsIContent* aLastPopup) 1.834 +{ 1.835 + // if the popup is on the nohide panels list, remove it but don't close any 1.836 + // other panels 1.837 + nsMenuPopupFrame* popupFrame = nullptr; 1.838 + bool foundPanel = false; 1.839 + nsMenuChainItem* item = mNoHidePanels; 1.840 + while (item) { 1.841 + if (item->Content() == aPopup) { 1.842 + foundPanel = true; 1.843 + popupFrame = item->Frame(); 1.844 + break; 1.845 + } 1.846 + item = item->GetParent(); 1.847 + } 1.848 + 1.849 + // when removing a menu, all of the child popups must be closed 1.850 + nsMenuChainItem* foundMenu = nullptr; 1.851 + item = mPopups; 1.852 + while (item) { 1.853 + if (item->Content() == aPopup) { 1.854 + foundMenu = item; 1.855 + break; 1.856 + } 1.857 + item = item->GetParent(); 1.858 + } 1.859 + 1.860 + nsPopupType type = ePopupTypePanel; 1.861 + bool deselectMenu = false; 1.862 + nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup; 1.863 + if (foundMenu) { 1.864 + // at this point, foundMenu will be set to the found item in the list. If 1.865 + // foundMenu is the topmost menu, the one to remove, then there are no other 1.866 + // popups to hide. If foundMenu is not the topmost menu, then there may be 1.867 + // open submenus below it. In this case, we need to make sure that those 1.868 + // submenus are closed up first. To do this, we scan up the menu list to 1.869 + // find the topmost popup with only menus between it and foundMenu and 1.870 + // close that menu first. In synchronous mode, the FirePopupHidingEvent 1.871 + // method will be called which in turn calls HidePopupCallback to close up 1.872 + // the next popup in the chain. These two methods will be called in 1.873 + // sequence recursively to close up all the necessary popups. In 1.874 + // asynchronous mode, a similar process occurs except that the 1.875 + // FirePopupHidingEvent method is called asynchronously. In either case, 1.876 + // nextPopup is set to the content node of the next popup to close, and 1.877 + // lastPopup is set to the last popup in the chain to close, which will be 1.878 + // aPopup, or null to close up all menus. 1.879 + 1.880 + nsMenuChainItem* topMenu = foundMenu; 1.881 + // Use IsMenu to ensure that foundMenu is a menu and scan down the child 1.882 + // list until a non-menu is found. If foundMenu isn't a menu at all, don't 1.883 + // scan and just close up this menu. 1.884 + if (foundMenu->IsMenu()) { 1.885 + item = topMenu->GetChild(); 1.886 + while (item && item->IsMenu()) { 1.887 + topMenu = item; 1.888 + item = item->GetChild(); 1.889 + } 1.890 + } 1.891 + 1.892 + deselectMenu = aDeselectMenu; 1.893 + popupToHide = topMenu->Content(); 1.894 + popupFrame = topMenu->Frame(); 1.895 + type = popupFrame->PopupType(); 1.896 + 1.897 + nsMenuChainItem* parent = topMenu->GetParent(); 1.898 + 1.899 + // close up another popup if there is one, and we are either hiding the 1.900 + // entire chain or the item to hide isn't the topmost popup. 1.901 + if (parent && (aHideChain || topMenu != foundMenu)) 1.902 + nextPopup = parent->Content(); 1.903 + 1.904 + lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup); 1.905 + } 1.906 + else if (foundPanel) { 1.907 + popupToHide = aPopup; 1.908 + } 1.909 + 1.910 + if (popupFrame) { 1.911 + nsPopupState state = popupFrame->PopupState(); 1.912 + // if the popup is already being hidden, don't attempt to hide it again 1.913 + if (state == ePopupHiding) 1.914 + return; 1.915 + // change the popup state to hiding. Don't set the hiding state if the 1.916 + // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will 1.917 + // run again. In the invisible state, we just want the events to fire. 1.918 + if (state != ePopupInvisible) 1.919 + popupFrame->SetPopupState(ePopupHiding); 1.920 + 1.921 + // for menus, popupToHide is always the frontmost item in the list to hide. 1.922 + if (aAsynchronous) { 1.923 + nsCOMPtr<nsIRunnable> event = 1.924 + new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup, 1.925 + type, deselectMenu, aIsRollup); 1.926 + NS_DispatchToCurrentThread(event); 1.927 + } 1.928 + else { 1.929 + FirePopupHidingEvent(popupToHide, nextPopup, lastPopup, 1.930 + popupFrame->PresContext(), type, deselectMenu, aIsRollup); 1.931 + } 1.932 + } 1.933 +} 1.934 + 1.935 +// This is used to hide the popup after a transition finishes. 1.936 +class TransitionEnder : public nsIDOMEventListener 1.937 +{ 1.938 +public: 1.939 + 1.940 + nsCOMPtr<nsIContent> mContent; 1.941 + bool mDeselectMenu; 1.942 + 1.943 + NS_DECL_CYCLE_COLLECTING_ISUPPORTS 1.944 + NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder) 1.945 + 1.946 + TransitionEnder(nsIContent* aContent, bool aDeselectMenu) 1.947 + : mContent(aContent), mDeselectMenu(aDeselectMenu) 1.948 + { 1.949 + } 1.950 + 1.951 + virtual ~TransitionEnder() { } 1.952 + 1.953 + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) MOZ_OVERRIDE 1.954 + { 1.955 + mContent->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false); 1.956 + 1.957 + nsMenuPopupFrame* popupFrame = do_QueryFrame(mContent->GetPrimaryFrame()); 1.958 + 1.959 + // Now hide the popup. There could be other properties transitioning, but 1.960 + // we'll assume they all end at the same time and just hide the popup upon 1.961 + // the first one ending. 1.962 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.963 + if (pm && popupFrame) { 1.964 + pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr, 1.965 + popupFrame->PopupType(), mDeselectMenu); 1.966 + } 1.967 + 1.968 + return NS_OK; 1.969 + } 1.970 +}; 1.971 + 1.972 +NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder) 1.973 +NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder) 1.974 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder) 1.975 + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) 1.976 + NS_INTERFACE_MAP_ENTRY(nsISupports) 1.977 +NS_INTERFACE_MAP_END 1.978 + 1.979 +NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent); 1.980 + 1.981 +void 1.982 +nsXULPopupManager::HidePopupCallback(nsIContent* aPopup, 1.983 + nsMenuPopupFrame* aPopupFrame, 1.984 + nsIContent* aNextPopup, 1.985 + nsIContent* aLastPopup, 1.986 + nsPopupType aPopupType, 1.987 + bool aDeselectMenu) 1.988 +{ 1.989 + if (mCloseTimer && mTimerMenu == aPopupFrame) { 1.990 + mCloseTimer->Cancel(); 1.991 + mCloseTimer = nullptr; 1.992 + mTimerMenu = nullptr; 1.993 + } 1.994 + 1.995 + // The popup to hide is aPopup. Search the list again to find the item that 1.996 + // corresponds to the popup to hide aPopup. This is done because it's 1.997 + // possible someone added another item (attempted to open another popup) 1.998 + // or removed a popup frame during the event processing so the item isn't at 1.999 + // the front anymore. 1.1000 + nsMenuChainItem* item = mNoHidePanels; 1.1001 + while (item) { 1.1002 + if (item->Content() == aPopup) { 1.1003 + item->Detach(&mNoHidePanels); 1.1004 + break; 1.1005 + } 1.1006 + item = item->GetParent(); 1.1007 + } 1.1008 + 1.1009 + if (!item) { 1.1010 + item = mPopups; 1.1011 + while (item) { 1.1012 + if (item->Content() == aPopup) { 1.1013 + item->Detach(&mPopups); 1.1014 + SetCaptureState(aPopup); 1.1015 + break; 1.1016 + } 1.1017 + item = item->GetParent(); 1.1018 + } 1.1019 + } 1.1020 + 1.1021 + delete item; 1.1022 + 1.1023 + nsWeakFrame weakFrame(aPopupFrame); 1.1024 + aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed); 1.1025 + ENSURE_TRUE(weakFrame.IsAlive()); 1.1026 + 1.1027 + // send the popuphidden event synchronously. This event has no default 1.1028 + // behaviour. 1.1029 + nsEventStatus status = nsEventStatus_eIgnore; 1.1030 + WidgetMouseEvent event(true, NS_XUL_POPUP_HIDDEN, nullptr, 1.1031 + WidgetMouseEvent::eReal); 1.1032 + EventDispatcher::Dispatch(aPopup, aPopupFrame->PresContext(), 1.1033 + &event, nullptr, &status); 1.1034 + ENSURE_TRUE(weakFrame.IsAlive()); 1.1035 + 1.1036 + // if there are more popups to close, look for the next one 1.1037 + if (aNextPopup && aPopup != aLastPopup) { 1.1038 + nsMenuChainItem* foundMenu = nullptr; 1.1039 + nsMenuChainItem* item = mPopups; 1.1040 + while (item) { 1.1041 + if (item->Content() == aNextPopup) { 1.1042 + foundMenu = item; 1.1043 + break; 1.1044 + } 1.1045 + item = item->GetParent(); 1.1046 + } 1.1047 + 1.1048 + // continue hiding the chain of popups until the last popup aLastPopup 1.1049 + // is reached, or until a popup of a different type is reached. This 1.1050 + // last check is needed so that a menulist inside a non-menu panel only 1.1051 + // closes the menu and not the panel as well. 1.1052 + if (foundMenu && 1.1053 + (aLastPopup || aPopupType == foundMenu->PopupType())) { 1.1054 + 1.1055 + nsCOMPtr<nsIContent> popupToHide = item->Content(); 1.1056 + nsMenuChainItem* parent = item->GetParent(); 1.1057 + 1.1058 + nsCOMPtr<nsIContent> nextPopup; 1.1059 + if (parent && popupToHide != aLastPopup) 1.1060 + nextPopup = parent->Content(); 1.1061 + 1.1062 + nsMenuPopupFrame* popupFrame = item->Frame(); 1.1063 + nsPopupState state = popupFrame->PopupState(); 1.1064 + if (state == ePopupHiding) 1.1065 + return; 1.1066 + if (state != ePopupInvisible) 1.1067 + popupFrame->SetPopupState(ePopupHiding); 1.1068 + 1.1069 + FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup, 1.1070 + popupFrame->PresContext(), 1.1071 + foundMenu->PopupType(), aDeselectMenu, false); 1.1072 + } 1.1073 + } 1.1074 +} 1.1075 + 1.1076 +void 1.1077 +nsXULPopupManager::HidePopup(nsIFrame* aFrame) 1.1078 +{ 1.1079 + nsMenuPopupFrame* popup = do_QueryFrame(aFrame); 1.1080 + if (popup) 1.1081 + HidePopup(aFrame->GetContent(), false, true, false, false); 1.1082 +} 1.1083 + 1.1084 +void 1.1085 +nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup) 1.1086 +{ 1.1087 + // Don't close up immediately. 1.1088 + // Kick off a close timer. 1.1089 + KillMenuTimer(); 1.1090 + 1.1091 + int32_t menuDelay = 1.1092 + LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms 1.1093 + 1.1094 + // Kick off the timer. 1.1095 + mCloseTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.1096 + mCloseTimer->InitWithCallback(this, menuDelay, nsITimer::TYPE_ONE_SHOT); 1.1097 + 1.1098 + // the popup will call PopupDestroyed if it is destroyed, which checks if it 1.1099 + // is set to mTimerMenu, so it should be safe to keep a reference to it 1.1100 + mTimerMenu = aPopup; 1.1101 +} 1.1102 + 1.1103 +void 1.1104 +nsXULPopupManager::HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames, 1.1105 + bool aDeselectMenu) 1.1106 +{ 1.1107 + // Create a weak frame list. This is done in a separate array with the 1.1108 + // right capacity predetermined, otherwise the array would get resized and 1.1109 + // move the weak frame pointers around. 1.1110 + nsTArray<nsWeakFrame> weakPopups(aFrames.Length()); 1.1111 + uint32_t f; 1.1112 + for (f = 0; f < aFrames.Length(); f++) { 1.1113 + nsWeakFrame* wframe = weakPopups.AppendElement(); 1.1114 + if (wframe) 1.1115 + *wframe = aFrames[f]; 1.1116 + } 1.1117 + 1.1118 + for (f = 0; f < weakPopups.Length(); f++) { 1.1119 + // check to ensure that the frame is still alive before hiding it. 1.1120 + if (weakPopups[f].IsAlive()) { 1.1121 + nsMenuPopupFrame* frame = 1.1122 + static_cast<nsMenuPopupFrame *>(weakPopups[f].GetFrame()); 1.1123 + frame->HidePopup(true, ePopupInvisible); 1.1124 + } 1.1125 + } 1.1126 + 1.1127 + SetCaptureState(nullptr); 1.1128 +} 1.1129 + 1.1130 +bool 1.1131 +nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected) 1.1132 +{ 1.1133 + nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell()); 1.1134 + while(docShellItem) { 1.1135 + if (docShellItem == aExpected) 1.1136 + return true; 1.1137 + 1.1138 + nsCOMPtr<nsIDocShellTreeItem> parent; 1.1139 + docShellItem->GetParent(getter_AddRefs(parent)); 1.1140 + docShellItem = parent; 1.1141 + } 1.1142 + 1.1143 + return false; 1.1144 +} 1.1145 + 1.1146 +void 1.1147 +nsXULPopupManager::HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide) 1.1148 +{ 1.1149 + nsTArray<nsMenuPopupFrame *> popupsToHide; 1.1150 + 1.1151 + // iterate to get the set of popup frames to hide 1.1152 + nsMenuChainItem* item = mPopups; 1.1153 + while (item) { 1.1154 + nsMenuChainItem* parent = item->GetParent(); 1.1155 + if (item->Frame()->PopupState() != ePopupInvisible && 1.1156 + IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) { 1.1157 + nsMenuPopupFrame* frame = item->Frame(); 1.1158 + item->Detach(&mPopups); 1.1159 + delete item; 1.1160 + popupsToHide.AppendElement(frame); 1.1161 + } 1.1162 + item = parent; 1.1163 + } 1.1164 + 1.1165 + // now look for panels to hide 1.1166 + item = mNoHidePanels; 1.1167 + while (item) { 1.1168 + nsMenuChainItem* parent = item->GetParent(); 1.1169 + if (item->Frame()->PopupState() != ePopupInvisible && 1.1170 + IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) { 1.1171 + nsMenuPopupFrame* frame = item->Frame(); 1.1172 + item->Detach(&mNoHidePanels); 1.1173 + delete item; 1.1174 + popupsToHide.AppendElement(frame); 1.1175 + } 1.1176 + item = parent; 1.1177 + } 1.1178 + 1.1179 + HidePopupsInList(popupsToHide, true); 1.1180 +} 1.1181 + 1.1182 +void 1.1183 +nsXULPopupManager::ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent) 1.1184 +{ 1.1185 + CloseMenuMode cmm = CloseMenuMode_Auto; 1.1186 + 1.1187 + static nsIContent::AttrValuesArray strings[] = 1.1188 + {&nsGkAtoms::none, &nsGkAtoms::single, nullptr}; 1.1189 + 1.1190 + switch (aMenu->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::closemenu, 1.1191 + strings, eCaseMatters)) { 1.1192 + case 0: 1.1193 + cmm = CloseMenuMode_None; 1.1194 + break; 1.1195 + case 1: 1.1196 + cmm = CloseMenuMode_Single; 1.1197 + break; 1.1198 + default: 1.1199 + break; 1.1200 + } 1.1201 + 1.1202 + // When a menuitem is selected to be executed, first hide all the open 1.1203 + // popups, but don't remove them yet. This is needed when a menu command 1.1204 + // opens a modal dialog. The views associated with the popups needed to be 1.1205 + // hidden and the accesibility events fired before the command executes, but 1.1206 + // the popuphiding/popuphidden events are fired afterwards. 1.1207 + nsTArray<nsMenuPopupFrame *> popupsToHide; 1.1208 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.1209 + if (cmm != CloseMenuMode_None) { 1.1210 + while (item) { 1.1211 + // if it isn't a <menupopup>, don't close it automatically 1.1212 + if (!item->IsMenu()) 1.1213 + break; 1.1214 + nsMenuChainItem* next = item->GetParent(); 1.1215 + popupsToHide.AppendElement(item->Frame()); 1.1216 + if (cmm == CloseMenuMode_Single) // only close one level of menu 1.1217 + break; 1.1218 + item = next; 1.1219 + } 1.1220 + 1.1221 + // Now hide the popups. If the closemenu mode is auto, deselect the menu, 1.1222 + // otherwise only one popup is closing, so keep the parent menu selected. 1.1223 + HidePopupsInList(popupsToHide, cmm == CloseMenuMode_Auto); 1.1224 + } 1.1225 + 1.1226 + aEvent->SetCloseMenuMode(cmm); 1.1227 + nsCOMPtr<nsIRunnable> event = aEvent; 1.1228 + NS_DispatchToCurrentThread(event); 1.1229 +} 1.1230 + 1.1231 +void 1.1232 +nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup, 1.1233 + bool aIsContextMenu, 1.1234 + bool aSelectFirstItem) 1.1235 +{ 1.1236 + nsCOMPtr<nsIContent> popup = aPopup; // keep a strong reference to the popup 1.1237 + 1.1238 + nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); 1.1239 + if (!popupFrame) 1.1240 + return; 1.1241 + 1.1242 + nsPresContext *presContext = popupFrame->PresContext(); 1.1243 + nsCOMPtr<nsIPresShell> presShell = presContext->PresShell(); 1.1244 + nsPopupType popupType = popupFrame->PopupType(); 1.1245 + 1.1246 + // generate the child frames if they have not already been generated 1.1247 + if (!popupFrame->HasGeneratedChildren()) { 1.1248 + popupFrame->SetGeneratedChildren(); 1.1249 + presShell->FrameConstructor()->GenerateChildFrames(popupFrame); 1.1250 + } 1.1251 + 1.1252 + // get the frame again 1.1253 + nsIFrame* frame = aPopup->GetPrimaryFrame(); 1.1254 + if (!frame) 1.1255 + return; 1.1256 + 1.1257 + presShell->FrameNeedsReflow(frame, nsIPresShell::eTreeChange, 1.1258 + NS_FRAME_HAS_DIRTY_CHILDREN); 1.1259 + 1.1260 + // cache the popup so that document.popupNode can retrieve the trigger node 1.1261 + // during the popupshowing event. It will be cleared below after the event 1.1262 + // has fired. 1.1263 + mOpeningPopup = aPopup; 1.1264 + 1.1265 + nsEventStatus status = nsEventStatus_eIgnore; 1.1266 + WidgetMouseEvent event(true, NS_XUL_POPUP_SHOWING, nullptr, 1.1267 + WidgetMouseEvent::eReal); 1.1268 + 1.1269 + // coordinates are relative to the root widget 1.1270 + nsPresContext* rootPresContext = 1.1271 + presShell->GetPresContext()->GetRootPresContext(); 1.1272 + if (rootPresContext) { 1.1273 + rootPresContext->PresShell()->GetViewManager()-> 1.1274 + GetRootWidget(getter_AddRefs(event.widget)); 1.1275 + } 1.1276 + else { 1.1277 + event.widget = nullptr; 1.1278 + } 1.1279 + 1.1280 + event.refPoint = LayoutDeviceIntPoint::FromUntyped(mCachedMousePoint); 1.1281 + event.modifiers = mCachedModifiers; 1.1282 + EventDispatcher::Dispatch(popup, presContext, &event, nullptr, &status); 1.1283 + 1.1284 + mCachedMousePoint = nsIntPoint(0, 0); 1.1285 + mOpeningPopup = nullptr; 1.1286 + 1.1287 + mCachedModifiers = 0; 1.1288 + 1.1289 + // if a panel, blur whatever has focus so that the panel can take the focus. 1.1290 + // This is done after the popupshowing event in case that event is cancelled. 1.1291 + // Using noautofocus="true" will disable this behaviour, which is needed for 1.1292 + // the autocomplete widget as it manages focus itself. 1.1293 + if (popupType == ePopupTypePanel && 1.1294 + !popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus, 1.1295 + nsGkAtoms::_true, eCaseMatters)) { 1.1296 + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); 1.1297 + if (fm) { 1.1298 + nsIDocument* doc = popup->GetCurrentDoc(); 1.1299 + 1.1300 + // Only remove the focus if the currently focused item is ouside the 1.1301 + // popup. It isn't a big deal if the current focus is in a child popup 1.1302 + // inside the popup as that shouldn't be visible. This check ensures that 1.1303 + // a node inside the popup that is focused during a popupshowing event 1.1304 + // remains focused. 1.1305 + nsCOMPtr<nsIDOMElement> currentFocusElement; 1.1306 + fm->GetFocusedElement(getter_AddRefs(currentFocusElement)); 1.1307 + nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement); 1.1308 + if (doc && currentFocus && 1.1309 + !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) { 1.1310 + fm->ClearFocus(doc->GetWindow()); 1.1311 + } 1.1312 + } 1.1313 + } 1.1314 + 1.1315 + // clear these as they are no longer valid 1.1316 + mRangeParent = nullptr; 1.1317 + mRangeOffset = 0; 1.1318 + 1.1319 + // get the frame again in case it went away 1.1320 + popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); 1.1321 + if (popupFrame) { 1.1322 + // if the event was cancelled, don't open the popup, reset its state back 1.1323 + // to closed and clear its trigger content. 1.1324 + if (status == nsEventStatus_eConsumeNoDefault) { 1.1325 + popupFrame->SetPopupState(ePopupClosed); 1.1326 + popupFrame->ClearTriggerContent(); 1.1327 + } 1.1328 + else { 1.1329 + ShowPopupCallback(aPopup, popupFrame, aIsContextMenu, aSelectFirstItem); 1.1330 + } 1.1331 + } 1.1332 +} 1.1333 + 1.1334 +void 1.1335 +nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup, 1.1336 + nsIContent* aNextPopup, 1.1337 + nsIContent* aLastPopup, 1.1338 + nsPresContext *aPresContext, 1.1339 + nsPopupType aPopupType, 1.1340 + bool aDeselectMenu, 1.1341 + bool aIsRollup) 1.1342 +{ 1.1343 + nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell(); 1.1344 + 1.1345 + nsEventStatus status = nsEventStatus_eIgnore; 1.1346 + WidgetMouseEvent event(true, NS_XUL_POPUP_HIDING, nullptr, 1.1347 + WidgetMouseEvent::eReal); 1.1348 + EventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status); 1.1349 + 1.1350 + // when a panel is closed, blur whatever has focus inside the popup 1.1351 + if (aPopupType == ePopupTypePanel && 1.1352 + !aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus, 1.1353 + nsGkAtoms::_true, eCaseMatters)) { 1.1354 + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); 1.1355 + if (fm) { 1.1356 + nsIDocument* doc = aPopup->GetCurrentDoc(); 1.1357 + 1.1358 + // Remove the focus from the focused node only if it is inside the popup. 1.1359 + nsCOMPtr<nsIDOMElement> currentFocusElement; 1.1360 + fm->GetFocusedElement(getter_AddRefs(currentFocusElement)); 1.1361 + nsCOMPtr<nsIContent> currentFocus = do_QueryInterface(currentFocusElement); 1.1362 + if (doc && currentFocus && 1.1363 + nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) { 1.1364 + fm->ClearFocus(doc->GetWindow()); 1.1365 + } 1.1366 + } 1.1367 + } 1.1368 + 1.1369 + // get frame again in case it went away 1.1370 + nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); 1.1371 + if (popupFrame) { 1.1372 + // if the event was cancelled, don't hide the popup, and reset its 1.1373 + // state back to open. Only popups in chrome shells can prevent a popup 1.1374 + // from hiding. 1.1375 + if (status == nsEventStatus_eConsumeNoDefault && 1.1376 + !popupFrame->IsInContentShell()) { 1.1377 + popupFrame->SetPopupState(ePopupOpenAndVisible); 1.1378 + } 1.1379 + else { 1.1380 + // If the popup has an animate attribute and it is not set to false, assume 1.1381 + // that it has a closing transition and wait for it to finish. The transition 1.1382 + // may still occur either way, but the view will be hidden and you won't be 1.1383 + // able to see it. If there is a next popup, indicating that mutliple popups 1.1384 + // are rolling up, don't wait and hide the popup right away since the effect 1.1385 + // would likely be undesirable. This also does a quick check to see if the 1.1386 + // popup has a transition defined, and skips the wait if not. Transitions 1.1387 + // are currently disabled on Linux due to rendering issues on certain 1.1388 + // configurations. 1.1389 +#ifndef MOZ_WIDGET_GTK 1.1390 + if (!aNextPopup && aPopup->HasAttr(kNameSpaceID_None, nsGkAtoms::animate) && 1.1391 + popupFrame->StyleDisplay()->mTransitionPropertyCount > 0) { 1.1392 + nsAutoString animate; 1.1393 + aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::animate, animate); 1.1394 + 1.1395 + // If animate="false" then don't transition at all. If animate="cancel", 1.1396 + // only show the transition if cancelling the popup or rolling up. 1.1397 + // Otherwise, always show the transition. 1.1398 + if (!animate.EqualsLiteral("false") && 1.1399 + (!animate.EqualsLiteral("cancel") || aIsRollup)) { 1.1400 + nsCOMPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aDeselectMenu); 1.1401 + aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"), 1.1402 + ender, false, false); 1.1403 + return; 1.1404 + } 1.1405 + } 1.1406 +#endif 1.1407 + 1.1408 + HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup, 1.1409 + aPopupType, aDeselectMenu); 1.1410 + } 1.1411 + } 1.1412 +} 1.1413 + 1.1414 +bool 1.1415 +nsXULPopupManager::IsPopupOpen(nsIContent* aPopup) 1.1416 +{ 1.1417 + // a popup is open if it is in the open list. The assertions ensure that the 1.1418 + // frame is in the correct state. If the popup is in the hiding or invisible 1.1419 + // state, it will still be in the open popup list until it is closed. 1.1420 + nsMenuChainItem* item = mPopups; 1.1421 + while (item) { 1.1422 + if (item->Content() == aPopup) { 1.1423 + NS_ASSERTION(item->Frame()->IsOpen() || 1.1424 + item->Frame()->PopupState() == ePopupHiding || 1.1425 + item->Frame()->PopupState() == ePopupInvisible, 1.1426 + "popup in open list not actually open"); 1.1427 + return true; 1.1428 + } 1.1429 + item = item->GetParent(); 1.1430 + } 1.1431 + 1.1432 + item = mNoHidePanels; 1.1433 + while (item) { 1.1434 + if (item->Content() == aPopup) { 1.1435 + NS_ASSERTION(item->Frame()->IsOpen() || 1.1436 + item->Frame()->PopupState() == ePopupHiding || 1.1437 + item->Frame()->PopupState() == ePopupInvisible, 1.1438 + "popup in open list not actually open"); 1.1439 + return true; 1.1440 + } 1.1441 + item = item->GetParent(); 1.1442 + } 1.1443 + 1.1444 + return false; 1.1445 +} 1.1446 + 1.1447 +bool 1.1448 +nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent) 1.1449 +{ 1.1450 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.1451 + while (item) { 1.1452 + nsMenuPopupFrame* popup = item->Frame(); 1.1453 + if (popup && popup->IsOpen()) { 1.1454 + nsMenuFrame* menuFrame = do_QueryFrame(popup->GetParent()); 1.1455 + if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) { 1.1456 + return true; 1.1457 + } 1.1458 + } 1.1459 + item = item->GetParent(); 1.1460 + } 1.1461 + 1.1462 + return false; 1.1463 +} 1.1464 + 1.1465 +nsIFrame* 1.1466 +nsXULPopupManager::GetTopPopup(nsPopupType aType) 1.1467 +{ 1.1468 + if ((aType == ePopupTypePanel || aType == ePopupTypeTooltip) && mNoHidePanels) 1.1469 + return mNoHidePanels->Frame(); 1.1470 + 1.1471 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.1472 + while (item) { 1.1473 + if (item->PopupType() == aType || aType == ePopupTypeAny) 1.1474 + return item->Frame(); 1.1475 + item = item->GetParent(); 1.1476 + } 1.1477 + 1.1478 + return nullptr; 1.1479 +} 1.1480 + 1.1481 +void 1.1482 +nsXULPopupManager::GetVisiblePopups(nsTArray<nsIFrame *>& aPopups) 1.1483 +{ 1.1484 + aPopups.Clear(); 1.1485 + 1.1486 + // Iterate over both lists of popups 1.1487 + nsMenuChainItem* item = mPopups; 1.1488 + for (int32_t list = 0; list < 2; list++) { 1.1489 + while (item) { 1.1490 + // Skip panels which are not open and visible as well as popups that 1.1491 + // are transparent to mouse events. 1.1492 + if (item->Frame()->PopupState() == ePopupOpenAndVisible && 1.1493 + !item->Frame()->IsMouseTransparent()) { 1.1494 + aPopups.AppendElement(item->Frame()); 1.1495 + } 1.1496 + 1.1497 + item = item->GetParent(); 1.1498 + } 1.1499 + 1.1500 + item = mNoHidePanels; 1.1501 + } 1.1502 +} 1.1503 + 1.1504 +already_AddRefed<nsIDOMNode> 1.1505 +nsXULPopupManager::GetLastTriggerNode(nsIDocument* aDocument, bool aIsTooltip) 1.1506 +{ 1.1507 + if (!aDocument) 1.1508 + return nullptr; 1.1509 + 1.1510 + nsCOMPtr<nsIDOMNode> node; 1.1511 + 1.1512 + // if mOpeningPopup is set, it means that a popupshowing event is being 1.1513 + // fired. In this case, just use the cached node, as the popup is not yet in 1.1514 + // the list of open popups. 1.1515 + if (mOpeningPopup && mOpeningPopup->GetCurrentDoc() == aDocument && 1.1516 + aIsTooltip == (mOpeningPopup->Tag() == nsGkAtoms::tooltip)) { 1.1517 + node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(GetPopupFrameForContent(mOpeningPopup, false))); 1.1518 + } 1.1519 + else { 1.1520 + nsMenuChainItem* item = aIsTooltip ? mNoHidePanels : mPopups; 1.1521 + while (item) { 1.1522 + // look for a popup of the same type and document. 1.1523 + if ((item->PopupType() == ePopupTypeTooltip) == aIsTooltip && 1.1524 + item->Content()->GetCurrentDoc() == aDocument) { 1.1525 + node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(item->Frame())); 1.1526 + if (node) 1.1527 + break; 1.1528 + } 1.1529 + item = item->GetParent(); 1.1530 + } 1.1531 + } 1.1532 + 1.1533 + return node.forget(); 1.1534 +} 1.1535 + 1.1536 +bool 1.1537 +nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup) 1.1538 +{ 1.1539 + // if a popup's IsOpen method returns true, then the popup must always be in 1.1540 + // the popup chain scanned in IsPopupOpen. 1.1541 + NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(aPopup->GetContent()), 1.1542 + "popup frame state doesn't match XULPopupManager open state"); 1.1543 + 1.1544 + nsPopupState state = aPopup->PopupState(); 1.1545 + 1.1546 + // if the popup is not in the open popup chain, then it must have a state that 1.1547 + // is either closed, in the process of being shown, or invisible. 1.1548 + NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed || 1.1549 + state == ePopupShowing || state == ePopupInvisible, 1.1550 + "popup not in XULPopupManager open list is open"); 1.1551 + 1.1552 + // don't show popups unless they are closed or invisible 1.1553 + if (state != ePopupClosed && state != ePopupInvisible) 1.1554 + return false; 1.1555 + 1.1556 + // Don't show popups that we already have in our popup chain 1.1557 + if (IsPopupOpen(aPopup->GetContent())) { 1.1558 + NS_WARNING("Refusing to show duplicate popup"); 1.1559 + return false; 1.1560 + } 1.1561 + 1.1562 + // if the popup was just rolled up, don't reopen it 1.1563 + nsCOMPtr<nsIWidget> widget = aPopup->GetWidget(); 1.1564 + if (widget && widget->GetLastRollup() == aPopup->GetContent()) 1.1565 + return false; 1.1566 + 1.1567 + nsCOMPtr<nsIDocShellTreeItem> dsti = aPopup->PresContext()->GetDocShell(); 1.1568 + nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti); 1.1569 + if (!baseWin) 1.1570 + return false; 1.1571 + 1.1572 + // chrome shells can always open popups, but other types of shells can only 1.1573 + // open popups when they are focused and visible 1.1574 + if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) { 1.1575 + // only allow popups in active windows 1.1576 + nsCOMPtr<nsIDocShellTreeItem> root; 1.1577 + dsti->GetRootTreeItem(getter_AddRefs(root)); 1.1578 + nsCOMPtr<nsIDOMWindow> rootWin = do_GetInterface(root); 1.1579 + 1.1580 + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); 1.1581 + if (!fm || !rootWin) 1.1582 + return false; 1.1583 + 1.1584 + nsCOMPtr<nsIDOMWindow> activeWindow; 1.1585 + fm->GetActiveWindow(getter_AddRefs(activeWindow)); 1.1586 + if (activeWindow != rootWin) 1.1587 + return false; 1.1588 + 1.1589 + // only allow popups in visible frames 1.1590 + bool visible; 1.1591 + baseWin->GetVisibility(&visible); 1.1592 + if (!visible) 1.1593 + return false; 1.1594 + } 1.1595 + 1.1596 + // platforms respond differently when an popup is opened in a minimized 1.1597 + // window, so this is always disabled. 1.1598 + nsCOMPtr<nsIWidget> mainWidget; 1.1599 + baseWin->GetMainWidget(getter_AddRefs(mainWidget)); 1.1600 + if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) { 1.1601 + return false; 1.1602 + } 1.1603 + 1.1604 + // cannot open a popup that is a submenu of a menupopup that isn't open. 1.1605 + nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent()); 1.1606 + if (menuFrame) { 1.1607 + nsMenuParent* parentPopup = menuFrame->GetMenuParent(); 1.1608 + if (parentPopup && !parentPopup->IsOpen()) 1.1609 + return false; 1.1610 + } 1.1611 + 1.1612 + return true; 1.1613 +} 1.1614 + 1.1615 +void 1.1616 +nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup) 1.1617 +{ 1.1618 + // when a popup frame is destroyed, just unhook it from the list of popups 1.1619 + if (mTimerMenu == aPopup) { 1.1620 + if (mCloseTimer) { 1.1621 + mCloseTimer->Cancel(); 1.1622 + mCloseTimer = nullptr; 1.1623 + } 1.1624 + mTimerMenu = nullptr; 1.1625 + } 1.1626 + 1.1627 + nsMenuChainItem* item = mNoHidePanels; 1.1628 + while (item) { 1.1629 + if (item->Frame() == aPopup) { 1.1630 + item->Detach(&mNoHidePanels); 1.1631 + delete item; 1.1632 + break; 1.1633 + } 1.1634 + item = item->GetParent(); 1.1635 + } 1.1636 + 1.1637 + nsTArray<nsMenuPopupFrame *> popupsToHide; 1.1638 + 1.1639 + item = mPopups; 1.1640 + while (item) { 1.1641 + nsMenuPopupFrame* frame = item->Frame(); 1.1642 + if (frame == aPopup) { 1.1643 + if (frame->PopupState() != ePopupInvisible) { 1.1644 + // Iterate through any child menus and hide them as well, since the 1.1645 + // parent is going away. We won't remove them from the list yet, just 1.1646 + // hide them, as they will be removed from the list when this function 1.1647 + // gets called for that child frame. 1.1648 + nsMenuChainItem* child = item->GetChild(); 1.1649 + while (child) { 1.1650 + // if the popup is a child frame of the menu that was destroyed, add 1.1651 + // it to the list of popups to hide. Don't bother with the events 1.1652 + // since the frames are going away. If the child menu is not a child 1.1653 + // frame, for example, a context menu, use HidePopup instead, but call 1.1654 + // it asynchronously since we are in the middle of frame destruction. 1.1655 + nsMenuPopupFrame* childframe = child->Frame(); 1.1656 + if (nsLayoutUtils::IsProperAncestorFrame(frame, childframe)) { 1.1657 + popupsToHide.AppendElement(childframe); 1.1658 + } 1.1659 + else { 1.1660 + // HidePopup will take care of hiding any of its children, so 1.1661 + // break out afterwards 1.1662 + HidePopup(child->Content(), false, false, true, false); 1.1663 + break; 1.1664 + } 1.1665 + 1.1666 + child = child->GetChild(); 1.1667 + } 1.1668 + } 1.1669 + 1.1670 + item->Detach(&mPopups); 1.1671 + delete item; 1.1672 + break; 1.1673 + } 1.1674 + 1.1675 + item = item->GetParent(); 1.1676 + } 1.1677 + 1.1678 + HidePopupsInList(popupsToHide, false); 1.1679 +} 1.1680 + 1.1681 +bool 1.1682 +nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup) 1.1683 +{ 1.1684 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.1685 + while (item && item->Frame() != aPopup) { 1.1686 + if (item->IsContextMenu()) 1.1687 + return true; 1.1688 + item = item->GetParent(); 1.1689 + } 1.1690 + 1.1691 + return false; 1.1692 +} 1.1693 + 1.1694 +void 1.1695 +nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup) 1.1696 +{ 1.1697 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.1698 + if (item && aOldPopup == item->Content()) 1.1699 + return; 1.1700 + 1.1701 + if (mWidget) { 1.1702 + mWidget->CaptureRollupEvents(nullptr, false); 1.1703 + mWidget = nullptr; 1.1704 + } 1.1705 + 1.1706 + if (item) { 1.1707 + nsMenuPopupFrame* popup = item->Frame(); 1.1708 + mWidget = popup->GetWidget(); 1.1709 + if (mWidget) { 1.1710 + mWidget->CaptureRollupEvents(nullptr, true); 1.1711 + popup->AttachedDismissalListener(); 1.1712 + } 1.1713 + } 1.1714 + 1.1715 + UpdateKeyboardListeners(); 1.1716 +} 1.1717 + 1.1718 +void 1.1719 +nsXULPopupManager::UpdateKeyboardListeners() 1.1720 +{ 1.1721 + nsCOMPtr<EventTarget> newTarget; 1.1722 + bool isForMenu = false; 1.1723 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.1724 + if (item) { 1.1725 + if (!item->IgnoreKeys()) 1.1726 + newTarget = item->Content()->GetDocument(); 1.1727 + isForMenu = item->PopupType() == ePopupTypeMenu; 1.1728 + } 1.1729 + else if (mActiveMenuBar) { 1.1730 + newTarget = mActiveMenuBar->GetContent()->GetDocument(); 1.1731 + isForMenu = true; 1.1732 + } 1.1733 + 1.1734 + if (mKeyListener != newTarget) { 1.1735 + if (mKeyListener) { 1.1736 + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true); 1.1737 + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true); 1.1738 + mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true); 1.1739 + mKeyListener = nullptr; 1.1740 + nsContentUtils::NotifyInstalledMenuKeyboardListener(false); 1.1741 + } 1.1742 + 1.1743 + if (newTarget) { 1.1744 + newTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, true); 1.1745 + newTarget->AddEventListener(NS_LITERAL_STRING("keydown"), this, true); 1.1746 + newTarget->AddEventListener(NS_LITERAL_STRING("keyup"), this, true); 1.1747 + nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu); 1.1748 + mKeyListener = newTarget; 1.1749 + } 1.1750 + } 1.1751 +} 1.1752 + 1.1753 +void 1.1754 +nsXULPopupManager::UpdateMenuItems(nsIContent* aPopup) 1.1755 +{ 1.1756 + // Walk all of the menu's children, checking to see if any of them has a 1.1757 + // command attribute. If so, then several attributes must potentially be updated. 1.1758 + 1.1759 + nsCOMPtr<nsIDocument> document = aPopup->GetCurrentDoc(); 1.1760 + if (!document) { 1.1761 + return; 1.1762 + } 1.1763 + 1.1764 + for (nsCOMPtr<nsIContent> grandChild = aPopup->GetFirstChild(); 1.1765 + grandChild; 1.1766 + grandChild = grandChild->GetNextSibling()) { 1.1767 + if (grandChild->NodeInfo()->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL)) { 1.1768 + // See if we have a command attribute. 1.1769 + nsAutoString command; 1.1770 + grandChild->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command); 1.1771 + if (!command.IsEmpty()) { 1.1772 + // We do! Look it up in our document 1.1773 + nsRefPtr<dom::Element> commandElement = 1.1774 + document->GetElementById(command); 1.1775 + if (commandElement) { 1.1776 + nsAutoString commandValue; 1.1777 + // The menu's disabled state needs to be updated to match the command. 1.1778 + if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue)) 1.1779 + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue, true); 1.1780 + else 1.1781 + grandChild->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true); 1.1782 + 1.1783 + // The menu's label, accesskey checked and hidden states need to be updated 1.1784 + // to match the command. Note that unlike the disabled state if the 1.1785 + // command has *no* value, we assume the menu is supplying its own. 1.1786 + if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue)) 1.1787 + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue, true); 1.1788 + 1.1789 + if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue)) 1.1790 + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue, true); 1.1791 + 1.1792 + if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue)) 1.1793 + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue, true); 1.1794 + 1.1795 + if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue)) 1.1796 + grandChild->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue, true); 1.1797 + } 1.1798 + } 1.1799 + } 1.1800 + } 1.1801 +} 1.1802 + 1.1803 +// Notify 1.1804 +// 1.1805 +// The item selection timer has fired, we might have to readjust the 1.1806 +// selected item. There are two cases here that we are trying to deal with: 1.1807 +// (1) diagonal movement from a parent menu to a submenu passing briefly over 1.1808 +// other items, and 1.1809 +// (2) moving out from a submenu to a parent or grandparent menu. 1.1810 +// In both cases, |mTimerMenu| is the menu item that might have an open submenu and 1.1811 +// the first item in |mPopups| is the item the mouse is currently over, which could be 1.1812 +// none of them. 1.1813 +// 1.1814 +// case (1): 1.1815 +// As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the 1.1816 +// submenu, it probably passes through one or more sibilings (B). As the mouse passes 1.1817 +// through B, it becomes the current menu item and the timer is set and mTimerMenu is 1.1818 +// set to A. Before the timer fires, the mouse leaves the menu containing A and B and 1.1819 +// enters the submenus. Now when the timer fires, |mPopups| is null (!= |mTimerMenu|) 1.1820 +// so we have to see if anything in A's children is selected (recall that even disabled 1.1821 +// items are selected, the style just doesn't show it). If that is the case, we need to 1.1822 +// set the selected item back to A. 1.1823 +// 1.1824 +// case (2); 1.1825 +// Item A has an open submenu, and in it there is an item (B) which also has an open 1.1826 +// submenu (so there are 3 menus displayed right now). The mouse then leaves B's child 1.1827 +// submenu and selects an item that is a sibling of A, call it C. When the mouse enters C, 1.1828 +// the timer is set and |mTimerMenu| is A and |mPopups| is C. As the timer fires, 1.1829 +// the mouse is still within C. The correct behavior is to set the current item to C 1.1830 +// and close up the chain parented at A. 1.1831 +// 1.1832 +// This brings up the question of is the logic of case (1) enough? The answer is no, 1.1833 +// and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected 1.1834 +// child, and if it does, set the selected item to A. Because B has a submenu open, it 1.1835 +// is selected and as a result, A is set to be the selected item even though the mouse 1.1836 +// rests in C -- very wrong. 1.1837 +// 1.1838 +// The solution is to use the same idea, but instead of only checking one level, 1.1839 +// drill all the way down to the deepest open submenu and check if it has something 1.1840 +// selected. Since the mouse is in a grandparent, it won't, and we know that we can 1.1841 +// safely close up A and all its children. 1.1842 +// 1.1843 +// The code below melds the two cases together. 1.1844 +// 1.1845 +nsresult 1.1846 +nsXULPopupManager::Notify(nsITimer* aTimer) 1.1847 +{ 1.1848 + if (aTimer == mCloseTimer) 1.1849 + KillMenuTimer(); 1.1850 + 1.1851 + return NS_OK; 1.1852 +} 1.1853 + 1.1854 +void 1.1855 +nsXULPopupManager::KillMenuTimer() 1.1856 +{ 1.1857 + if (mCloseTimer && mTimerMenu) { 1.1858 + mCloseTimer->Cancel(); 1.1859 + mCloseTimer = nullptr; 1.1860 + 1.1861 + if (mTimerMenu->IsOpen()) 1.1862 + HidePopup(mTimerMenu->GetContent(), false, false, true, false); 1.1863 + } 1.1864 + 1.1865 + mTimerMenu = nullptr; 1.1866 +} 1.1867 + 1.1868 +void 1.1869 +nsXULPopupManager::CancelMenuTimer(nsMenuParent* aMenuParent) 1.1870 +{ 1.1871 + if (mCloseTimer && mTimerMenu == aMenuParent) { 1.1872 + mCloseTimer->Cancel(); 1.1873 + mCloseTimer = nullptr; 1.1874 + mTimerMenu = nullptr; 1.1875 + } 1.1876 +} 1.1877 + 1.1878 +bool 1.1879 +nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent, 1.1880 + nsMenuPopupFrame* aFrame) 1.1881 +{ 1.1882 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.1883 + if (!aFrame && item) 1.1884 + aFrame = item->Frame(); 1.1885 + 1.1886 + if (aFrame) { 1.1887 + bool action; 1.1888 + nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action); 1.1889 + if (result) { 1.1890 + aFrame->ChangeMenuItem(result, false); 1.1891 + if (action) { 1.1892 + WidgetGUIEvent* evt = aKeyEvent->GetInternalNSEvent()->AsGUIEvent(); 1.1893 + nsMenuFrame* menuToOpen = result->Enter(evt); 1.1894 + if (menuToOpen) { 1.1895 + nsCOMPtr<nsIContent> content = menuToOpen->GetContent(); 1.1896 + ShowMenu(content, true, false); 1.1897 + } 1.1898 + } 1.1899 + return true; 1.1900 + } 1.1901 + 1.1902 + return false; 1.1903 + } 1.1904 + 1.1905 + if (mActiveMenuBar) { 1.1906 + nsMenuFrame* result = mActiveMenuBar->FindMenuWithShortcut(aKeyEvent); 1.1907 + if (result) { 1.1908 + mActiveMenuBar->SetActive(true); 1.1909 + result->OpenMenu(true); 1.1910 + return true; 1.1911 + } 1.1912 + } 1.1913 + 1.1914 + return false; 1.1915 +} 1.1916 + 1.1917 + 1.1918 +bool 1.1919 +nsXULPopupManager::HandleKeyboardNavigation(uint32_t aKeyCode) 1.1920 +{ 1.1921 + // navigate up through the open menus, looking for the topmost one 1.1922 + // in the same hierarchy 1.1923 + nsMenuChainItem* item = nullptr; 1.1924 + nsMenuChainItem* nextitem = GetTopVisibleMenu(); 1.1925 + 1.1926 + while (nextitem) { 1.1927 + item = nextitem; 1.1928 + nextitem = item->GetParent(); 1.1929 + 1.1930 + if (nextitem) { 1.1931 + // stop if the parent isn't a menu 1.1932 + if (!nextitem->IsMenu()) 1.1933 + break; 1.1934 + 1.1935 + // check to make sure that the parent is actually the parent menu. It won't 1.1936 + // be if the parent is in a different frame hierarchy, for example, for a 1.1937 + // context menu opened on another menu. 1.1938 + nsMenuParent* expectedParent = static_cast<nsMenuParent *>(nextitem->Frame()); 1.1939 + nsMenuFrame* menuFrame = do_QueryFrame(item->Frame()->GetParent()); 1.1940 + if (!menuFrame || menuFrame->GetMenuParent() != expectedParent) { 1.1941 + break; 1.1942 + } 1.1943 + } 1.1944 + } 1.1945 + 1.1946 + nsIFrame* itemFrame; 1.1947 + if (item) 1.1948 + itemFrame = item->Frame(); 1.1949 + else if (mActiveMenuBar) 1.1950 + itemFrame = mActiveMenuBar; 1.1951 + else 1.1952 + return false; 1.1953 + 1.1954 + nsNavigationDirection theDirection; 1.1955 + NS_ASSERTION(aKeyCode >= nsIDOMKeyEvent::DOM_VK_END && 1.1956 + aKeyCode <= nsIDOMKeyEvent::DOM_VK_DOWN, "Illegal key code"); 1.1957 + theDirection = NS_DIRECTION_FROM_KEY_CODE(itemFrame, aKeyCode); 1.1958 + 1.1959 + // if a popup is open, first check for navigation within the popup 1.1960 + if (item && HandleKeyboardNavigationInPopup(item, theDirection)) 1.1961 + return true; 1.1962 + 1.1963 + // no popup handled the key, so check the active menubar, if any 1.1964 + if (mActiveMenuBar) { 1.1965 + nsMenuFrame* currentMenu = mActiveMenuBar->GetCurrentMenuItem(); 1.1966 + 1.1967 + if (NS_DIRECTION_IS_INLINE(theDirection)) { 1.1968 + nsMenuFrame* nextItem = (theDirection == eNavigationDirection_End) ? 1.1969 + GetNextMenuItem(mActiveMenuBar, currentMenu, false) : 1.1970 + GetPreviousMenuItem(mActiveMenuBar, currentMenu, false); 1.1971 + mActiveMenuBar->ChangeMenuItem(nextItem, true); 1.1972 + return true; 1.1973 + } 1.1974 + else if (NS_DIRECTION_IS_BLOCK(theDirection)) { 1.1975 + // Open the menu and select its first item. 1.1976 + if (currentMenu) { 1.1977 + nsCOMPtr<nsIContent> content = currentMenu->GetContent(); 1.1978 + ShowMenu(content, true, false); 1.1979 + } 1.1980 + return true; 1.1981 + } 1.1982 + } 1.1983 + 1.1984 + return false; 1.1985 +} 1.1986 + 1.1987 +bool 1.1988 +nsXULPopupManager::HandleKeyboardNavigationInPopup(nsMenuChainItem* item, 1.1989 + nsMenuPopupFrame* aFrame, 1.1990 + nsNavigationDirection aDir) 1.1991 +{ 1.1992 + NS_ASSERTION(aFrame, "aFrame is null"); 1.1993 + NS_ASSERTION(!item || item->Frame() == aFrame, 1.1994 + "aFrame is expected to be equal to item->Frame()"); 1.1995 + 1.1996 + nsMenuFrame* currentMenu = aFrame->GetCurrentMenuItem(); 1.1997 + 1.1998 + aFrame->ClearIncrementalString(); 1.1999 + 1.2000 + // This method only gets called if we're open. 1.2001 + if (!currentMenu && NS_DIRECTION_IS_INLINE(aDir)) { 1.2002 + // We've been opened, but we haven't had anything selected. 1.2003 + // We can handle End, but our parent handles Start. 1.2004 + if (aDir == eNavigationDirection_End) { 1.2005 + nsMenuFrame* nextItem = GetNextMenuItem(aFrame, nullptr, true); 1.2006 + if (nextItem) { 1.2007 + aFrame->ChangeMenuItem(nextItem, false); 1.2008 + return true; 1.2009 + } 1.2010 + } 1.2011 + return false; 1.2012 + } 1.2013 + 1.2014 + bool isContainer = false; 1.2015 + bool isOpen = false; 1.2016 + if (currentMenu) { 1.2017 + isOpen = currentMenu->IsOpen(); 1.2018 + isContainer = currentMenu->IsMenu(); 1.2019 + if (isOpen) { 1.2020 + // for an open popup, have the child process the event 1.2021 + nsMenuChainItem* child = item ? item->GetChild() : nullptr; 1.2022 + if (child && HandleKeyboardNavigationInPopup(child, aDir)) 1.2023 + return true; 1.2024 + } 1.2025 + else if (aDir == eNavigationDirection_End && 1.2026 + isContainer && !currentMenu->IsDisabled()) { 1.2027 + // The menu is not yet open. Open it and select the first item. 1.2028 + nsCOMPtr<nsIContent> content = currentMenu->GetContent(); 1.2029 + ShowMenu(content, true, false); 1.2030 + return true; 1.2031 + } 1.2032 + } 1.2033 + 1.2034 + // For block progression, we can move in either direction 1.2035 + if (NS_DIRECTION_IS_BLOCK(aDir) || 1.2036 + NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir)) { 1.2037 + nsMenuFrame* nextItem; 1.2038 + 1.2039 + if (aDir == eNavigationDirection_Before) 1.2040 + nextItem = GetPreviousMenuItem(aFrame, currentMenu, true); 1.2041 + else if (aDir == eNavigationDirection_After) 1.2042 + nextItem = GetNextMenuItem(aFrame, currentMenu, true); 1.2043 + else if (aDir == eNavigationDirection_First) 1.2044 + nextItem = GetNextMenuItem(aFrame, nullptr, true); 1.2045 + else 1.2046 + nextItem = GetPreviousMenuItem(aFrame, nullptr, true); 1.2047 + 1.2048 + if (nextItem) { 1.2049 + aFrame->ChangeMenuItem(nextItem, false); 1.2050 + return true; 1.2051 + } 1.2052 + } 1.2053 + else if (currentMenu && isContainer && isOpen) { 1.2054 + if (aDir == eNavigationDirection_Start) { 1.2055 + // close a submenu when Left is pressed 1.2056 + nsMenuPopupFrame* popupFrame = currentMenu->GetPopup(); 1.2057 + if (popupFrame) 1.2058 + HidePopup(popupFrame->GetContent(), false, false, false, false); 1.2059 + return true; 1.2060 + } 1.2061 + } 1.2062 + 1.2063 + return false; 1.2064 +} 1.2065 + 1.2066 +bool 1.2067 +nsXULPopupManager::HandleKeyboardEventWithKeyCode( 1.2068 + nsIDOMKeyEvent* aKeyEvent, 1.2069 + nsMenuChainItem* aTopVisibleMenuItem) 1.2070 +{ 1.2071 + uint32_t keyCode; 1.2072 + aKeyEvent->GetKeyCode(&keyCode); 1.2073 + 1.2074 + // Escape should close panels, but the other keys should have no effect. 1.2075 + if (aTopVisibleMenuItem && 1.2076 + aTopVisibleMenuItem->PopupType() != ePopupTypeMenu) { 1.2077 + if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE) { 1.2078 + HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true); 1.2079 + aKeyEvent->StopPropagation(); 1.2080 + aKeyEvent->PreventDefault(); 1.2081 + } 1.2082 + return true; 1.2083 + } 1.2084 + 1.2085 + bool consume = (mPopups || mActiveMenuBar); 1.2086 + switch (keyCode) { 1.2087 + case nsIDOMKeyEvent::DOM_VK_LEFT: 1.2088 + case nsIDOMKeyEvent::DOM_VK_RIGHT: 1.2089 + case nsIDOMKeyEvent::DOM_VK_UP: 1.2090 + case nsIDOMKeyEvent::DOM_VK_DOWN: 1.2091 + case nsIDOMKeyEvent::DOM_VK_HOME: 1.2092 + case nsIDOMKeyEvent::DOM_VK_END: 1.2093 + HandleKeyboardNavigation(keyCode); 1.2094 + break; 1.2095 + 1.2096 + case nsIDOMKeyEvent::DOM_VK_ESCAPE: 1.2097 + // Pressing Escape hides one level of menus only. If no menu is open, 1.2098 + // check if a menubar is active and inform it that a menu closed. Even 1.2099 + // though in this latter case, a menu didn't actually close, the effect 1.2100 + // ends up being the same. Similar for the tab key below. 1.2101 + if (aTopVisibleMenuItem) { 1.2102 + HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true); 1.2103 + } else if (mActiveMenuBar) { 1.2104 + mActiveMenuBar->MenuClosed(); 1.2105 + } 1.2106 + break; 1.2107 + 1.2108 + case nsIDOMKeyEvent::DOM_VK_TAB: 1.2109 +#ifndef XP_MACOSX 1.2110 + case nsIDOMKeyEvent::DOM_VK_F10: 1.2111 +#endif 1.2112 + // close popups or deactivate menubar when Tab or F10 are pressed 1.2113 + if (aTopVisibleMenuItem) { 1.2114 + Rollup(0, nullptr, nullptr); 1.2115 + } else if (mActiveMenuBar) { 1.2116 + mActiveMenuBar->MenuClosed(); 1.2117 + } 1.2118 + break; 1.2119 + 1.2120 + case nsIDOMKeyEvent::DOM_VK_RETURN: { 1.2121 + // If there is a popup open, check if the current item needs to be opened. 1.2122 + // Otherwise, tell the active menubar, if any, to activate the menu. The 1.2123 + // Enter method will return a menu if one needs to be opened as a result. 1.2124 + nsMenuFrame* menuToOpen = nullptr; 1.2125 + WidgetGUIEvent* GUIEvent = aKeyEvent->GetInternalNSEvent()->AsGUIEvent(); 1.2126 + if (aTopVisibleMenuItem) { 1.2127 + menuToOpen = aTopVisibleMenuItem->Frame()->Enter(GUIEvent); 1.2128 + } else if (mActiveMenuBar) { 1.2129 + menuToOpen = mActiveMenuBar->Enter(GUIEvent); 1.2130 + } 1.2131 + if (menuToOpen) { 1.2132 + nsCOMPtr<nsIContent> content = menuToOpen->GetContent(); 1.2133 + ShowMenu(content, true, false); 1.2134 + } 1.2135 + break; 1.2136 + } 1.2137 + 1.2138 + default: 1.2139 + return false; 1.2140 + } 1.2141 + 1.2142 + if (consume) { 1.2143 + aKeyEvent->StopPropagation(); 1.2144 + aKeyEvent->PreventDefault(); 1.2145 + } 1.2146 + return true; 1.2147 +} 1.2148 + 1.2149 +nsMenuFrame* 1.2150 +nsXULPopupManager::GetNextMenuItem(nsIFrame* aParent, 1.2151 + nsMenuFrame* aStart, 1.2152 + bool aIsPopup) 1.2153 +{ 1.2154 + nsPresContext* presContext = aParent->PresContext(); 1.2155 + nsIFrame* immediateParent = presContext->PresShell()-> 1.2156 + FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr); 1.2157 + if (!immediateParent) 1.2158 + immediateParent = aParent; 1.2159 + 1.2160 + nsIFrame* currFrame = nullptr; 1.2161 + if (aStart) 1.2162 + currFrame = aStart->GetNextSibling(); 1.2163 + else 1.2164 + currFrame = immediateParent->GetFirstPrincipalChild(); 1.2165 + 1.2166 + while (currFrame) { 1.2167 + // See if it's a menu item. 1.2168 + if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { 1.2169 + return do_QueryFrame(currFrame); 1.2170 + } 1.2171 + currFrame = currFrame->GetNextSibling(); 1.2172 + } 1.2173 + 1.2174 + currFrame = immediateParent->GetFirstPrincipalChild(); 1.2175 + 1.2176 + // Still don't have anything. Try cycling from the beginning. 1.2177 + while (currFrame && currFrame != aStart) { 1.2178 + // See if it's a menu item. 1.2179 + if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { 1.2180 + return do_QueryFrame(currFrame); 1.2181 + } 1.2182 + 1.2183 + currFrame = currFrame->GetNextSibling(); 1.2184 + } 1.2185 + 1.2186 + // No luck. Just return our start value. 1.2187 + return aStart; 1.2188 +} 1.2189 + 1.2190 +nsMenuFrame* 1.2191 +nsXULPopupManager::GetPreviousMenuItem(nsIFrame* aParent, 1.2192 + nsMenuFrame* aStart, 1.2193 + bool aIsPopup) 1.2194 +{ 1.2195 + nsPresContext* presContext = aParent->PresContext(); 1.2196 + nsIFrame* immediateParent = presContext->PresShell()-> 1.2197 + FrameConstructor()->GetInsertionPoint(aParent->GetContent(), nullptr); 1.2198 + if (!immediateParent) 1.2199 + immediateParent = aParent; 1.2200 + 1.2201 + const nsFrameList& frames(immediateParent->PrincipalChildList()); 1.2202 + 1.2203 + nsIFrame* currFrame = nullptr; 1.2204 + if (aStart) 1.2205 + currFrame = aStart->GetPrevSibling(); 1.2206 + else 1.2207 + currFrame = frames.LastChild(); 1.2208 + 1.2209 + while (currFrame) { 1.2210 + // See if it's a menu item. 1.2211 + if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { 1.2212 + return do_QueryFrame(currFrame); 1.2213 + } 1.2214 + currFrame = currFrame->GetPrevSibling(); 1.2215 + } 1.2216 + 1.2217 + currFrame = frames.LastChild(); 1.2218 + 1.2219 + // Still don't have anything. Try cycling from the end. 1.2220 + while (currFrame && currFrame != aStart) { 1.2221 + // See if it's a menu item. 1.2222 + if (IsValidMenuItem(presContext, currFrame->GetContent(), aIsPopup)) { 1.2223 + return do_QueryFrame(currFrame); 1.2224 + } 1.2225 + 1.2226 + currFrame = currFrame->GetPrevSibling(); 1.2227 + } 1.2228 + 1.2229 + // No luck. Just return our start value. 1.2230 + return aStart; 1.2231 +} 1.2232 + 1.2233 +bool 1.2234 +nsXULPopupManager::IsValidMenuItem(nsPresContext* aPresContext, 1.2235 + nsIContent* aContent, 1.2236 + bool aOnPopup) 1.2237 +{ 1.2238 + int32_t ns = aContent->GetNameSpaceID(); 1.2239 + nsIAtom *tag = aContent->Tag(); 1.2240 + if (ns == kNameSpaceID_XUL) { 1.2241 + if (tag != nsGkAtoms::menu && tag != nsGkAtoms::menuitem) 1.2242 + return false; 1.2243 + } 1.2244 + else if (ns != kNameSpaceID_XHTML || !aOnPopup || tag != nsGkAtoms::option) { 1.2245 + return false; 1.2246 + } 1.2247 + 1.2248 + bool skipNavigatingDisabledMenuItem = true; 1.2249 + if (aOnPopup) { 1.2250 + skipNavigatingDisabledMenuItem = 1.2251 + LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem, 1.2252 + 0) != 0; 1.2253 + } 1.2254 + 1.2255 + return !(skipNavigatingDisabledMenuItem && 1.2256 + aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, 1.2257 + nsGkAtoms::_true, eCaseMatters)); 1.2258 +} 1.2259 + 1.2260 +nsresult 1.2261 +nsXULPopupManager::HandleEvent(nsIDOMEvent* aEvent) 1.2262 +{ 1.2263 + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent); 1.2264 + NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED); 1.2265 + 1.2266 + //handlers shouldn't be triggered by non-trusted events. 1.2267 + bool trustedEvent = false; 1.2268 + aEvent->GetIsTrusted(&trustedEvent); 1.2269 + if (!trustedEvent) { 1.2270 + return NS_OK; 1.2271 + } 1.2272 + 1.2273 + nsAutoString eventType; 1.2274 + keyEvent->GetType(eventType); 1.2275 + if (eventType.EqualsLiteral("keyup")) { 1.2276 + return KeyUp(keyEvent); 1.2277 + } 1.2278 + if (eventType.EqualsLiteral("keydown")) { 1.2279 + return KeyDown(keyEvent); 1.2280 + } 1.2281 + if (eventType.EqualsLiteral("keypress")) { 1.2282 + return KeyPress(keyEvent); 1.2283 + } 1.2284 + 1.2285 + NS_ABORT(); 1.2286 + 1.2287 + return NS_OK; 1.2288 +} 1.2289 + 1.2290 +nsresult 1.2291 +nsXULPopupManager::KeyUp(nsIDOMKeyEvent* aKeyEvent) 1.2292 +{ 1.2293 + // don't do anything if a menu isn't open or a menubar isn't active 1.2294 + if (!mActiveMenuBar) { 1.2295 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.2296 + if (!item || item->PopupType() != ePopupTypeMenu) 1.2297 + return NS_OK; 1.2298 + } 1.2299 + 1.2300 + aKeyEvent->StopPropagation(); 1.2301 + aKeyEvent->PreventDefault(); 1.2302 + 1.2303 + return NS_OK; // I am consuming event 1.2304 +} 1.2305 + 1.2306 +nsresult 1.2307 +nsXULPopupManager::KeyDown(nsIDOMKeyEvent* aKeyEvent) 1.2308 +{ 1.2309 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.2310 + if (item && item->Frame()->IsMenuLocked()) 1.2311 + return NS_OK; 1.2312 + 1.2313 + if (HandleKeyboardEventWithKeyCode(aKeyEvent, item)) { 1.2314 + return NS_OK; 1.2315 + } 1.2316 + 1.2317 + // don't do anything if a menu isn't open or a menubar isn't active 1.2318 + if (!mActiveMenuBar && (!item || item->PopupType() != ePopupTypeMenu)) 1.2319 + return NS_OK; 1.2320 + 1.2321 + int32_t menuAccessKey = -1; 1.2322 + 1.2323 + // If the key just pressed is the access key (usually Alt), 1.2324 + // dismiss and unfocus the menu. 1.2325 + 1.2326 + nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); 1.2327 + if (menuAccessKey) { 1.2328 + uint32_t theChar; 1.2329 + aKeyEvent->GetKeyCode(&theChar); 1.2330 + 1.2331 + if (theChar == (uint32_t)menuAccessKey) { 1.2332 + bool ctrl = false; 1.2333 + if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_CONTROL) 1.2334 + aKeyEvent->GetCtrlKey(&ctrl); 1.2335 + bool alt=false; 1.2336 + if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_ALT) 1.2337 + aKeyEvent->GetAltKey(&alt); 1.2338 + bool shift=false; 1.2339 + if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_SHIFT) 1.2340 + aKeyEvent->GetShiftKey(&shift); 1.2341 + bool meta=false; 1.2342 + if (menuAccessKey != nsIDOMKeyEvent::DOM_VK_META) 1.2343 + aKeyEvent->GetMetaKey(&meta); 1.2344 + if (!(ctrl || alt || shift || meta)) { 1.2345 + // The access key just went down and no other 1.2346 + // modifiers are already down. 1.2347 + if (mPopups) 1.2348 + Rollup(0, nullptr, nullptr); 1.2349 + else if (mActiveMenuBar) 1.2350 + mActiveMenuBar->MenuClosed(); 1.2351 + } 1.2352 + aKeyEvent->PreventDefault(); 1.2353 + } 1.2354 + } 1.2355 + 1.2356 + // Since a menu was open, stop propagation of the event to keep other event 1.2357 + // listeners from becoming confused. 1.2358 + aKeyEvent->StopPropagation(); 1.2359 + return NS_OK; 1.2360 +} 1.2361 + 1.2362 +nsresult 1.2363 +nsXULPopupManager::KeyPress(nsIDOMKeyEvent* aKeyEvent) 1.2364 +{ 1.2365 + // Don't check prevent default flag -- menus always get first shot at key events. 1.2366 + 1.2367 + nsMenuChainItem* item = GetTopVisibleMenu(); 1.2368 + if (item && 1.2369 + (item->Frame()->IsMenuLocked() || item->PopupType() != ePopupTypeMenu)) { 1.2370 + return NS_OK; 1.2371 + } 1.2372 + 1.2373 + nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent); 1.2374 + NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED); 1.2375 + // if a menu is open or a menubar is active, it consumes the key event 1.2376 + bool consume = (mPopups || mActiveMenuBar); 1.2377 + HandleShortcutNavigation(keyEvent, nullptr); 1.2378 + if (consume) { 1.2379 + aKeyEvent->StopPropagation(); 1.2380 + aKeyEvent->PreventDefault(); 1.2381 + } 1.2382 + 1.2383 + return NS_OK; // I am consuming event 1.2384 +} 1.2385 + 1.2386 +NS_IMETHODIMP 1.2387 +nsXULPopupShowingEvent::Run() 1.2388 +{ 1.2389 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.2390 + if (pm) { 1.2391 + pm->FirePopupShowingEvent(mPopup, mIsContextMenu, mSelectFirstItem); 1.2392 + } 1.2393 + 1.2394 + return NS_OK; 1.2395 +} 1.2396 + 1.2397 +NS_IMETHODIMP 1.2398 +nsXULPopupHidingEvent::Run() 1.2399 +{ 1.2400 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.2401 + 1.2402 + nsIDocument *document = mPopup->GetCurrentDoc(); 1.2403 + if (pm && document) { 1.2404 + nsIPresShell* presShell = document->GetShell(); 1.2405 + if (presShell) { 1.2406 + nsPresContext* context = presShell->GetPresContext(); 1.2407 + if (context) { 1.2408 + pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup, 1.2409 + context, mPopupType, mDeselectMenu, mIsRollup); 1.2410 + } 1.2411 + } 1.2412 + } 1.2413 + 1.2414 + return NS_OK; 1.2415 +} 1.2416 + 1.2417 +NS_IMETHODIMP 1.2418 +nsXULMenuCommandEvent::Run() 1.2419 +{ 1.2420 + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); 1.2421 + if (!pm) 1.2422 + return NS_OK; 1.2423 + 1.2424 + // The order of the nsViewManager and nsIPresShell COM pointers is 1.2425 + // important below. We want the pres shell to get released before the 1.2426 + // associated view manager on exit from this function. 1.2427 + // See bug 54233. 1.2428 + // XXXndeakin is this still needed? 1.2429 + 1.2430 + nsCOMPtr<nsIContent> popup; 1.2431 + nsMenuFrame* menuFrame = do_QueryFrame(mMenu->GetPrimaryFrame()); 1.2432 + nsWeakFrame weakFrame(menuFrame); 1.2433 + if (menuFrame && mFlipChecked) { 1.2434 + if (menuFrame->IsChecked()) { 1.2435 + mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true); 1.2436 + } else { 1.2437 + mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, 1.2438 + NS_LITERAL_STRING("true"), true); 1.2439 + } 1.2440 + } 1.2441 + 1.2442 + if (menuFrame && weakFrame.IsAlive()) { 1.2443 + // Find the popup that the menu is inside. Below, this popup will 1.2444 + // need to be hidden. 1.2445 + nsIFrame* frame = menuFrame->GetParent(); 1.2446 + while (frame) { 1.2447 + nsMenuPopupFrame* popupFrame = do_QueryFrame(frame); 1.2448 + if (popupFrame) { 1.2449 + popup = popupFrame->GetContent(); 1.2450 + break; 1.2451 + } 1.2452 + frame = frame->GetParent(); 1.2453 + } 1.2454 + 1.2455 + nsPresContext* presContext = menuFrame->PresContext(); 1.2456 + nsCOMPtr<nsIPresShell> shell = presContext->PresShell(); 1.2457 + nsRefPtr<nsViewManager> kungFuDeathGrip = shell->GetViewManager(); 1.2458 + 1.2459 + // Deselect ourselves. 1.2460 + if (mCloseMenuMode != CloseMenuMode_None) 1.2461 + menuFrame->SelectMenu(false); 1.2462 + 1.2463 + AutoHandlingUserInputStatePusher userInpStatePusher(mUserInput, nullptr, 1.2464 + shell->GetDocument()); 1.2465 + nsContentUtils::DispatchXULCommand(mMenu, mIsTrusted, nullptr, shell, 1.2466 + mControl, mAlt, mShift, mMeta); 1.2467 + } 1.2468 + 1.2469 + if (popup && mCloseMenuMode != CloseMenuMode_None) 1.2470 + pm->HidePopup(popup, mCloseMenuMode == CloseMenuMode_Auto, true, false, false); 1.2471 + 1.2472 + return NS_OK; 1.2473 +}