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