1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* ***** BEGIN LICENSE BLOCK *****
3
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
5
* The contents of this file are subject to the Netscape Public License
6
* Version 1.1 (the "License"); you may not use this file except in
7
* compliance with the License. You may obtain a copy of the License at
8
* http://www.mozilla.org/NPL/
10
* Software distributed under the License is distributed on an "AS IS" basis,
11
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12
* for the specific language governing rights and limitations under the
15
* The Original Code is mozilla.org code.
17
* The Initial Developer of the Original Code is
18
* Netscape Communications Corporation.
19
* Portions created by the Initial Developer are Copyright (C) 1998
20
* the Initial Developer. All Rights Reserved.
23
* Original Author: David W. Hyatt (hyatt@netscape.com)
24
* Dean Tessman <dean_tessman@hotmail.com>
25
* Pierre Phaneuf <pp@ludusdesign.com>
26
* Robert O'Callahan <roc+moz@cs.cmu.edu>
29
* Alternatively, the contents of this file may be used under the terms of
30
* either the GNU General Public License Version 2 or later (the "GPL"), or
31
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32
* in which case the provisions of the GPL or the LGPL are applicable instead
33
* of those above. If you wish to allow use of your version of this file only
34
* under the terms of either the GPL or the LGPL, and not to allow others to
35
* use your version of this file under the terms of the NPL, indicate your
36
* decision by deleting the provisions above and replace them with the notice
37
* and other provisions required by the GPL or the LGPL. If you do not delete
38
* the provisions above, a recipient may use your version of this file under
39
* the terms of any one of the NPL, the GPL or the LGPL.
41
* ***** END LICENSE BLOCK ***** */
44
This file provides the implementation for xul popup listener which
45
tracks xul popups and context menus
49
#include "nsXULAtoms.h"
50
#include "nsIDOMElement.h"
51
#include "nsIDOMXULElement.h"
52
#include "nsIDOMNodeList.h"
53
#include "nsIDOMDocument.h"
54
#include "nsIDOMDocumentXBL.h"
55
#include "nsIXULPopupListener.h"
56
#include "nsIDOMMouseListener.h"
57
#include "nsIDOMContextMenuListener.h"
58
#include "nsContentCID.h"
59
#include "nsContentUtils.h"
61
#include "nsIScriptGlobalObject.h"
62
#include "nsIScriptContext.h"
63
#include "nsIDOMWindowInternal.h"
64
#include "nsIDOMXULDocument.h"
65
#include "nsIDocument.h"
66
#include "nsIContent.h"
67
#include "nsIDOMMouseEvent.h"
68
#include "nsIDOMNSUIEvent.h"
69
#include "nsIDOMEventTarget.h"
70
#include "nsIDOMNSEvent.h"
71
#include "nsIPrefService.h"
72
#include "nsIPrefBranch.h"
73
#include "nsIServiceManagerUtils.h"
74
#include "nsIPrincipal.h"
75
#include "nsIScriptSecurityManager.h"
77
#include "nsIBoxObject.h"
78
#include "nsIPopupBoxObject.h"
80
// for event firing in context menus
81
#include "nsIPresContext.h"
82
#include "nsIPresShell.h"
83
#include "nsIEventStateManager.h"
87
// on win32 and os/2, context menus come up on mouse up. On other platforms,
88
// they appear on mouse down. Certain bits of code care about this difference.
89
#if !defined(XP_WIN) && !defined(XP_OS2)
90
#define NS_CONTEXT_MENU_IS_MOUSEUP 1
94
////////////////////////////////////////////////////////////////////////
97
// This is the popup listener implementation for popup menus and context menus.
99
class XULPopupListenerImpl : public nsIXULPopupListener,
100
public nsIDOMMouseListener,
101
public nsIDOMContextMenuListener
104
XULPopupListenerImpl(void);
105
virtual ~XULPopupListenerImpl(void);
111
// nsIXULPopupListener
112
NS_IMETHOD Init(nsIDOMElement* aElement, const XULPopupType& popupType);
114
// nsIDOMMouseListener
115
NS_IMETHOD MouseDown(nsIDOMEvent* aMouseEvent);
116
NS_IMETHOD MouseUp(nsIDOMEvent* aMouseEvent) { return NS_OK; };
117
NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent) { return NS_OK; };
118
NS_IMETHOD MouseDblClick(nsIDOMEvent* aMouseEvent) { return NS_OK; };
119
NS_IMETHOD MouseOver(nsIDOMEvent* aMouseEvent) { return NS_OK; };
120
NS_IMETHOD MouseOut(nsIDOMEvent* aMouseEvent) { return NS_OK; };
122
// nsIDOMContextMenuListener
123
NS_IMETHOD ContextMenu(nsIDOMEvent* aContextMenuEvent);
125
// nsIDOMEventListener
126
NS_IMETHOD HandleEvent(nsIDOMEvent* anEvent) { return NS_OK; };
130
virtual nsresult LaunchPopup(nsIDOMEvent* anEvent);
131
virtual nsresult LaunchPopup(PRInt32 aClientX, PRInt32 aClientY) ;
132
virtual void ClosePopup();
136
nsresult PreLaunchPopup(nsIDOMEvent* aMouseEvent);
137
nsresult FireFocusOnTargetContent(nsIDOMNode* aTargetNode);
139
// |mElement| is the node to which this listener is attached.
140
nsIDOMElement* mElement; // Weak ref. The element will go away first.
142
// The popup that is getting shown on top of mElement.
143
nsIDOMElement* mPopupContent;
145
// The type of the popup
146
XULPopupType popupType;
150
////////////////////////////////////////////////////////////////////////
152
XULPopupListenerImpl::XULPopupListenerImpl(void)
153
: mElement(nsnull), mPopupContent(nsnull)
157
XULPopupListenerImpl::~XULPopupListenerImpl(void)
163
fprintf(stdout, "%d - RDF: XULPopupListenerImpl\n", gInstanceCount);
167
NS_IMPL_ADDREF(XULPopupListenerImpl)
168
NS_IMPL_RELEASE(XULPopupListenerImpl)
171
NS_INTERFACE_MAP_BEGIN(XULPopupListenerImpl)
172
NS_INTERFACE_MAP_ENTRY(nsIXULPopupListener)
173
NS_INTERFACE_MAP_ENTRY(nsIDOMMouseListener)
174
NS_INTERFACE_MAP_ENTRY(nsIDOMContextMenuListener)
175
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMMouseListener)
176
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULPopupListener)
180
XULPopupListenerImpl::Init(nsIDOMElement* aElement, const XULPopupType& popup)
182
mElement = aElement; // Weak reference. Don't addref it.
187
////////////////////////////////////////////////////////////////
188
// nsIDOMMouseListener
191
XULPopupListenerImpl::MouseDown(nsIDOMEvent* aMouseEvent)
193
if(popupType != eXULPopupType_context)
194
return PreLaunchPopup(aMouseEvent);
200
XULPopupListenerImpl::ContextMenu(nsIDOMEvent* aMouseEvent)
202
if(popupType == eXULPopupType_context)
203
return PreLaunchPopup(aMouseEvent);
209
XULPopupListenerImpl::PreLaunchPopup(nsIDOMEvent* aMouseEvent)
213
nsCOMPtr<nsIDOMMouseEvent> mouseEvent;
214
mouseEvent = do_QueryInterface(aMouseEvent);
216
//non-ui event passed in. bad things.
220
// check if someone has attempted to prevent this action.
221
nsCOMPtr<nsIDOMNSUIEvent> nsUIEvent;
222
nsUIEvent = do_QueryInterface(mouseEvent);
227
// Get the node that was clicked on.
228
nsCOMPtr<nsIDOMEventTarget> target;
229
mouseEvent->GetTarget(getter_AddRefs(target));
230
nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(target);
232
PRBool preventDefault;
233
nsUIEvent->GetPreventDefault(&preventDefault);
234
if (preventDefault && targetNode && popupType == eXULPopupType_context) {
235
// Someone called preventDefault on a context menu.
236
// Let's make sure they are allowed to do so.
237
nsCOMPtr<nsIPrefService> prefService =
238
do_GetService(NS_PREFSERVICE_CONTRACTID);
240
NS_ENSURE_TRUE(prefService, NS_ERROR_FAILURE);
242
nsCOMPtr<nsIPrefBranch> prefBranch;
243
prefService->GetBranch(nsnull, getter_AddRefs(prefBranch));
246
nsresult rv = prefBranch->GetBoolPref("dom.event.contextmenu.enabled",
248
if (NS_SUCCEEDED(rv) && !eventEnabled) {
249
// The user wants his contextmenus. Let's make sure that this is a website
250
// and not chrome since there could be places in chrome which don't want
252
nsCOMPtr<nsIDocument> doc;
253
nsCOMPtr<nsIPrincipal> prin;
254
nsContentUtils::GetDocumentAndPrincipal(targetNode,
256
getter_AddRefs(prin));
258
nsCOMPtr<nsIPrincipal> system;
259
nsContentUtils::GetSecurityManager()->GetSystemPrincipal(getter_AddRefs(system));
260
if (prin != system) {
261
// This isn't chrome. Cancel the preventDefault() and
262
// let the event go forth.
263
preventDefault = PR_FALSE;
269
if (preventDefault) {
270
// someone called preventDefault. bail.
274
// This is a gross hack to deal with a recursive popup situation happening in AIM code.
275
// See http://bugzilla.mozilla.org/show_bug.cgi?id=96920.
276
// If a menu item child was clicked on that leads to a popup needing
277
// to show, we know (guaranteed) that we're dealing with a menu or
278
// submenu of an already-showing popup. We don't need to do anything at all.
279
if (popupType == eXULPopupType_popup) {
280
nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
281
nsIAtom *tag = targetContent->Tag();
282
if (tag == nsXULAtoms::menu || tag == nsXULAtoms::menuitem)
286
// Get the document with the popup.
287
nsCOMPtr<nsIContent> content = do_QueryInterface(mElement);
289
// Turn the document into a XUL document so we can use SetPopupNode.
290
nsCOMPtr<nsIDOMXULDocument> xulDocument = do_QueryInterface(content->GetDocument());
292
NS_ERROR("Popup attached to an element that isn't in XUL!");
293
return NS_ERROR_FAILURE;
296
// Store clicked-on node in xul document for context menus and menu popups.
297
xulDocument->SetPopupNode( targetNode );
299
nsCOMPtr<nsIDOMNSEvent> nsevent(do_QueryInterface(aMouseEvent));
302
case eXULPopupType_popup:
303
// Check for left mouse button down
304
mouseEvent->GetButton(&button);
306
// Time to launch a popup menu.
307
LaunchPopup(aMouseEvent);
310
nsevent->PreventBubble();
313
aMouseEvent->PreventDefault();
316
case eXULPopupType_context:
318
// Time to launch a context menu
319
#ifndef NS_CONTEXT_MENU_IS_MOUSEUP
320
// If the context menu launches on mousedown,
321
// we have to fire focus on the content we clicked on
322
FireFocusOnTargetContent(targetNode);
324
LaunchPopup(aMouseEvent);
327
nsevent->PreventBubble();
330
aMouseEvent->PreventDefault();
337
XULPopupListenerImpl::FireFocusOnTargetContent(nsIDOMNode* aTargetNode)
340
nsCOMPtr<nsIDOMDocument> domDoc;
341
rv = aTargetNode->GetOwnerDocument(getter_AddRefs(domDoc));
342
if(NS_SUCCEEDED(rv) && domDoc)
344
nsCOMPtr<nsIPresContext> context;
345
nsCOMPtr<nsIDocument> tempdoc = do_QueryInterface(domDoc);
347
// Get nsIDOMElement for targetNode
348
nsIPresShell *shell = tempdoc->GetShellAt(0);
350
return NS_ERROR_FAILURE;
352
shell->GetPresContext(getter_AddRefs(context));
354
nsCOMPtr<nsIContent> content = do_QueryInterface(aTargetNode);
355
nsIFrame* targetFrame;
356
shell->GetPrimaryFrameFor(content, &targetFrame);
357
if (!targetFrame) return NS_ERROR_FAILURE;
359
PRBool suppressBlur = PR_FALSE;
360
const nsStyleUserInterface* ui = targetFrame->GetStyleUserInterface();
361
suppressBlur = (ui->mUserFocus == NS_STYLE_USER_FOCUS_IGNORE);
363
nsCOMPtr<nsIDOMElement> element;
364
nsCOMPtr<nsIContent> newFocus = do_QueryInterface(content);
366
nsIFrame* currFrame = targetFrame;
367
// Look for the nearest enclosing focusable frame.
369
const nsStyleUserInterface* ui = currFrame->GetStyleUserInterface();
370
if ((ui->mUserFocus != NS_STYLE_USER_FOCUS_IGNORE) &&
371
(ui->mUserFocus != NS_STYLE_USER_FOCUS_NONE))
373
newFocus = currFrame->GetContent();
374
nsCOMPtr<nsIDOMElement> domElement(do_QueryInterface(newFocus));
376
element = domElement;
380
currFrame = currFrame->GetParent();
382
nsCOMPtr<nsIContent> focusableContent = do_QueryInterface(element);
383
nsIEventStateManager *esm = context->EventStateManager();
385
if (focusableContent)
386
focusableContent->SetFocus(context);
387
else if (!suppressBlur)
388
esm->SetContentState(nsnull, NS_EVENT_STATE_FOCUS);
390
esm->SetContentState(focusableContent, NS_EVENT_STATE_ACTIVE);
398
// Do everything needed to shut down the popup.
400
// NOTE: This routine is safe to call even if the popup is already closed.
403
XULPopupListenerImpl :: ClosePopup ( )
405
if ( mPopupContent ) {
406
nsCOMPtr<nsIDOMXULElement> popupElement(do_QueryInterface(mPopupContent));
407
nsCOMPtr<nsIBoxObject> boxObject;
409
popupElement->GetBoxObject(getter_AddRefs(boxObject));
410
nsCOMPtr<nsIPopupBoxObject> popupObject(do_QueryInterface(boxObject));
412
popupObject->HidePopup();
414
mPopupContent = nsnull; // release the popup
423
XULPopupListenerImpl::LaunchPopup ( nsIDOMEvent* anEvent )
425
// Retrieve our x and y position.
426
nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(anEvent) );
428
//non-ui event passed in. bad things.
433
mouseEvent->GetClientX(&xPos);
434
mouseEvent->GetClientY(&yPos);
436
return LaunchPopup(xPos, yPos);
441
GetImmediateChild(nsIContent* aContent, nsIAtom *aTag, nsIContent** aResult)
444
PRInt32 childCount = aContent->GetChildCount();
445
for (PRInt32 i = 0; i < childCount; i++) {
446
nsIContent *child = aContent->GetChildAt(i);
447
if (child->Tag() == aTag) {
457
static void ConvertPosition(nsIDOMElement* aPopupElt, nsString& aAnchor, nsString& aAlign, PRInt32& aY)
459
nsAutoString position;
460
aPopupElt->GetAttribute(NS_LITERAL_STRING("position"), position);
461
if (position.IsEmpty())
464
if (position.Equals(NS_LITERAL_STRING("before_start"))) {
465
aAnchor.Assign(NS_LITERAL_STRING("topleft"));
466
aAlign.Assign(NS_LITERAL_STRING("bottomleft"));
468
else if (position.Equals(NS_LITERAL_STRING("before_end"))) {
469
aAnchor.Assign(NS_LITERAL_STRING("topright"));
470
aAlign.Assign(NS_LITERAL_STRING("bottomright"));
472
else if (position.Equals(NS_LITERAL_STRING("after_start"))) {
473
aAnchor.Assign(NS_LITERAL_STRING("bottomleft"));
474
aAlign.Assign(NS_LITERAL_STRING("topleft"));
476
else if (position.Equals(NS_LITERAL_STRING("after_end"))) {
477
aAnchor.Assign(NS_LITERAL_STRING("bottomright"));
478
aAlign.Assign(NS_LITERAL_STRING("topright"));
480
else if (position.Equals(NS_LITERAL_STRING("start_before"))) {
481
aAnchor.Assign(NS_LITERAL_STRING("topleft"));
482
aAlign.Assign(NS_LITERAL_STRING("topright"));
484
else if (position.Equals(NS_LITERAL_STRING("start_after"))) {
485
aAnchor.Assign(NS_LITERAL_STRING("bottomleft"));
486
aAlign.Assign(NS_LITERAL_STRING("bottomright"));
488
else if (position.Equals(NS_LITERAL_STRING("end_before"))) {
489
aAnchor.Assign(NS_LITERAL_STRING("topright"));
490
aAlign.Assign(NS_LITERAL_STRING("topleft"));
492
else if (position.Equals(NS_LITERAL_STRING("end_after"))) {
493
aAnchor.Assign(NS_LITERAL_STRING("bottomright"));
494
aAlign.Assign(NS_LITERAL_STRING("bottomleft"));
496
else if (position.Equals(NS_LITERAL_STRING("overlap"))) {
497
aAnchor.Assign(NS_LITERAL_STRING("topleft"));
498
aAlign.Assign(NS_LITERAL_STRING("topleft"));
500
else if (position.Equals(NS_LITERAL_STRING("after_pointer")))
507
// Given the element on which the event was triggered and the mouse locations in
508
// Client and widget coordinates, popup a new window showing the appropriate
511
// This looks for an attribute on |aElement| of the appropriate popup type
512
// (popup, context) and uses that attribute's value as an ID for
513
// the popup content in the document.
516
XULPopupListenerImpl::LaunchPopup(PRInt32 aClientX, PRInt32 aClientY)
520
nsAutoString type(NS_LITERAL_STRING("popup"));
521
if ( popupType == eXULPopupType_context ) {
522
type.Assign(NS_LITERAL_STRING("context"));
524
// position the menu two pixels down and to the right from the current
525
// mouse position. This makes it easier to dismiss the menu by just
531
nsAutoString identifier;
532
mElement->GetAttribute(type, identifier);
534
if (identifier.IsEmpty()) {
535
if (type.Equals(NS_LITERAL_STRING("popup")))
536
mElement->GetAttribute(NS_LITERAL_STRING("menu"), identifier);
537
else if (type.Equals(NS_LITERAL_STRING("context")))
538
mElement->GetAttribute(NS_LITERAL_STRING("contextmenu"), identifier);
539
if (identifier.IsEmpty())
543
// Try to find the popup content and the document.
544
nsCOMPtr<nsIContent> content = do_QueryInterface(mElement);
545
nsCOMPtr<nsIDocument> document = content->GetDocument();
547
// Turn the document into a DOM document so we can use getElementById
548
nsCOMPtr<nsIDOMDocument> domDocument = do_QueryInterface(document);
550
NS_ERROR("Popup attached to an element that isn't in XUL!");
551
return NS_ERROR_FAILURE;
554
// Handle the _child case for popups and context menus
555
nsCOMPtr<nsIDOMElement> popupContent;
557
if (identifier == NS_LITERAL_STRING("_child")) {
558
nsCOMPtr<nsIContent> popup;
560
GetImmediateChild(content, nsXULAtoms::menupopup, getter_AddRefs(popup));
562
popupContent = do_QueryInterface(popup);
564
nsCOMPtr<nsIDOMDocumentXBL> nsDoc(do_QueryInterface(domDocument));
565
nsCOMPtr<nsIDOMNodeList> list;
566
nsDoc->GetAnonymousNodes(mElement, getter_AddRefs(list));
568
PRUint32 ctr,listLength;
569
nsCOMPtr<nsIDOMNode> node;
570
list->GetLength(&listLength);
571
for (ctr = 0; ctr < listLength; ctr++) {
572
list->Item(ctr, getter_AddRefs(node));
573
nsCOMPtr<nsIContent> childContent(do_QueryInterface(node));
575
nsINodeInfo *ni = childContent->GetNodeInfo();
577
if (ni && ni->Equals(nsXULAtoms::menupopup, kNameSpaceID_XUL)) {
578
popupContent = do_QueryInterface(childContent);
585
else if (NS_FAILED(rv = domDocument->GetElementById(identifier,
586
getter_AddRefs(popupContent)))) {
587
// Use getElementById to obtain the popup content and gracefully fail if
588
// we didn't find any popup content in the document.
589
NS_ERROR("GetElementById had some kind of spasm.");
595
// We have some popup content. Obtain our window.
596
nsCOMPtr<nsIDOMWindowInternal> domWindow =
597
do_QueryInterface(document->GetScriptGlobalObject());
600
// Find out if we're anchored.
601
mPopupContent = popupContent.get();
603
nsAutoString anchorAlignment;
604
mPopupContent->GetAttribute(NS_LITERAL_STRING("popupanchor"), anchorAlignment);
606
nsAutoString popupAlignment;
607
mPopupContent->GetAttribute(NS_LITERAL_STRING("popupalign"), popupAlignment);
609
PRInt32 xPos = aClientX, yPos = aClientY;
611
ConvertPosition(mPopupContent, anchorAlignment, popupAlignment, yPos);
612
if (!anchorAlignment.IsEmpty() && !popupAlignment.IsEmpty())
615
nsCOMPtr<nsIBoxObject> popupBox;
616
nsCOMPtr<nsIDOMXULElement> xulPopupElt(do_QueryInterface(mPopupContent));
617
xulPopupElt->GetBoxObject(getter_AddRefs(popupBox));
618
nsCOMPtr<nsIPopupBoxObject> popupBoxObject(do_QueryInterface(popupBox));
620
popupBoxObject->ShowPopup(mElement, mPopupContent, xPos, yPos,
621
type.get(), anchorAlignment.get(),
622
popupAlignment.get());
628
////////////////////////////////////////////////////////////////
630
NS_NewXULPopupListener(nsIXULPopupListener** pop)
632
XULPopupListenerImpl* popup = new XULPopupListenerImpl();
634
return NS_ERROR_OUT_OF_MEMORY;