LCOV - code coverage report
Current view: top level - dom/xul - nsXULPopupListener.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 10 198 5.1 %
Date: 2017-07-14 16:53:18 Functions: 4 17 23.5 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* This Source Code Form is subject to the terms of the Mozilla Public
       3             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       4             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       5             : 
       6             : /*
       7             :   This file provides the implementation for xul popup listener which
       8             :   tracks xul popups and context menus
       9             :  */
      10             : 
      11             : #include "nsXULPopupListener.h"
      12             : #include "nsCOMPtr.h"
      13             : #include "nsGkAtoms.h"
      14             : #include "nsIDOMElement.h"
      15             : #include "nsIDOMXULElement.h"
      16             : #include "nsIDOMNodeList.h"
      17             : #include "nsIDOMDocument.h"
      18             : #include "nsIDOMDocumentXBL.h"
      19             : #include "nsContentCID.h"
      20             : #include "nsContentUtils.h"
      21             : #include "nsXULPopupManager.h"
      22             : #include "nsIScriptContext.h"
      23             : #include "nsIDOMWindow.h"
      24             : #include "nsIDOMXULDocument.h"
      25             : #include "nsIDocument.h"
      26             : #include "nsServiceManagerUtils.h"
      27             : #include "nsIPrincipal.h"
      28             : #include "nsIScriptSecurityManager.h"
      29             : #include "nsLayoutUtils.h"
      30             : #include "mozilla/ReflowInput.h"
      31             : #include "nsIObjectLoadingContent.h"
      32             : #include "mozilla/EventStateManager.h"
      33             : #include "mozilla/EventStates.h"
      34             : #include "mozilla/Preferences.h"
      35             : #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
      36             : #include "mozilla/dom/EventTarget.h"
      37             : #include "mozilla/dom/FragmentOrElement.h"
      38             : 
      39             : // for event firing in context menus
      40             : #include "nsPresContext.h"
      41             : #include "nsIPresShell.h"
      42             : #include "nsFocusManager.h"
      43             : #include "nsPIDOMWindow.h"
      44             : #include "nsViewManager.h"
      45             : #include "nsError.h"
      46             : #include "nsMenuFrame.h"
      47             : 
      48             : using namespace mozilla;
      49             : using namespace mozilla::dom;
      50             : 
      51             : // on win32 and os/2, context menus come up on mouse up. On other platforms,
      52             : // they appear on mouse down. Certain bits of code care about this difference.
      53             : #if defined(XP_WIN)
      54             : #define NS_CONTEXT_MENU_IS_MOUSEUP 1
      55             : #endif
      56             : 
      57          25 : nsXULPopupListener::nsXULPopupListener(mozilla::dom::Element* aElement,
      58          25 :                                        bool aIsContext)
      59          25 :   : mElement(aElement), mPopupContent(nullptr), mIsContext(aIsContext)
      60             : {
      61          25 : }
      62             : 
      63           0 : nsXULPopupListener::~nsXULPopupListener(void)
      64             : {
      65           0 :   ClosePopup();
      66           0 : }
      67             : 
      68           0 : NS_IMPL_CYCLE_COLLECTION(nsXULPopupListener, mElement, mPopupContent)
      69          75 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPopupListener)
      70          50 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPopupListener)
      71             : 
      72           0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXULPopupListener)
      73             :   // If the owner, mElement, can be skipped, so can we.
      74           0 :   if (tmp->mElement) {
      75           0 :     return mozilla::dom::FragmentOrElement::CanSkip(tmp->mElement, true);
      76             :   }
      77           0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
      78             : 
      79           0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXULPopupListener)
      80           0 :   if (tmp->mElement) {
      81           0 :     return mozilla::dom::FragmentOrElement::CanSkipInCC(tmp->mElement);
      82             :   }
      83           0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
      84             : 
      85           0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXULPopupListener)
      86           0 :   if (tmp->mElement) {
      87           0 :     return mozilla::dom::FragmentOrElement::CanSkipThis(tmp->mElement);
      88             :   }
      89           0 : NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
      90             : 
      91          75 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULPopupListener)
      92          50 :   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
      93          25 :   NS_INTERFACE_MAP_ENTRY(nsISupports)
      94          25 : NS_INTERFACE_MAP_END
      95             : 
      96             : ////////////////////////////////////////////////////////////////
      97             : // nsIDOMEventListener
      98             : 
      99             : nsresult
     100           0 : nsXULPopupListener::HandleEvent(nsIDOMEvent* aEvent)
     101             : {
     102           0 :   nsAutoString eventType;
     103           0 :   aEvent->GetType(eventType);
     104             : 
     105           0 :   if(!((eventType.EqualsLiteral("mousedown") && !mIsContext) ||
     106           0 :        (eventType.EqualsLiteral("contextmenu") && mIsContext)))
     107           0 :     return NS_OK;
     108             : 
     109             :   int16_t button;
     110             : 
     111           0 :   nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
     112           0 :   if (!mouseEvent) {
     113             :     //non-ui event passed in.  bad things.
     114           0 :     return NS_OK;
     115             :   }
     116             : 
     117             :   // Get the node that was clicked on.
     118           0 :   EventTarget* target = mouseEvent->AsEvent()->InternalDOMEvent()->GetTarget();
     119           0 :   nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(target);
     120             : 
     121           0 :   if (!targetNode && mIsContext) {
     122             :     // Not a DOM node, see if it's the DOM window (bug 380818).
     123           0 :     nsCOMPtr<nsPIDOMWindowInner> domWin = do_QueryInterface(target);
     124           0 :     if (!domWin) {
     125           0 :       return NS_ERROR_DOM_WRONG_TYPE_ERR;
     126             :     }
     127             :     // Try to use the root node as target node.
     128           0 :     nsCOMPtr<nsIDocument> doc = domWin->GetDoc();
     129             : 
     130           0 :     if (doc)
     131           0 :       targetNode = do_QueryInterface(doc->GetRootElement());
     132           0 :     if (!targetNode) {
     133           0 :       return NS_ERROR_FAILURE;
     134             :     }
     135             :   }
     136             : 
     137           0 :   nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
     138           0 :   if (!targetContent) {
     139           0 :     return NS_OK;
     140             :   }
     141           0 :   if (EventStateManager::IsRemoteTarget(targetContent)) {
     142           0 :     return NS_OK;
     143             :   }
     144             : 
     145             :   bool preventDefault;
     146           0 :   mouseEvent->AsEvent()->GetDefaultPrevented(&preventDefault);
     147           0 :   if (preventDefault && targetNode && mIsContext) {
     148             :     // Someone called preventDefault on a context menu.
     149             :     // Let's make sure they are allowed to do so.
     150             :     bool eventEnabled =
     151           0 :       Preferences::GetBool("dom.event.contextmenu.enabled", true);
     152           0 :     if (!eventEnabled) {
     153             :       // If the target node is for plug-in, we should not open XUL context
     154             :       // menu on windowless plug-ins.
     155           0 :       nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(targetNode);
     156             :       uint32_t type;
     157           0 :       if (olc && NS_SUCCEEDED(olc->GetDisplayedType(&type)) &&
     158           0 :           type == nsIObjectLoadingContent::TYPE_PLUGIN) {
     159           0 :         return NS_OK;
     160             :       }
     161             : 
     162             :       // The user wants his contextmenus.  Let's make sure that this is a website
     163             :       // and not chrome since there could be places in chrome which don't want
     164             :       // contextmenus.
     165           0 :       nsCOMPtr<nsINode> node = do_QueryInterface(targetNode);
     166           0 :       if (node) {
     167           0 :         nsCOMPtr<nsIPrincipal> system;
     168           0 :         nsContentUtils::GetSecurityManager()->
     169           0 :           GetSystemPrincipal(getter_AddRefs(system));
     170           0 :         if (node->NodePrincipal() != system) {
     171             :           // This isn't chrome.  Cancel the preventDefault() and
     172             :           // let the event go forth.
     173           0 :           preventDefault = false;
     174             :         }
     175             :       }
     176             :     }
     177             :   }
     178             : 
     179           0 :   if (preventDefault) {
     180             :     // someone called preventDefault. bail.
     181           0 :     return NS_OK;
     182             :   }
     183             : 
     184             :   // prevent popups on menu and menuitems as they handle their own popups
     185             :   // This was added for bug 96920.
     186             :   // If a menu item child was clicked on that leads to a popup needing
     187             :   // to show, we know (guaranteed) that we're dealing with a menu or
     188             :   // submenu of an already-showing popup.  We don't need to do anything at all.
     189           0 :   if (!mIsContext) {
     190           0 :     if (targetContent &&
     191           0 :         targetContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem))
     192           0 :       return NS_OK;
     193             :   }
     194             : 
     195           0 :   if (mIsContext) {
     196             : #ifndef NS_CONTEXT_MENU_IS_MOUSEUP
     197           0 :     uint16_t inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
     198           0 :     mouseEvent->GetMozInputSource(&inputSource);
     199           0 :     bool isTouch = inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
     200             :     // If the context menu launches on mousedown,
     201             :     // we have to fire focus on the content we clicked on
     202           0 :     FireFocusOnTargetContent(targetNode, isTouch);
     203             : #endif
     204             :   }
     205             :   else {
     206             :     // Only open popups when the left mouse button is down.
     207           0 :     mouseEvent->GetButton(&button);
     208           0 :     if (button != 0)
     209           0 :       return NS_OK;
     210             :   }
     211             : 
     212             :   // Open the popup. LaunchPopup will call StopPropagation and PreventDefault
     213             :   // in the right situations.
     214           0 :   LaunchPopup(aEvent, targetContent);
     215             : 
     216           0 :   return NS_OK;
     217             : }
     218             : 
     219             : #ifndef NS_CONTEXT_MENU_IS_MOUSEUP
     220             : nsresult
     221           0 : nsXULPopupListener::FireFocusOnTargetContent(nsIDOMNode* aTargetNode, bool aIsTouch)
     222             : {
     223             :   nsresult rv;
     224           0 :   nsCOMPtr<nsIDOMDocument> domDoc;
     225           0 :   rv = aTargetNode->GetOwnerDocument(getter_AddRefs(domDoc));
     226           0 :   if(NS_SUCCEEDED(rv) && domDoc)
     227             :   {
     228           0 :     nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
     229             : 
     230             :     // Get nsIDOMElement for targetNode
     231           0 :     nsIPresShell *shell = doc->GetShell();
     232           0 :     if (!shell)
     233           0 :       return NS_ERROR_FAILURE;
     234             : 
     235             :     // strong reference to keep this from going away between events
     236             :     // XXXbz between what events?  We don't use this local at all!
     237           0 :     RefPtr<nsPresContext> context = shell->GetPresContext();
     238             : 
     239           0 :     nsCOMPtr<nsIContent> content = do_QueryInterface(aTargetNode);
     240           0 :     nsIFrame* targetFrame = content->GetPrimaryFrame();
     241           0 :     if (!targetFrame) return NS_ERROR_FAILURE;
     242             : 
     243           0 :     const nsStyleUserInterface* ui = targetFrame->StyleUserInterface();
     244           0 :     bool suppressBlur = (ui->mUserFocus == StyleUserFocus::Ignore);
     245             : 
     246           0 :     nsCOMPtr<nsIDOMElement> element;
     247           0 :     nsCOMPtr<nsIContent> newFocus = do_QueryInterface(content);
     248             : 
     249           0 :     nsIFrame* currFrame = targetFrame;
     250             :     // Look for the nearest enclosing focusable frame.
     251           0 :     while (currFrame) {
     252             :         int32_t tabIndexUnused;
     253           0 :         if (currFrame->IsFocusable(&tabIndexUnused, true)) {
     254           0 :           newFocus = currFrame->GetContent();
     255           0 :           nsCOMPtr<nsIDOMElement> domElement(do_QueryInterface(newFocus));
     256           0 :           if (domElement) {
     257           0 :             element = domElement;
     258           0 :             break;
     259             :           }
     260             :         }
     261           0 :         currFrame = currFrame->GetParent();
     262             :     }
     263             : 
     264           0 :     nsIFocusManager* fm = nsFocusManager::GetFocusManager();
     265           0 :     if (fm) {
     266           0 :       if (element) {
     267             :         uint32_t focusFlags = nsIFocusManager::FLAG_BYMOUSE |
     268           0 :                               nsIFocusManager::FLAG_NOSCROLL;
     269           0 :         if (aIsTouch) {
     270           0 :           focusFlags |= nsIFocusManager::FLAG_BYTOUCH;
     271             :         }
     272           0 :         fm->SetFocus(element, focusFlags);
     273           0 :       } else if (!suppressBlur) {
     274           0 :         nsPIDOMWindowOuter *window = doc->GetWindow();
     275           0 :         fm->ClearFocus(window);
     276             :       }
     277             :     }
     278             : 
     279           0 :     EventStateManager* esm = context->EventStateManager();
     280           0 :     nsCOMPtr<nsIContent> focusableContent = do_QueryInterface(element);
     281           0 :     esm->SetContentState(focusableContent, NS_EVENT_STATE_ACTIVE);
     282             :   }
     283           0 :   return rv;
     284             : }
     285             : #endif
     286             : 
     287             : // ClosePopup
     288             : //
     289             : // Do everything needed to shut down the popup.
     290             : //
     291             : // NOTE: This routine is safe to call even if the popup is already closed.
     292             : //
     293             : void
     294           0 : nsXULPopupListener::ClosePopup()
     295             : {
     296           0 :   if (mPopupContent) {
     297             :     // this is called when the listener is going away, so make sure that the
     298             :     // popup is hidden. Use asynchronous hiding just to be safe so we don't
     299             :     // fire events during destruction.
     300           0 :     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
     301           0 :     if (pm)
     302           0 :       pm->HidePopup(mPopupContent, false, true, true, false);
     303           0 :     mPopupContent = nullptr;  // release the popup
     304             :   }
     305           0 : } // ClosePopup
     306             : 
     307             : static already_AddRefed<nsIContent>
     308           0 : GetImmediateChild(nsIContent* aContent, nsIAtom *aTag)
     309             : {
     310           0 :   for (nsIContent* child = aContent->GetFirstChild();
     311           0 :        child;
     312           0 :        child = child->GetNextSibling()) {
     313           0 :     if (child->IsXULElement(aTag)) {
     314           0 :       nsCOMPtr<nsIContent> ret = child;
     315           0 :       return ret.forget();
     316             :     }
     317             :   }
     318             : 
     319           0 :   return nullptr;
     320             : }
     321             : 
     322             : //
     323             : // LaunchPopup
     324             : //
     325             : // Given the element on which the event was triggered and the mouse locations in
     326             : // Client and widget coordinates, popup a new window showing the appropriate
     327             : // content.
     328             : //
     329             : // aTargetContent is the target of the mouse event aEvent that triggered the
     330             : // popup. mElement is the element that the popup menu is attached to.
     331             : // aTargetContent may be equal to mElement or it may be a descendant.
     332             : //
     333             : // This looks for an attribute on |mElement| of the appropriate popup type
     334             : // (popup, context) and uses that attribute's value as an ID for
     335             : // the popup content in the document.
     336             : //
     337             : nsresult
     338           0 : nsXULPopupListener::LaunchPopup(nsIDOMEvent* aEvent, nsIContent* aTargetContent)
     339             : {
     340           0 :   nsresult rv = NS_OK;
     341             : 
     342           0 :   nsAutoString identifier;
     343           0 :   nsIAtom* type = mIsContext ? nsGkAtoms::context : nsGkAtoms::popup;
     344           0 :   bool hasPopupAttr = mElement->GetAttr(kNameSpaceID_None, type, identifier);
     345             : 
     346           0 :   if (identifier.IsEmpty()) {
     347           0 :     hasPopupAttr = mElement->GetAttr(kNameSpaceID_None,
     348           0 :                           mIsContext ? nsGkAtoms::contextmenu : nsGkAtoms::menu,
     349           0 :                           identifier) || hasPopupAttr;
     350             :   }
     351             : 
     352           0 :   if (hasPopupAttr) {
     353           0 :     aEvent->StopPropagation();
     354           0 :     aEvent->PreventDefault();
     355             :   }
     356             : 
     357           0 :   if (identifier.IsEmpty())
     358           0 :     return rv;
     359             : 
     360             :   // Try to find the popup content and the document.
     361           0 :   nsCOMPtr<nsIDocument> document = mElement->GetComposedDoc();
     362           0 :   if (!document) {
     363           0 :     NS_WARNING("No document!");
     364           0 :     return NS_ERROR_FAILURE;
     365             :   }
     366             : 
     367             :   // Handle the _child case for popups and context menus
     368           0 :   nsCOMPtr<nsIContent> popup;
     369           0 :   if (identifier.EqualsLiteral("_child")) {
     370           0 :     popup = GetImmediateChild(mElement, nsGkAtoms::menupopup);
     371           0 :     if (!popup) {
     372           0 :       nsCOMPtr<nsIDOMDocumentXBL> nsDoc(do_QueryInterface(document));
     373           0 :       nsCOMPtr<nsIDOMNodeList> list;
     374           0 :       nsCOMPtr<nsIDOMElement> el = do_QueryInterface(mElement);
     375           0 :       nsDoc->GetAnonymousNodes(el, getter_AddRefs(list));
     376           0 :       if (list) {
     377             :         uint32_t ctr,listLength;
     378           0 :         nsCOMPtr<nsIDOMNode> node;
     379           0 :         list->GetLength(&listLength);
     380           0 :         for (ctr = 0; ctr < listLength; ctr++) {
     381           0 :           list->Item(ctr, getter_AddRefs(node));
     382           0 :           nsCOMPtr<nsIContent> childContent(do_QueryInterface(node));
     383             : 
     384           0 :           if (childContent->NodeInfo()->Equals(nsGkAtoms::menupopup,
     385             :                                                kNameSpaceID_XUL)) {
     386           0 :             popup.swap(childContent);
     387           0 :             break;
     388             :           }
     389             :         }
     390             :       }
     391             :     }
     392           0 :   } else if (!mElement->IsInUncomposedDoc() ||
     393           0 :              !(popup = document->GetElementById(identifier))) {
     394             :     // XXXsmaug Should we try to use ShadowRoot::GetElementById in case
     395             :     //          mElement is in shadow DOM?
     396             :     //
     397             :     // Use getElementById to obtain the popup content and gracefully fail if
     398             :     // we didn't find any popup content in the document.
     399           0 :     NS_WARNING("GetElementById had some kind of spasm.");
     400           0 :     return rv;
     401             :   }
     402             : 
     403             :   // return if no popup was found or the popup is the element itself.
     404           0 :   if (!popup || popup == mElement)
     405           0 :     return NS_OK;
     406             : 
     407             :   // Submenus can't be used as context menus or popups, bug 288763.
     408             :   // Similar code also in nsXULTooltipListener::GetTooltipFor.
     409           0 :   nsIContent* parent = popup->GetParent();
     410           0 :   if (parent) {
     411           0 :     nsMenuFrame* menu = do_QueryFrame(parent->GetPrimaryFrame());
     412           0 :     if (menu)
     413           0 :       return NS_OK;
     414             :   }
     415             : 
     416           0 :   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
     417           0 :   if (!pm)
     418           0 :     return NS_OK;
     419             : 
     420             :   // For left-clicks, if the popup has an position attribute, or both the
     421             :   // popupanchor and popupalign attributes are used, anchor the popup to the
     422             :   // element, otherwise just open it at the screen position where the mouse
     423             :   // was clicked. Context menus always open at the mouse position.
     424           0 :   mPopupContent = popup;
     425           0 :   if (!mIsContext &&
     426           0 :       (mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::position) ||
     427           0 :        (mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupanchor) &&
     428           0 :         mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupalign)))) {
     429           0 :     pm->ShowPopup(mPopupContent, mElement, EmptyString(), 0, 0,
     430           0 :                   false, true, false, aEvent);
     431             :   }
     432             :   else {
     433           0 :     int32_t xPos = 0, yPos = 0;
     434           0 :     nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
     435           0 :     mouseEvent->GetScreenX(&xPos);
     436           0 :     mouseEvent->GetScreenY(&yPos);
     437             : 
     438           0 :     pm->ShowPopupAtScreen(mPopupContent, xPos, yPos, mIsContext, aEvent);
     439             :   }
     440             : 
     441           0 :   return NS_OK;
     442             : }

Generated by: LCOV version 1.13