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