tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsXULPopupListener.cpp (9657B)


      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 
     13 #include "XULButtonElement.h"
     14 #include "mozilla/BasePrincipal.h"
     15 #include "mozilla/EventStateManager.h"
     16 #include "mozilla/Preferences.h"
     17 #include "mozilla/ReflowInput.h"
     18 #include "mozilla/dom/Document.h"
     19 #include "mozilla/dom/DocumentInlines.h"
     20 #include "mozilla/dom/Element.h"
     21 #include "mozilla/dom/Event.h"  // for Event
     22 #include "mozilla/dom/EventTarget.h"
     23 #include "mozilla/dom/FragmentOrElement.h"
     24 #include "mozilla/dom/MouseEvent.h"
     25 #include "mozilla/dom/MouseEventBinding.h"
     26 #include "nsCOMPtr.h"
     27 #include "nsContentUtils.h"
     28 #include "nsGkAtoms.h"
     29 #include "nsIObjectLoadingContent.h"
     30 #include "nsIScriptContext.h"
     31 #include "nsLayoutUtils.h"
     32 #include "nsServiceManagerUtils.h"
     33 #include "nsXULPopupManager.h"
     34 
     35 // for event firing in context menus
     36 #include "nsError.h"
     37 #include "nsFocusManager.h"
     38 #include "nsPIDOMWindow.h"
     39 #include "nsPresContext.h"
     40 
     41 using namespace mozilla;
     42 using namespace mozilla::dom;
     43 
     44 // on win32 and os/2, context menus come up on mouse up. On other platforms,
     45 // they appear on mouse down. Certain bits of code care about this difference.
     46 #if defined(XP_WIN)
     47 #  define NS_CONTEXT_MENU_IS_MOUSEUP 1
     48 #endif
     49 
     50 nsXULPopupListener::nsXULPopupListener(mozilla::dom::Element* aElement,
     51                                       bool aIsContext)
     52    : mElement(aElement), mPopupContent(nullptr), mIsContext(aIsContext) {}
     53 
     54 nsXULPopupListener::~nsXULPopupListener(void) { ClosePopup(); }
     55 
     56 NS_IMPL_CYCLE_COLLECTION(nsXULPopupListener, mElement, mPopupContent)
     57 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPopupListener)
     58 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPopupListener)
     59 
     60 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXULPopupListener)
     61  // If the owner, mElement, can be skipped, so can we.
     62  if (tmp->mElement) {
     63    return mozilla::dom::FragmentOrElement::CanSkip(tmp->mElement, true);
     64  }
     65 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
     66 
     67 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXULPopupListener)
     68  if (tmp->mElement) {
     69    return mozilla::dom::FragmentOrElement::CanSkipInCC(tmp->mElement);
     70  }
     71 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
     72 
     73 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXULPopupListener)
     74  if (tmp->mElement) {
     75    return mozilla::dom::FragmentOrElement::CanSkipThis(tmp->mElement);
     76  }
     77 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
     78 
     79 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULPopupListener)
     80  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
     81  NS_INTERFACE_MAP_ENTRY(nsISupports)
     82 NS_INTERFACE_MAP_END
     83 
     84 ////////////////////////////////////////////////////////////////
     85 // nsIDOMEventListener
     86 
     87 nsresult nsXULPopupListener::HandleEvent(Event* aEvent) {
     88  nsAutoString eventType;
     89  aEvent->GetType(eventType);
     90 
     91  if (!((eventType.EqualsLiteral("mousedown") && !mIsContext) ||
     92        (eventType.EqualsLiteral("contextmenu") && mIsContext)))
     93    return NS_OK;
     94 
     95  MouseEvent* mouseEvent = aEvent->AsMouseEvent();
     96  if (!mouseEvent) {
     97    // non-ui event passed in.  bad things.
     98    return NS_OK;
     99  }
    100 
    101  // Get the node that was clicked on.
    102  nsCOMPtr<nsIContent> targetContent =
    103      nsIContent::FromEventTargetOrNull(mouseEvent->GetTarget());
    104  if (!targetContent) {
    105    return NS_OK;
    106  }
    107 
    108  if (nsIContent* content =
    109          nsIContent::FromEventTargetOrNull(mouseEvent->GetOriginalTarget())) {
    110    if (EventStateManager::IsTopLevelRemoteTarget(content)) {
    111      return NS_OK;
    112    }
    113  }
    114 
    115  bool preventDefault = mouseEvent->DefaultPrevented();
    116  if (preventDefault && mIsContext) {
    117    // Someone called preventDefault on a context menu.
    118    // Let's make sure they are allowed to do so.
    119    bool eventEnabled =
    120        Preferences::GetBool("dom.event.contextmenu.enabled", true);
    121    if (!eventEnabled) {
    122      // The user wants his contextmenus.  Let's make sure that this is a
    123      // website and not chrome since there could be places in chrome which
    124      // don't want contextmenus.
    125      if (!targetContent->NodePrincipal()->IsSystemPrincipal()) {
    126        // This isn't chrome.  Cancel the preventDefault() and
    127        // let the event go forth.
    128        preventDefault = false;
    129      }
    130    }
    131  }
    132 
    133  if (preventDefault) {
    134    // someone called preventDefault. bail.
    135    return NS_OK;
    136  }
    137 
    138  // prevent popups on menu and menuitems as they handle their own popups
    139  // This was added for bug 96920.
    140  // If a menu item child was clicked on that leads to a popup needing
    141  // to show, we know (guaranteed) that we're dealing with a menu or
    142  // submenu of an already-showing popup.  We don't need to do anything at all.
    143  if (!mIsContext &&
    144      targetContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem)) {
    145    return NS_OK;
    146  }
    147 
    148  if (!mIsContext && mouseEvent->Button() != 0) {
    149    // Only open popups when the left mouse button is down.
    150    return NS_OK;
    151  }
    152 
    153  // Open the popup. LaunchPopup will call StopPropagation and PreventDefault
    154  // in the right situations.
    155  LaunchPopup(mouseEvent);
    156 
    157  return NS_OK;
    158 }
    159 
    160 // ClosePopup
    161 //
    162 // Do everything needed to shut down the popup.
    163 //
    164 // NOTE: This routine is safe to call even if the popup is already closed.
    165 //
    166 void nsXULPopupListener::ClosePopup() {
    167  if (mPopupContent) {
    168    // this is called when the listener is going away, so make sure that the
    169    // popup is hidden. Use asynchronous hiding just to be safe so we don't
    170    // fire events during destruction.
    171    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
    172    if (pm)
    173      pm->HidePopup(mPopupContent,
    174                    {HidePopupOption::DeselectMenu, HidePopupOption::Async});
    175    mPopupContent = nullptr;  // release the popup
    176  }
    177 }  // ClosePopup
    178 
    179 static already_AddRefed<Element> GetImmediateChild(nsIContent* aContent,
    180                                                   nsAtom* aTag) {
    181  for (nsIContent* child = aContent->GetFirstChild(); child;
    182       child = child->GetNextSibling()) {
    183    if (child->IsXULElement(aTag)) {
    184      RefPtr<Element> ret = child->AsElement();
    185      return ret.forget();
    186    }
    187  }
    188 
    189  return nullptr;
    190 }
    191 
    192 //
    193 // LaunchPopup
    194 //
    195 // Given the element on which the event was triggered and the mouse locations in
    196 // Client and widget coordinates, popup a new window showing the appropriate
    197 // content.
    198 //
    199 // aTargetContent is the target of the mouse event aEvent that triggered the
    200 // popup. mElement is the element that the popup menu is attached to.
    201 // aTargetContent may be equal to mElement or it may be a descendant.
    202 //
    203 // This looks for an attribute on |mElement| of the appropriate popup type
    204 // (popup, context) and uses that attribute's value as an ID for
    205 // the popup content in the document.
    206 //
    207 nsresult nsXULPopupListener::LaunchPopup(MouseEvent* aEvent) {
    208  nsresult rv = NS_OK;
    209 
    210  nsAutoString identifier;
    211  nsAtom* type = mIsContext ? nsGkAtoms::context : nsGkAtoms::popup;
    212  bool hasPopupAttr = mElement->GetAttr(type, identifier);
    213 
    214  if (identifier.IsEmpty()) {
    215    hasPopupAttr =
    216        mElement->GetAttr(mIsContext ? nsGkAtoms::contextmenu : nsGkAtoms::menu,
    217                          identifier) ||
    218        hasPopupAttr;
    219  }
    220 
    221  if (hasPopupAttr) {
    222    aEvent->StopPropagation();
    223    aEvent->PreventDefault();
    224  }
    225 
    226  if (identifier.IsEmpty()) return rv;
    227 
    228  // Try to find the popup content and the document.
    229  nsCOMPtr<Document> document = mElement->GetComposedDoc();
    230  if (!document) {
    231    NS_WARNING("No document!");
    232    return NS_ERROR_FAILURE;
    233  }
    234 
    235  // Handle the _child case for popups and context menus
    236  RefPtr<Element> popup;
    237  if (identifier.EqualsLiteral("_child")) {
    238    popup = GetImmediateChild(mElement, nsGkAtoms::menupopup);
    239  } else if (!mElement->IsInUncomposedDoc() ||
    240             !(popup = document->GetElementById(identifier))) {
    241    // XXXsmaug Should we try to use ShadowRoot::GetElementById in case
    242    //          mElement is in shadow DOM?
    243    //
    244    // Use getElementById to obtain the popup content and gracefully fail if
    245    // we didn't find any popup content in the document.
    246    NS_WARNING("GetElementById had some kind of spasm.");
    247    return rv;
    248  }
    249 
    250  // return if no popup was found or the popup is the element itself.
    251  if (!popup || popup == mElement) {
    252    return NS_OK;
    253  }
    254 
    255  // Submenus can't be used as context menus or popups, bug 288763.
    256  // Similar code also in nsXULTooltipListener::GetTooltipFor.
    257  if (auto* button = XULButtonElement::FromNodeOrNull(popup->GetParent())) {
    258    if (button->IsMenu()) {
    259      return NS_OK;
    260    }
    261  }
    262 
    263  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
    264  if (!pm) return NS_OK;
    265 
    266  // For left-clicks, if the popup has an position attribute, or both the
    267  // popupanchor and popupalign attributes are used, anchor the popup to the
    268  // element, otherwise just open it at the screen position where the mouse
    269  // was clicked. Context menus always open at the mouse position.
    270  mPopupContent = popup;
    271  if (!mIsContext && (mPopupContent->HasAttr(nsGkAtoms::position) ||
    272                      (mPopupContent->HasAttr(nsGkAtoms::popupanchor) &&
    273                       mPopupContent->HasAttr(nsGkAtoms::popupalign)))) {
    274    pm->ShowPopup(mPopupContent, mElement, u""_ns, 0, 0, false, true, false,
    275                  aEvent);
    276  } else {
    277    const CSSIntPoint pos =
    278        RoundedToInt(aEvent->ScreenPoint(CallerType::System));
    279    pm->ShowPopupAtScreen(mPopupContent, pos.x, pos.y, mIsContext, aEvent);
    280  }
    281 
    282  return NS_OK;
    283 }