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