From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/xul/nsXULPopupListener.cpp | 285 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 dom/xul/nsXULPopupListener.cpp (limited to 'dom/xul/nsXULPopupListener.cpp') diff --git a/dom/xul/nsXULPopupListener.cpp b/dom/xul/nsXULPopupListener.cpp new file mode 100644 index 0000000000..6f6ac31582 --- /dev/null +++ b/dom/xul/nsXULPopupListener.cpp @@ -0,0 +1,285 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + This file provides the implementation for xul popup listener which + tracks xul popups and context menus + */ + +#include "nsXULPopupListener.h" +#include "XULButtonElement.h" +#include "nsCOMPtr.h" +#include "nsGkAtoms.h" +#include "nsContentCID.h" +#include "nsContentUtils.h" +#include "nsXULPopupManager.h" +#include "nsIScriptContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/DocumentInlines.h" +#include "nsServiceManagerUtils.h" +#include "nsLayoutUtils.h" +#include "mozilla/ReflowInput.h" +#include "nsIObjectLoadingContent.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" // for Event +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/FragmentOrElement.h" +#include "mozilla/dom/MouseEvent.h" +#include "mozilla/dom/MouseEventBinding.h" + +// for event firing in context menus +#include "nsPresContext.h" +#include "nsFocusManager.h" +#include "nsPIDOMWindow.h" +#include "nsViewManager.h" +#include "nsError.h" + +using namespace mozilla; +using namespace mozilla::dom; + +// on win32 and os/2, context menus come up on mouse up. On other platforms, +// they appear on mouse down. Certain bits of code care about this difference. +#if defined(XP_WIN) +# define NS_CONTEXT_MENU_IS_MOUSEUP 1 +#endif + +nsXULPopupListener::nsXULPopupListener(mozilla::dom::Element* aElement, + bool aIsContext) + : mElement(aElement), mPopupContent(nullptr), mIsContext(aIsContext) {} + +nsXULPopupListener::~nsXULPopupListener(void) { ClosePopup(); } + +NS_IMPL_CYCLE_COLLECTION(nsXULPopupListener, mElement, mPopupContent) +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPopupListener) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPopupListener) + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXULPopupListener) + // If the owner, mElement, can be skipped, so can we. + if (tmp->mElement) { + return mozilla::dom::FragmentOrElement::CanSkip(tmp->mElement, true); + } +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXULPopupListener) + if (tmp->mElement) { + return mozilla::dom::FragmentOrElement::CanSkipInCC(tmp->mElement); + } +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXULPopupListener) + if (tmp->mElement) { + return mozilla::dom::FragmentOrElement::CanSkipThis(tmp->mElement); + } +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULPopupListener) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +//////////////////////////////////////////////////////////////// +// nsIDOMEventListener + +nsresult nsXULPopupListener::HandleEvent(Event* aEvent) { + nsAutoString eventType; + aEvent->GetType(eventType); + + if (!((eventType.EqualsLiteral("mousedown") && !mIsContext) || + (eventType.EqualsLiteral("contextmenu") && mIsContext))) + return NS_OK; + + MouseEvent* mouseEvent = aEvent->AsMouseEvent(); + if (!mouseEvent) { + // non-ui event passed in. bad things. + return NS_OK; + } + + // Get the node that was clicked on. + nsCOMPtr targetContent = + nsIContent::FromEventTargetOrNull(mouseEvent->GetTarget()); + if (!targetContent) { + return NS_OK; + } + + if (nsIContent* content = + nsIContent::FromEventTargetOrNull(mouseEvent->GetOriginalTarget())) { + if (EventStateManager::IsTopLevelRemoteTarget(content)) { + return NS_OK; + } + } + + bool preventDefault = mouseEvent->DefaultPrevented(); + if (preventDefault && mIsContext) { + // Someone called preventDefault on a context menu. + // Let's make sure they are allowed to do so. + bool eventEnabled = + Preferences::GetBool("dom.event.contextmenu.enabled", true); + if (!eventEnabled) { + // The user wants his contextmenus. Let's make sure that this is a + // website and not chrome since there could be places in chrome which + // don't want contextmenus. + if (!targetContent->NodePrincipal()->IsSystemPrincipal()) { + // This isn't chrome. Cancel the preventDefault() and + // let the event go forth. + preventDefault = false; + } + } + } + + if (preventDefault) { + // someone called preventDefault. bail. + return NS_OK; + } + + // prevent popups on menu and menuitems as they handle their own popups + // This was added for bug 96920. + // If a menu item child was clicked on that leads to a popup needing + // to show, we know (guaranteed) that we're dealing with a menu or + // submenu of an already-showing popup. We don't need to do anything at all. + if (!mIsContext && + targetContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem)) { + return NS_OK; + } + + if (!mIsContext && mouseEvent->Button() != 0) { + // Only open popups when the left mouse button is down. + return NS_OK; + } + + // Open the popup. LaunchPopup will call StopPropagation and PreventDefault + // in the right situations. + LaunchPopup(mouseEvent); + + return NS_OK; +} + +// ClosePopup +// +// Do everything needed to shut down the popup. +// +// NOTE: This routine is safe to call even if the popup is already closed. +// +void nsXULPopupListener::ClosePopup() { + if (mPopupContent) { + // this is called when the listener is going away, so make sure that the + // popup is hidden. Use asynchronous hiding just to be safe so we don't + // fire events during destruction. + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) + pm->HidePopup(mPopupContent, + {HidePopupOption::DeselectMenu, HidePopupOption::Async}); + mPopupContent = nullptr; // release the popup + } +} // ClosePopup + +static already_AddRefed GetImmediateChild(nsIContent* aContent, + nsAtom* aTag) { + for (nsIContent* child = aContent->GetFirstChild(); child; + child = child->GetNextSibling()) { + if (child->IsXULElement(aTag)) { + RefPtr ret = child->AsElement(); + return ret.forget(); + } + } + + return nullptr; +} + +// +// LaunchPopup +// +// Given the element on which the event was triggered and the mouse locations in +// Client and widget coordinates, popup a new window showing the appropriate +// content. +// +// aTargetContent is the target of the mouse event aEvent that triggered the +// popup. mElement is the element that the popup menu is attached to. +// aTargetContent may be equal to mElement or it may be a descendant. +// +// This looks for an attribute on |mElement| of the appropriate popup type +// (popup, context) and uses that attribute's value as an ID for +// the popup content in the document. +// +nsresult nsXULPopupListener::LaunchPopup(MouseEvent* aEvent) { + nsresult rv = NS_OK; + + nsAutoString identifier; + nsAtom* type = mIsContext ? nsGkAtoms::context : nsGkAtoms::popup; + bool hasPopupAttr = mElement->GetAttr(kNameSpaceID_None, type, identifier); + + if (identifier.IsEmpty()) { + hasPopupAttr = + mElement->GetAttr(kNameSpaceID_None, + mIsContext ? nsGkAtoms::contextmenu : nsGkAtoms::menu, + identifier) || + hasPopupAttr; + } + + if (hasPopupAttr) { + aEvent->StopPropagation(); + aEvent->PreventDefault(); + } + + if (identifier.IsEmpty()) return rv; + + // Try to find the popup content and the document. + nsCOMPtr document = mElement->GetComposedDoc(); + if (!document) { + NS_WARNING("No document!"); + return NS_ERROR_FAILURE; + } + + // Handle the _child case for popups and context menus + RefPtr popup; + if (identifier.EqualsLiteral("_child")) { + popup = GetImmediateChild(mElement, nsGkAtoms::menupopup); + } else if (!mElement->IsInUncomposedDoc() || + !(popup = document->GetElementById(identifier))) { + // XXXsmaug Should we try to use ShadowRoot::GetElementById in case + // mElement is in shadow DOM? + // + // Use getElementById to obtain the popup content and gracefully fail if + // we didn't find any popup content in the document. + NS_WARNING("GetElementById had some kind of spasm."); + return rv; + } + + // return if no popup was found or the popup is the element itself. + if (!popup || popup == mElement) { + return NS_OK; + } + + // Submenus can't be used as context menus or popups, bug 288763. + // Similar code also in nsXULTooltipListener::GetTooltipFor. + if (auto* button = XULButtonElement::FromNodeOrNull(popup->GetParent())) { + if (button->IsMenu()) { + return NS_OK; + } + } + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) return NS_OK; + + // For left-clicks, if the popup has an position attribute, or both the + // popupanchor and popupalign attributes are used, anchor the popup to the + // element, otherwise just open it at the screen position where the mouse + // was clicked. Context menus always open at the mouse position. + mPopupContent = popup; + if (!mIsContext && + (mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::position) || + (mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupanchor) && + mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupalign)))) { + pm->ShowPopup(mPopupContent, mElement, u""_ns, 0, 0, false, true, false, + aEvent); + } else { + CSSIntPoint pos = aEvent->ScreenPoint(CallerType::System); + pm->ShowPopupAtScreen(mPopupContent, pos.x, pos.y, mIsContext, aEvent); + } + + return NS_OK; +} -- cgit v1.2.3