Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/events/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 286 kB image not shown  

Quelle  EventStateManager.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */


#include "EventStateManager.h"

#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/Attributes.h"
#include "mozilla/EditorBase.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventForwards.h"
#include "mozilla/Hal.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/Likely.h"
#include "mozilla/FocusModel.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/PointerLockManager.h"
#include "mozilla/PresShell.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ScrollTypes.h"
#include "mozilla/TextComposition.h"
#include "mozilla/TextControlElement.h"
#include "mozilla/TextEditor.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TouchEvents.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/BrowserBridgeChild.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/DOMIntersectionObserver.h"
#include "mozilla/dom/DragEvent.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/FrameLoaderBinding.h"
#include "mozilla/dom/HTMLLabelElement.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/PointerEventHandler.h"
#include "mozilla/dom/UIEvent.h"
#include "mozilla/dom/UIEventBinding.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/WheelEventBinding.h"
#include "mozilla/glean/ProcesstoolsMetrics.h"
#include "mozilla/ScrollContainerFrame.h"
#include "mozilla/StaticPrefs_accessibility.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_mousewheel.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/StaticPrefs_zoom.h"

#include "ContentEventHandler.h"
#include "IMEContentObserver.h"
#include "WheelHandlingHelper.h"
#include "RemoteDragStartData.h"

#include "nsCommandParams.h"
#include "nsCOMPtr.h"
#include "nsCopySupport.h"
#include "nsFocusManager.h"
#include "nsGenericHTMLElement.h"
#include "nsIClipboard.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "mozilla/dom/Document.h"
#include "nsICookieJarSettings.h"
#include "nsIFrame.h"
#include "nsFrameLoaderOwner.h"
#include "nsIWeakReferenceUtils.h"
#include "nsIWidget.h"
#include "nsLiteralString.h"
#include "nsPresContext.h"
#include "nsTArray.h"
#include "nsGkAtoms.h"
#include "nsIFormControl.h"
#include "nsComboboxControlFrame.h"
#include "nsIDOMXULControlElement.h"
#include "nsNameSpaceManager.h"
#include "nsIBaseWindow.h"
#include "nsFrameSelection.h"
#include "nsPIDOMWindow.h"
#include "nsPIWindowRoot.h"
#include "nsIWebNavigation.h"
#include "nsIDocumentViewer.h"
#include "nsFrameManager.h"
#include "nsIBrowserChild.h"
#include "nsMenuPopupFrame.h"

#include "nsIObserverService.h"
#include "nsIDocShell.h"

#include "nsSubDocumentFrame.h"
#include "nsLayoutUtils.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsUnicharUtils.h"
#include "nsContentUtils.h"

#include "imgIContainer.h"
#include "nsIProperties.h"
#include "nsISupportsPrimitives.h"

#include "nsServiceManagerUtils.h"
#include "nsITimer.h"
#include "nsFontMetrics.h"
#include "nsIDragService.h"
#include "nsIDragSession.h"
#include "mozilla/dom/DataTransfer.h"
#include "nsContentAreaDragDrop.h"
#include "nsTreeBodyFrame.h"
#include "nsIController.h"
#include "mozilla/Services.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Record.h"
#include "mozilla/dom/Selection.h"

#include "mozilla/Preferences.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/ProfilerLabels.h"
#include "Units.h"

#ifdef XP_MACOSX
#  import <ApplicationServices/ApplicationServices.h>
#endif

namespace mozilla {

using namespace dom;

static const LayoutDeviceIntPoint kInvalidRefPoint =
    LayoutDeviceIntPoint(-1, -1);

static uint32_t gMouseOrKeyboardEventCounter = 0;
static nsITimer* gUserInteractionTimer = nullptr;
static nsITimerCallback* gUserInteractionTimerCallback = nullptr;

static const double kCursorLoadingTimeout = 1000;  // ms
MOZ_RUNINIT static AutoWeakFrame gLastCursorSourceFrame;
static TimeStamp gLastCursorUpdateTime;
static TimeStamp gTypingStartTime;
static TimeStamp gTypingEndTime;
static int32_t gTypingInteractionKeyPresses = 0;
MOZ_RUNINIT static dom::InteractionData gTypingInteraction = {};

static inline int32_t RoundDown(double aDouble) {
  return (aDouble > 0) ? static_cast<int32_t>(floor(aDouble))
                       : static_cast<int32_t>(ceil(aDouble));
}

static bool IsSelectingLink(nsIFrame* aTargetFrame) {
  if (!aTargetFrame) {
    return false;
  }
  const nsFrameSelection* frameSel = aTargetFrame->GetConstFrameSelection();
  if (!frameSel || !frameSel->GetDragState()) {
    return false;
  }

  if (!nsContentUtils::GetClosestLinkInFlatTree(aTargetFrame->GetContent())) {
    return false;
  }
  return true;
}

static UniquePtr<WidgetMouseEvent> CreateMouseOrPointerWidgetEvent(
    WidgetMouseEvent* aMouseEvent, EventMessage aMessage,
    EventTarget* aRelatedTarget);

/**
 * Returns the common ancestor for mouseup purpose, given the
 * current mouseup target and the previous mousedown target.
 */

static nsINode* GetCommonAncestorForMouseUp(
    nsINode* aCurrentMouseUpTarget, nsINode* aLastMouseDownTarget,
    Maybe<FormControlType>& aLastMouseDownInputControlType) {
  if (!aCurrentMouseUpTarget || !aLastMouseDownTarget) {
    return nullptr;
  }

  if (aCurrentMouseUpTarget == aLastMouseDownTarget) {
    return aCurrentMouseUpTarget;
  }

  // Build the chain of parents
  AutoTArray<nsINode*, 30> parents1;
  do {
    parents1.AppendElement(aCurrentMouseUpTarget);
    aCurrentMouseUpTarget = aCurrentMouseUpTarget->GetFlattenedTreeParentNode();
  } while (aCurrentMouseUpTarget);

  AutoTArray<nsINode*, 30> parents2;
  do {
    parents2.AppendElement(aLastMouseDownTarget);
    if (aLastMouseDownTarget == parents1.LastElement()) {
      break;
    }
    aLastMouseDownTarget = aLastMouseDownTarget->GetFlattenedTreeParentNode();
  } while (aLastMouseDownTarget);

  // Find where the parent chain differs
  uint32_t pos1 = parents1.Length();
  uint32_t pos2 = parents2.Length();
  nsINode* parent = nullptr;
  for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
    nsINode* child1 = parents1.ElementAt(--pos1);
    nsINode* child2 = parents2.ElementAt(--pos2);
    if (child1 != child2) {
      break;
    }

    // If the input control type is different between mouseup and mousedown,
    // this is not a valid click.
    if (HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(child1)) {
      if (aLastMouseDownInputControlType.isSome() &&
          aLastMouseDownInputControlType.ref() != input->ControlType()) {
        break;
      }
    }
    parent = child1;
  }

  return parent;
}

static bool HasNativeKeyBindings(nsIContent* aContent,
                                 WidgetKeyboardEvent* aEvent) {
  MOZ_ASSERT(aEvent->mMessage == eKeyPress);

  if (!aContent) {
    return false;
  }

  const RefPtr<dom::Element> targetElement = aContent->AsElement();
  if (!targetElement) {
    return false;
  }

  const auto type = [&]() -> Maybe<NativeKeyBindingsType> {
    if (BrowserParent::GetFrom(targetElement)) {
      const nsCOMPtr<nsIWidget> widget = aEvent->mWidget;
      if (MOZ_UNLIKELY(!widget)) {
        return Nothing();
      }
      widget::InputContext context = widget->GetInputContext();
      return context.mIMEState.IsEditable()
                 ? Some(context.GetNativeKeyBindingsType())
                 : Nothing();
    }

    const autoconst textControlElement =
        TextControlElement::FromNode(targetElement);
    if (textControlElement &&
        textControlElement->IsSingleLineTextControlOrTextArea() &&
        !textControlElement->IsInDesignMode()) {
      return textControlElement->IsTextArea()
                 ? Some(NativeKeyBindingsType::MultiLineEditor)
                 : Some(NativeKeyBindingsType::SingleLineEditor);
    }
    return targetElement->IsEditable()
               ? Some(NativeKeyBindingsType::RichTextEditor)
               : Nothing();
  }();
  if (type.isNothing()) {
    return false;
  }

  const nsTArray<CommandInt>& commands =
      aEvent->EditCommandsConstRef(type.value());
  return !commands.IsEmpty();
}

LazyLogModule sMouseBoundaryLog("MouseBoundaryEvents");
LazyLogModule sPointerBoundaryLog("PointerBoundaryEvents");

/******************************************************************/
/* mozilla::UITimerCallback                                       */
/******************************************************************/

class UITimerCallback final : public nsITimerCallback, public nsINamed {
 public:
  UITimerCallback() : mPreviousCount(0) {}
  NS_DECL_ISUPPORTS
  NS_DECL_NSITIMERCALLBACK
  NS_DECL_NSINAMED
 private:
  ~UITimerCallback() = default;
  uint32_t mPreviousCount;
};

NS_IMPL_ISUPPORTS(UITimerCallback, nsITimerCallback, nsINamed)

// If aTimer is nullptr, this method always sends "user-interaction-inactive"
// notification.
NS_IMETHODIMP
UITimerCallback::Notify(nsITimer* aTimer) {
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (!obs) return NS_ERROR_FAILURE;
  if ((gMouseOrKeyboardEventCounter == mPreviousCount) || !aTimer) {
    gMouseOrKeyboardEventCounter = 0;
    obs->NotifyObservers(nullptr, "user-interaction-inactive", nullptr);
    if (gUserInteractionTimer) {
      gUserInteractionTimer->Cancel();
      NS_RELEASE(gUserInteractionTimer);
    }
  } else {
    obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
    EventStateManager::UpdateUserActivityTimer();

    if (XRE_IsParentProcess()) {
      hal::BatteryInformation batteryInfo;
      hal::GetCurrentBatteryInformation(&batteryInfo);
      glean::power_battery::percentage_when_user_active.AccumulateSingleSample(
          uint64_t(batteryInfo.level() * 100));
    }
  }
  mPreviousCount = gMouseOrKeyboardEventCounter;
  return NS_OK;
}

NS_IMETHODIMP
UITimerCallback::GetName(nsACString& aName) {
  aName.AssignLiteral("UITimerCallback_timer");
  return NS_OK;
}

/******************************************************************/
/* mozilla::OverOutElementsWrapper                                */
/******************************************************************/

NS_IMPL_CYCLE_COLLECTION(OverOutElementsWrapper, mDeepestEnterEventTarget,
                         mDispatchingOverEventTarget,
                         mDispatchingOutOrDeepestLeaveEventTarget)
NS_IMPL_CYCLE_COLLECTING_ADDREF(OverOutElementsWrapper)
NS_IMPL_CYCLE_COLLECTING_RELEASE(OverOutElementsWrapper)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OverOutElementsWrapper)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

already_AddRefed<nsIWidget> OverOutElementsWrapper::GetLastOverWidget() const {
  nsCOMPtr<nsIWidget> widget = do_QueryReferent(mLastOverWidget);
  return widget.forget();
}

void OverOutElementsWrapper::ContentRemoved(nsIContent& aContent) {
  if (!mDeepestEnterEventTarget) {
    return;
  }

  if (!nsContentUtils::ContentIsFlattenedTreeDescendantOf(
          mDeepestEnterEventTarget, &aContent)) {
    return;
  }

  LogModule* const logModule = mType == BoundaryEventType::Mouse
                                   ? sMouseBoundaryLog
                                   : sPointerBoundaryLog;

  if (!StaticPrefs::
          dom_events_mouse_pointer_boundary_keep_enter_targets_after_over_target_removed()) {
    MOZ_LOG(logModule, LogLevel::Info,
            ("The last \"over\" event target (%p) is removed",
             mDeepestEnterEventTarget.get()));
    StoreOverEventTargetAndDeepestEnterEventTarget(nullptr);
    return;
  }

  if (mDispatchingOverEventTarget &&
      (mDeepestEnterEventTarget == mDispatchingOverEventTarget ||
       nsContentUtils::ContentIsFlattenedTreeDescendantOf(
           mDispatchingOverEventTarget, &aContent))) {
    if (mDispatchingOverEventTarget ==
        mDispatchingOutOrDeepestLeaveEventTarget) {
      MOZ_LOG(logModule, LogLevel::Info,
              ("The dispatching \"%s\" event target (%p) is removed",
               LastOverEventTargetIsOutEventTarget() ? "out" : "leave",
               mDispatchingOutOrDeepestLeaveEventTarget.get()));
      mDispatchingOutOrDeepestLeaveEventTarget = nullptr;
    }
    MOZ_LOG(logModule, LogLevel::Info,
            ("The dispatching \"over\" event target (%p) is removed",
             mDispatchingOverEventTarget.get()));
    mDispatchingOverEventTarget = nullptr;
  }
  if (mDispatchingOutOrDeepestLeaveEventTarget &&
      (mDeepestEnterEventTarget == mDispatchingOutOrDeepestLeaveEventTarget ||
       nsContentUtils::ContentIsFlattenedTreeDescendantOf(
           mDispatchingOutOrDeepestLeaveEventTarget, &aContent))) {
    MOZ_LOG(logModule, LogLevel::Info,
            ("The dispatching \"%s\" event target (%p) is removed",
             LastOverEventTargetIsOutEventTarget() ? "out" : "leave",
             mDispatchingOutOrDeepestLeaveEventTarget.get()));
    mDispatchingOutOrDeepestLeaveEventTarget = nullptr;
  }
  MOZ_LOG(logModule, LogLevel::Info,
          ("The last \"%s\" event target (%p) is removed and now the last "
           "deepest enter target becomes %s(%p)",
           LastOverEventTargetIsOutEventTarget() ? "over" : "enter",
           mDeepestEnterEventTarget.get(),
           aContent.GetFlattenedTreeParent()
               ? ToString(*aContent.GetFlattenedTreeParent()).c_str()
               : "nullptr",
           aContent.GetFlattenedTreeParent()));
  UpdateDeepestEnterEventTarget(aContent.GetFlattenedTreeParent());
}

void OverOutElementsWrapper::TryToRestorePendingRemovedOverTarget(
    const WidgetEvent* aEvent) {
  if (!MaybeHasPendingRemovingOverEventTarget()) {
    return;
  }

  LogModule* const logModule = mType == BoundaryEventType::Mouse
                                   ? sMouseBoundaryLog
                                   : sPointerBoundaryLog;

  // If we receive a mouse event immediately, let's try to restore the last
  // "over" event target as the following "out" event target.  We assume that a
  // synthesized mousemove or another mouse event is being dispatched at latest
  // the next animation frame from the removal.  However, synthesized mouse move
  // which is enqueued by ContentRemoved() may not sent to this instance because
  // the target is considered with the latest layout, so the document of this
  // instance may be moved somewhere before the next animation frame.
  // Therefore, we should not restore the last "over" target if we receive an
  // unexpected event like a keyboard event, a wheel event, etc.
  if (aEvent->AsMouseEvent()) {
    // Restore the original "over" event target should be allowed only when it's
    // reconnected under the last deepest "enter" event target because we need
    // to dispatch "leave" events later at least on the ancestors which have
    // never been removed from the tree.
    // XXX If new ancestor is inserted between mDeepestEnterEventTarget and
    // mPendingToRemoveLastOverEventTarget, we will dispatch "leave" event even
    // though we have not dispatched "enter" event on the element.  For fixing
    // this, we need to store the full path of the last "out" event target when
    // it's removed from the tree.  I guess we can be relax for this issue
    // because this hack is required for web apps which reconnect the target
    // to the same position immediately.
    // XXX Should be IsInclusiveFlatTreeDescendantOf()?  However, it may
    // be reconnected into a subtree which is different from where the
    // last over element was.
    nsCOMPtr<nsIContent> pendingRemovingOverEventTarget =
        GetPendingRemovingOverEventTarget();
    if (pendingRemovingOverEventTarget &&
        pendingRemovingOverEventTarget->IsInclusiveDescendantOf(
            mDeepestEnterEventTarget)) {
      // StoreOverEventTargetAndDeepestEnterEventTarget() always resets
      // mLastOverWidget.  When we restore the pending removing "over" event
      // target, we need to keep storing the original "over" widget too.
      nsCOMPtr<nsIWeakReference> widget = std::move(mLastOverWidget);
      StoreOverEventTargetAndDeepestEnterEventTarget(
          pendingRemovingOverEventTarget);
      mLastOverWidget = std::move(widget);
      MOZ_LOG(logModule, LogLevel::Info,
              ("The \"over\" event target (%p) is restored",
               mDeepestEnterEventTarget.get()));
      return;
    }
    MOZ_LOG(logModule, LogLevel::Debug,
            ("Forgetting the last \"over\" event target (%p) because it is not "
             "reconnected under the deepest enter event target (%p)",
             mPendingRemovingOverEventTarget.get(),
             mDeepestEnterEventTarget.get()));
  } else {
    MOZ_LOG(logModule, LogLevel::Debug,
            ("Forgetting the last \"over\" event target (%p) because an "
             "unexpected event (%s) is being dispatched, that means that "
             "EventStateManager didn't receive a synthesized mousemove which "
             "should be dispatched at next animation frame from the removal",
             mPendingRemovingOverEventTarget.get(), ToChar(aEvent->mMessage)));
  }

  // Now, we should not restore mPendingRemovingOverEventTarget to
  // mDeepestEnterEventTarget anymore since mPendingRemovingOverEventTarget was
  // moved outside the subtree of mDeepestEnterEventTarget.
  mPendingRemovingOverEventTarget = nullptr;
}

void OverOutElementsWrapper::WillDispatchOverAndEnterEvent(
    nsIContent* aOverEventTarget) {
  StoreOverEventTargetAndDeepestEnterEventTarget(aOverEventTarget);
  // Store the first "over" event target we fire and don't refire "over" event
  // to that element while the first "over" event is still ongoing.
  mDispatchingOverEventTarget = aOverEventTarget;
}

void OverOutElementsWrapper::DidDispatchOverAndEnterEvent(
    nsIContent* aOriginalOverTargetInComposedDoc,
    nsIWidget* aOverEventTargetWidget) {
  mDispatchingOverEventTarget = nullptr;
  mLastOverWidget = do_GetWeakReference(aOverEventTargetWidget);

  // Pointer Events define that once the `pointerover` event target is removed
  // from the tree, `pointerout` should not be fired on that and the closest
  // connected ancestor at the target removal should be kept as the deepest
  // `pointerleave` target.  Therefore, we don't need the special handling for
  // `pointerout` event target if the last `pointerover` target is temporarily
  // removed from the tree.
  if (mType == OverOutElementsWrapper::BoundaryEventType::Pointer) {
    return;
  }

  // Assume that the caller checks whether aOriginalOverTarget is in the
  // original document.  If we don't enable the strict mouse/pointer event
  // boundary event dispatching by the pref (see below),
  // mDeepestEnterEventTarget is set to nullptr when the last "over" target is
  // removed.  Therefore, we cannot check whether aOriginalOverTarget is in the
  // original document here.
  if (!aOriginalOverTargetInComposedDoc) {
    return;
  }
  MOZ_ASSERT_IF(mDeepestEnterEventTarget,
                mDeepestEnterEventTarget->GetComposedDoc() ==
                    aOriginalOverTargetInComposedDoc->GetComposedDoc());
  // If the "mouseover" event target is removed temporarily while we're
  // dispatching "mouseover" and "mouseenter" events and the target gets back
  // under the deepest enter event target, we should restore the "mouseover"
  // target.
  if ((!StaticPrefs::
           dom_events_mouse_pointer_boundary_keep_enter_targets_after_over_target_removed() &&
       !mDeepestEnterEventTarget) ||
      (!LastOverEventTargetIsOutEventTarget() && mDeepestEnterEventTarget &&
       nsContentUtils::ContentIsFlattenedTreeDescendantOf(
           aOriginalOverTargetInComposedDoc, mDeepestEnterEventTarget))) {
    StoreOverEventTargetAndDeepestEnterEventTarget(
        aOriginalOverTargetInComposedDoc);
    LogModule* const logModule = mType == BoundaryEventType::Mouse
                                     ? sMouseBoundaryLog
                                     : sPointerBoundaryLog;
    MOZ_LOG(logModule, LogLevel::Info,
            ("The \"over\" event target (%p) is restored",
             mDeepestEnterEventTarget.get()));
  }
}

void OverOutElementsWrapper::StoreOverEventTargetAndDeepestEnterEventTarget(
    nsIContent* aOverEventTargetAndDeepestEnterEventTarget) {
  mDeepestEnterEventTarget = aOverEventTargetAndDeepestEnterEventTarget;
  mPendingRemovingOverEventTarget = nullptr;
  mDeepestEnterEventTargetIsOverEventTarget = !!mDeepestEnterEventTarget;
  mLastOverWidget = nullptr;  // Set it after dispatching the "over" event.
}

void OverOutElementsWrapper::UpdateDeepestEnterEventTarget(
    nsIContent* aDeepestEnterEventTarget) {
  if (MOZ_UNLIKELY(mDeepestEnterEventTarget == aDeepestEnterEventTarget)) {
    return;
  }

  if (!aDeepestEnterEventTarget) {
    // If the root element is removed, we don't need to dispatch "leave"
    // events on any elements.  Therefore, we can forget everything.
    StoreOverEventTargetAndDeepestEnterEventTarget(nullptr);
    return;
  }

  if (LastOverEventTargetIsOutEventTarget()) {
    MOZ_ASSERT(mDeepestEnterEventTarget);
    if (mType == BoundaryEventType::Pointer) {
      // The spec of Pointer Events defines that once the `pointerover` event
      // target is removed from the tree, `pointerout` should not be fired on
      // that and the closest connected ancestor at the target removal should be
      // kept as the deepest `pointerleave` target.  All browsers considers the
      // last `pointerover` event target is removed immediately when it occurs.
      // Therefore, we don't need the special handling which we do for the
      // `mouseout` event target below for considering whether we'll dispatch
      // `pointerout` on the last `pointerover` target.
      mPendingRemovingOverEventTarget = nullptr;
    } else {
      // Now, the `mouseout` event target is removed from the DOM at least
      // temporarily.  Let's keep storing it for restoring it if it's
      // reconnected into mDeepestEnterEventTarget in a tick because the other
      // browsers do not treat temporary removal of the last `mouseover` target
      // keeps storing it as the next `mouseout` event target.
      MOZ_ASSERT(!mPendingRemovingOverEventTarget);
      MOZ_ASSERT(mDeepestEnterEventTarget);
      mPendingRemovingOverEventTarget =
          do_GetWeakReference(mDeepestEnterEventTarget);
    }
  } else {
    MOZ_ASSERT(!mDeepestEnterEventTargetIsOverEventTarget);
    // If mDeepestEnterEventTarget is not the last "over" event target, we've
    // already done the complicated state managing above.  Therefore, we only
    // need to update mDeepestEnterEventTarget in this case.
  }
  mDeepestEnterEventTarget = aDeepestEnterEventTarget;
  mDeepestEnterEventTargetIsOverEventTarget = false;
  // Do not update mLastOverWidget here because it's required to ignore some
  // following pointer events which are fired on widget under different top
  // level widget.
}

/******************************************************************/
/* mozilla::EventStateManager                                     */
/******************************************************************/

static uint32_t sESMInstanceCount = 0;

bool EventStateManager::sNormalLMouseEventInProcess = false;
int16_t EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed;
EventStateManager* EventStateManager::sActiveESM = nullptr;
EventStateManager* EventStateManager::sCursorSettingManager = nullptr;
MOZ_RUNINIT AutoWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
LayoutDeviceIntPoint EventStateManager::sPreLockScreenPoint =
    LayoutDeviceIntPoint(0, 0);
LayoutDeviceIntPoint EventStateManager::sLastRefPoint = kInvalidRefPoint;
CSSIntPoint EventStateManager::sLastScreenPoint = CSSIntPoint(0, 0);
LayoutDeviceIntPoint EventStateManager::sSynthCenteringPoint = kInvalidRefPoint;
CSSIntPoint EventStateManager::sLastClientPoint = CSSIntPoint(0, 0);
MOZ_RUNINIT nsCOMPtr<nsIContent> EventStateManager::sDragOverContent = nullptr;

EventStateManager::WheelPrefs* EventStateManager::WheelPrefs::sInstance =
    nullptr;
EventStateManager::DeltaAccumulator*
    EventStateManager::DeltaAccumulator::sInstance = nullptr;

constexpr const StyleCursorKind kInvalidCursorKind =
    static_cast<StyleCursorKind>(255);

EventStateManager::EventStateManager()
    : mLockCursor(kInvalidCursorKind),
      mCurrentTarget(nullptr),
      // init d&d gesture state machine variables
      mGestureDownPoint(0, 0),
      mGestureModifiers(0),
      mGestureDownButtons(0),
      mGestureDownButton(0),
      mPresContext(nullptr),
      mShouldAlwaysUseLineDeltas(false),
      mShouldAlwaysUseLineDeltasInitialized(false),
      mGestureDownInTextControl(false),
      mInTouchDrag(false),
      m_haveShutdown(false) {
  if (sESMInstanceCount == 0) {
    gUserInteractionTimerCallback = new UITimerCallback();
    if (gUserInteractionTimerCallback) NS_ADDREF(gUserInteractionTimerCallback);
    UpdateUserActivityTimer();
  }
  ++sESMInstanceCount;
}

nsresult EventStateManager::UpdateUserActivityTimer() {
  if (!gUserInteractionTimerCallback) return NS_OK;

  if (!gUserInteractionTimer) {
    gUserInteractionTimer = NS_NewTimer().take();
  }

  if (gUserInteractionTimer) {
    gUserInteractionTimer->InitWithCallback(
        gUserInteractionTimerCallback,
        StaticPrefs::dom_events_user_interaction_interval(),
        nsITimer::TYPE_ONE_SHOT);
  }
  return NS_OK;
}

nsresult EventStateManager::Init() {
  nsCOMPtr<nsIObserverService> observerService =
      mozilla::services::GetObserverService();
  if (!observerService) return NS_ERROR_FAILURE;

  observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);

  return NS_OK;
}

bool EventStateManager::ShouldAlwaysUseLineDeltas() {
  if (MOZ_UNLIKELY(!mShouldAlwaysUseLineDeltasInitialized)) {
    mShouldAlwaysUseLineDeltasInitialized = true;
    mShouldAlwaysUseLineDeltas =
        !StaticPrefs::dom_event_wheel_deltaMode_lines_disabled();
    if (!mShouldAlwaysUseLineDeltas && mDocument) {
      if (nsIPrincipal* principal =
              mDocument->GetPrincipalForPrefBasedHacks()) {
        mShouldAlwaysUseLineDeltas = principal->IsURIInPrefList(
            "dom.event.wheel-deltaMode-lines.always-enabled");
      }
    }
  }
  return mShouldAlwaysUseLineDeltas;
}

EventStateManager::~EventStateManager() {
  ReleaseCurrentIMEContentObserver();

  if (sActiveESM == this) {
    sActiveESM = nullptr;
  }

  if (StaticPrefs::ui_click_hold_context_menus()) {
    KillClickHoldTimer();
  }

  if (sCursorSettingManager == this) {
    sCursorSettingManager = nullptr;
  }

  --sESMInstanceCount;
  if (sESMInstanceCount == 0) {
    WheelTransaction::Shutdown();
    if (gUserInteractionTimerCallback) {
      gUserInteractionTimerCallback->Notify(nullptr);
      NS_RELEASE(gUserInteractionTimerCallback);
    }
    if (gUserInteractionTimer) {
      gUserInteractionTimer->Cancel();
      NS_RELEASE(gUserInteractionTimer);
    }
    WheelPrefs::Shutdown();
    DeltaAccumulator::Shutdown();
  }

  if (sDragOverContent && sDragOverContent->OwnerDoc() == mDocument) {
    sDragOverContent = nullptr;
  }

  if (!m_haveShutdown) {
    Shutdown();

    // Don't remove from Observer service in Shutdown because Shutdown also
    // gets called from xpcom shutdown observer.  And we don't want to remove
    // from the service in that case.

    nsCOMPtr<nsIObserverService> observerService =
        mozilla::services::GetObserverService();
    if (observerService) {
      observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
    }
  }
}

nsresult EventStateManager::Shutdown() {
  m_haveShutdown = true;
  return NS_OK;
}

NS_IMETHODIMP
EventStateManager::Observe(nsISupports* aSubject, const char* aTopic,
                           const char16_t* someData) {
  if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
    Shutdown();
  }

  return NS_OK;
}

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventStateManager)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(EventStateManager)
NS_IMPL_CYCLE_COLLECTING_RELEASE(EventStateManager)

NS_IMPL_CYCLE_COLLECTION_WEAK(EventStateManager, mCurrentTargetContent,
                              mGestureDownContent, mGestureDownFrameOwner,
                              mLastLeftMouseDownInfo.mLastMouseDownContent,
                              mLastMiddleMouseDownInfo.mLastMouseDownContent,
                              mLastRightMouseDownInfo.mLastMouseDownContent,
                              mActiveContent, mHoverContent, mURLTargetContent,
                              mPopoverPointerDownTarget, mMouseEnterLeaveHelper,
                              mPointersEnterLeaveHelper, mDocument,
                              mIMEContentObserver, mAccessKeys)

void EventStateManager::ReleaseCurrentIMEContentObserver() {
  if (mIMEContentObserver) {
    mIMEContentObserver->DisconnectFromEventStateManager();
  }
  mIMEContentObserver = nullptr;
}

void EventStateManager::OnStartToObserveContent(
    IMEContentObserver* aIMEContentObserver) {
  if (mIMEContentObserver == aIMEContentObserver) {
    return;
  }
  ReleaseCurrentIMEContentObserver();
  mIMEContentObserver = aIMEContentObserver;
}

void EventStateManager::OnStopObservingContent(
    IMEContentObserver* aIMEContentObserver) {
  aIMEContentObserver->DisconnectFromEventStateManager();
  NS_ENSURE_TRUE_VOID(mIMEContentObserver == aIMEContentObserver);
  mIMEContentObserver = nullptr;
}

void EventStateManager::TryToFlushPendingNotificationsToIME() {
  if (mIMEContentObserver) {
    mIMEContentObserver->TryToFlushPendingNotifications(true);
  }
}

static bool IsMessageMouseUserActivity(EventMessage aMessage) {
  return aMessage == eMouseMove || aMessage == eMouseUp ||
         aMessage == eMouseDown || aMessage == ePointerAuxClick ||
         aMessage == eMouseDoubleClick || aMessage == ePointerClick ||
         aMessage == eMouseActivate || aMessage == eMouseLongTap;
}

static bool IsMessageGamepadUserActivity(EventMessage aMessage) {
  return aMessage == eGamepadButtonDown || aMessage == eGamepadButtonUp ||
         aMessage == eGamepadAxisMove;
}

// static
bool EventStateManager::IsKeyboardEventUserActivity(WidgetEvent* aEvent) {
  // We ignore things that shouldn't cause popups, but also things that look
  // like shortcut presses. In some obscure cases these may actually be
  // website input, but any meaningful website will have other input anyway,
  // and we can't very well tell whether shortcut input was supposed to be
  // directed at chrome or the document.

  WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
  // Access keys should be treated as page interaction.
  if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
    return true;
  }
  if (!keyEvent->CanTreatAsUserInput() || keyEvent->IsControl() ||
      keyEvent->IsMeta() || keyEvent->IsAlt()) {
    return false;
  }
  // Deal with function keys:
  switch (keyEvent->mKeyNameIndex) {
    case KEY_NAME_INDEX_F1:
    case KEY_NAME_INDEX_F2:
    case KEY_NAME_INDEX_F3:
    case KEY_NAME_INDEX_F4:
    case KEY_NAME_INDEX_F5:
    case KEY_NAME_INDEX_F6:
    case KEY_NAME_INDEX_F7:
    case KEY_NAME_INDEX_F8:
    case KEY_NAME_INDEX_F9:
    case KEY_NAME_INDEX_F10:
    case KEY_NAME_INDEX_F11:
    case KEY_NAME_INDEX_F12:
    case KEY_NAME_INDEX_F13:
    case KEY_NAME_INDEX_F14:
    case KEY_NAME_INDEX_F15:
    case KEY_NAME_INDEX_F16:
    case KEY_NAME_INDEX_F17:
    case KEY_NAME_INDEX_F18:
    case KEY_NAME_INDEX_F19:
    case KEY_NAME_INDEX_F20:
    case KEY_NAME_INDEX_F21:
    case KEY_NAME_INDEX_F22:
    case KEY_NAME_INDEX_F23:
    case KEY_NAME_INDEX_F24:
      return false;
    default:
      return true;
  }
}

static void OnTypingInteractionEnded() {
  // We don't consider a single keystroke to be typing.
  if (gTypingInteractionKeyPresses > 1) {
    gTypingInteraction.mInteractionCount += gTypingInteractionKeyPresses;
    gTypingInteraction.mInteractionTimeInMilliseconds += static_cast<uint32_t>(
        std::ceil((gTypingEndTime - gTypingStartTime).ToMilliseconds()));
  }

  gTypingInteractionKeyPresses = 0;
  gTypingStartTime = TimeStamp();
  gTypingEndTime = TimeStamp();
}

static void HandleKeyUpInteraction(WidgetKeyboardEvent* aKeyEvent) {
  if (EventStateManager::IsKeyboardEventUserActivity(aKeyEvent)) {
    TimeStamp now = TimeStamp::Now();
    if (gTypingEndTime.IsNull()) {
      gTypingEndTime = now;
    }
    TimeDuration delay = now - gTypingEndTime;
    // Has it been too long since the last keystroke to be considered typing?
    if (gTypingInteractionKeyPresses > 0 &&
        delay >
            TimeDuration::FromMilliseconds(
                StaticPrefs::browser_places_interactions_typing_timeout_ms())) {
      OnTypingInteractionEnded();
    }
    gTypingInteractionKeyPresses++;
    if (gTypingStartTime.IsNull()) {
      gTypingStartTime = now;
    }
    gTypingEndTime = now;
  }
}

nsresult EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
                                           WidgetEvent* aEvent,
                                           nsIFrame* aTargetFrame,
                                           nsIContent* aTargetContent,
                                           nsEventStatus* aStatus,
                                           nsIContent* aOverrideClickTarget) {
  AUTO_PROFILER_LABEL("EventStateManager::PreHandleEvent", DOM);
  NS_ENSURE_ARG_POINTER(aStatus);
  NS_ENSURE_ARG(aPresContext);
  if (!aEvent) {
    NS_ERROR("aEvent is null. This should never happen.");
    return NS_ERROR_NULL_POINTER;
  }

  NS_WARNING_ASSERTION(
      !aTargetFrame || !aTargetFrame->GetContent() ||
          aTargetFrame->GetContent() == aTargetContent ||
          aTargetFrame->GetContent()->GetFlattenedTreeParent() ==
              aTargetContent ||
          aTargetFrame->IsGeneratedContentFrame(),
      "aTargetFrame should be related with aTargetContent");
#if DEBUG
  if (aTargetFrame && aTargetFrame->IsGeneratedContentFrame()) {
    nsCOMPtr<nsIContent> targetContent;
    aTargetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
    MOZ_ASSERT(aTargetContent == targetContent,
               "Unexpected target for generated content frame!");
  }
#endif

  mCurrentTarget = aTargetFrame;
  mCurrentTargetContent = nullptr;

  // Do not take account eMouseEnterIntoWidget/ExitFromWidget so that loading
  // a page when user is not active doesn't change the state to active.
  WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
  if (aEvent->IsTrusted() &&
      ((mouseEvent && mouseEvent->IsReal() &&
        IsMessageMouseUserActivity(mouseEvent->mMessage)) ||
       aEvent->mClass == eWheelEventClass ||
       aEvent->mClass == ePointerEventClass ||
       aEvent->mClass == eTouchEventClass ||
       aEvent->mClass == eKeyboardEventClass ||
       (aEvent->mClass == eDragEventClass && aEvent->mMessage == eDrop) ||
       IsMessageGamepadUserActivity(aEvent->mMessage))) {
    if (gMouseOrKeyboardEventCounter == 0) {
      nsCOMPtr<nsIObserverService> obs =
          mozilla::services::GetObserverService();
      if (obs) {
        obs->NotifyObservers(nullptr, "user-interaction-active", nullptr);
        UpdateUserActivityTimer();
      }
    }
    ++gMouseOrKeyboardEventCounter;

    nsCOMPtr<nsINode> node = aTargetContent;
    if (node &&
        ((aEvent->mMessage == eKeyUp && IsKeyboardEventUserActivity(aEvent)) ||
         aEvent->mMessage == eMouseUp || aEvent->mMessage == eWheel ||
         aEvent->mMessage == eTouchEnd || aEvent->mMessage == ePointerUp ||
         aEvent->mMessage == eDrop)) {
      Document* doc = node->OwnerDoc();
      while (doc) {
        doc->SetUserHasInteracted();
        doc = nsContentUtils::IsChildOfSameType(doc)
                  ? doc->GetInProcessParentDocument()
                  : nullptr;
      }
    }
  }

  WheelTransaction::OnEvent(aEvent);

  // Focus events don't necessarily need a frame.
  if (!mCurrentTarget && !aTargetContent) {
    NS_ERROR("mCurrentTarget and aTargetContent are null");
    return NS_ERROR_NULL_POINTER;
  }
#ifdef DEBUG
  if (aEvent->HasDragEventMessage() && PointerLockManager::IsLocked()) {
    NS_ASSERTION(PointerLockManager::IsLocked(),
                 "Pointer is locked. Drag events should be suppressed when "
                 "the pointer is locked.");
  }
#endif
  // Store last known screenPoint and clientPoint so pointer lock
  // can use these values as constants.
  if (aEvent->IsTrusted() &&
      ((mouseEvent && mouseEvent->IsReal()) ||
       aEvent->mClass == eWheelEventClass) &&
      !PointerLockManager::IsLocked()) {
    // XXX Probably doesn't matter much, but storing these in CSS pixels instead
    // of device pixels means behavior can be a bit odd if you zoom while
    // pointer-locked.
    sLastScreenPoint = RoundedToInt(
        Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint)
            .extract());
    sLastClientPoint = RoundedToInt(Event::GetClientCoords(
        aPresContext, aEvent, aEvent->mRefPoint, CSSDoublePoint{0, 0}));
  }

  *aStatus = nsEventStatus_eIgnore;

  if (aEvent->mClass == eQueryContentEventClass) {
    HandleQueryContentEvent(aEvent->AsQueryContentEvent());
    return NS_OK;
  }

  WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
  if (touchEvent && mInTouchDrag) {
    if (touchEvent->mMessage == eTouchMove) {
      GenerateDragGesture(aPresContext, touchEvent);
    } else {
      mInTouchDrag = false;
      StopTrackingDragGesture(true);
    }
  }

  if (mMouseEnterLeaveHelper && aEvent->IsTrusted()) {
    // When the last `mouseover` event target is removed from the document,
    // we makes mMouseEnterLeaveHelper update the last deepest `mouseenter`
    // event target to the removed node parent and mark it as not the following
    // `mouseout` event target.  However, the other browsers may dispatch
    // `mouseout` on it if it's restored "immediately".  Therefore, we use
    // the next animation frame as the deadline.  ContentRemoved() enqueues a
    // synthesized `mousemove` to dispatch mouse boundary events under the
    // mouse cursor soon and the synthesized event (or eMouseExitFromWidget if
    // our window is moved) will reach here at latest the next animation frame.
    // Therefore, we can use the event as the deadline.  If the removed last
    // `mouseover` target is reconnected before a synthesized mouse event or
    // a real mouse event, let's restore it as the following `mouseout` event
    // target.  Otherwise, e.g., a keyboard event, let's forget it.
    mMouseEnterLeaveHelper->TryToRestorePendingRemovedOverTarget(aEvent);
  }

  static constexpr auto const allowSynthesisForTests = []() -> bool {
    nsCOMPtr<nsIDragService> dragService =
        do_GetService("@mozilla.org/widget/dragservice;1");
    return dragService &&
           !dragService->GetNeverAllowSessionIsSynthesizedForTests();
  };

  switch (aEvent->mMessage) {
    case eContextMenu:
      if (PointerLockManager::IsLocked()) {
        return NS_ERROR_DOM_INVALID_STATE_ERR;
      }
      break;
    case eMouseTouchDrag:
      mInTouchDrag = true;
      BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
      break;
    case eMouseDown: {
      switch (mouseEvent->mButton) {
        case MouseButton::ePrimary:
          BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
          mLastLeftMouseDownInfo.mClickCount = mouseEvent->mClickCount;
          SetClickCount(mouseEvent, aStatus);
          sNormalLMouseEventInProcess = true;
          break;
        case MouseButton::eMiddle:
          mLastMiddleMouseDownInfo.mClickCount = mouseEvent->mClickCount;
          SetClickCount(mouseEvent, aStatus);
          break;
        case MouseButton::eSecondary:
          mLastRightMouseDownInfo.mClickCount = mouseEvent->mClickCount;
          SetClickCount(mouseEvent, aStatus);
          break;
      }
      if (!StaticPrefs::dom_popup_experimental()) {
        NotifyTargetUserActivation(aEvent, aTargetContent);
      }
      break;
    }
    case eMouseUp: {
      switch (mouseEvent->mButton) {
        case MouseButton::ePrimary:
          if (StaticPrefs::ui_click_hold_context_menus()) {
            KillClickHoldTimer();
          }
          mInTouchDrag = false;
          StopTrackingDragGesture(true);
          sNormalLMouseEventInProcess = false;
          // then fall through...
          [[fallthrough]];
        case MouseButton::eSecondary:
        case MouseButton::eMiddle:
          RefPtr<EventStateManager> esm =
              ESMFromContentOrThis(aOverrideClickTarget);
          esm->SetClickCount(mouseEvent, aStatus, aOverrideClickTarget);
          break;
      }
      break;
    }
    case eMouseEnterIntoWidget:
      PointerEventHandler::UpdateActivePointerState(mouseEvent, aTargetContent);
      // In some cases on e10s eMouseEnterIntoWidget
      // event was sent twice into child process of content.
      // (From specific widget code (sending is not permanent) and
      // from ESM::DispatchMouseOrPointerBoundaryEvent (sending is permanent)).
      // IsCrossProcessForwardingStopped() helps to suppress sending accidental
      // event from widget code.
      aEvent->StopCrossProcessForwarding();
      break;
    case eMouseExitFromWidget:
      // If this is a remote frame, we receive eMouseExitFromWidget from the
      // parent the mouse exits our content. Since the parent may update the
      // cursor while the mouse is outside our frame, and since PuppetWidget
      // caches the current cursor internally, re-entering our content (say from
      // over a window edge) wont update the cursor if the cached value and the
      // current cursor match. So when the mouse exits a remote frame, clear the
      // cached widget cursor so a proper update will occur when the mouse
      // re-enters.
      if (XRE_IsContentProcess()) {
        ClearCachedWidgetCursor(mCurrentTarget);
      }

      // IsCrossProcessForwardingStopped() helps to suppress double event
      // sending into process of content. For more information see comment
      // above, at eMouseEnterIntoWidget case.
      aEvent->StopCrossProcessForwarding();

      // If the event is not a top-level window or puppet widget exit, then it's
      // not really an exit --- we may have traversed widget boundaries but
      // we're still in our toplevel window or puppet widget.
      if (mouseEvent->mExitFrom.value() !=
              WidgetMouseEvent::ePlatformTopLevel &&
          mouseEvent->mExitFrom.value() != WidgetMouseEvent::ePuppet) {
        // Treat it as a synthetic move so we don't generate spurious
        // "exit" or "move" events.  Any necessary "out" or "over" events
        // will be generated by GenerateMouseEnterExit
        mouseEvent->mMessage = eMouseMove;
        mouseEvent->mReason = WidgetMouseEvent::eSynthesized;
        // then fall through...
      } else {
        MOZ_ASSERT_IF(XRE_IsParentProcess(),
                      mouseEvent->mExitFrom.value() ==
                          WidgetMouseEvent::ePlatformTopLevel);
        MOZ_ASSERT_IF(XRE_IsContentProcess(), mouseEvent->mExitFrom.value() ==
                                                  WidgetMouseEvent::ePuppet);
        // We should synthetize corresponding pointer events
        GeneratePointerEnterExit(ePointerLeave, mouseEvent);
        GenerateMouseEnterExit(mouseEvent);
        // This is really an exit and should stop here
        aEvent->mMessage = eVoidEvent;
        break;
      }
      [[fallthrough]];
    case eMouseMove:
    case ePointerDown:
      if (aEvent->mMessage == ePointerDown) {
        PointerEventHandler::UpdateActivePointerState(mouseEvent,
                                                      aTargetContent);
        PointerEventHandler::ImplicitlyCapturePointer(aTargetFrame, aEvent);
        if (StaticPrefs::dom_popup_experimental()) {
          // https://html.spec.whatwg.org/multipage/interaction.html#activation-triggering-input-event
          if (mouseEvent->mInputSource ==
              MouseEvent_Binding::MOZ_SOURCE_MOUSE) {
            NotifyTargetUserActivation(aEvent, aTargetContent);
          }
        } else if (mouseEvent->mInputSource !=
                   MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
          NotifyTargetUserActivation(aEvent, aTargetContent);
        }

        LightDismissOpenPopovers(aEvent, aTargetContent);
      }
      [[fallthrough]];
    case ePointerMove: {
      if (!mInTouchDrag &&
          PointerEventHandler::IsDragAndDropEnabled(*mouseEvent)) {
        GenerateDragGesture(aPresContext, mouseEvent);
      }
      // on the Mac, GenerateDragGesture() may not return until the drag
      // has completed and so |aTargetFrame| may have been deleted (moving
      // a bookmark, for example).  If this is the case, however, we know
      // that ClearFrameRefs() has been called and it cleared out
      // |mCurrentTarget|. As a result, we should pass |mCurrentTarget|
      // into UpdateCursor().
      UpdateCursor(aPresContext, mouseEvent, mCurrentTarget, aStatus);

      UpdateLastRefPointOfMouseEvent(mouseEvent);
      if (PointerLockManager::IsLocked()) {
        ResetPointerToWindowCenterWhilePointerLocked(mouseEvent);
      }
      UpdateLastPointerPosition(mouseEvent);

      GenerateMouseEnterExit(mouseEvent);
      // Flush pending layout changes, so that later mouse move events
      // will go to the right nodes.
      FlushLayout(aPresContext);
      break;
    }
    case ePointerUp:
      LightDismissOpenPopovers(aEvent, aTargetContent);
      GenerateMouseEnterExit(mouseEvent);
      if (StaticPrefs::dom_popup_experimental() &&
          mouseEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_MOUSE) {
        NotifyTargetUserActivation(aEvent, aTargetContent);
      }
      break;
    case ePointerGotCapture:
      GenerateMouseEnterExit(mouseEvent);
      break;
    case eDragStart:
      if (StaticPrefs::ui_click_hold_context_menus()) {
        // an external drag gesture event came in, not generated internally
        // by Gecko. Make sure we get rid of the click-hold timer.
        KillClickHoldTimer();
      }
      break;
    case eDragOver: {
      WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
      MOZ_ASSERT(dragEvent);
      if (dragEvent->mFlags.mIsSynthesizedForTests &&
          allowSynthesisForTests()) {
        dragEvent->InitDropEffectForTests();
      }
      // Send the enter/exit events before eDrop.
      GenerateDragDropEnterExit(aPresContext, dragEvent);
      break;
    }
    case eDrop: {
      if (aEvent->mFlags.mIsSynthesizedForTests && allowSynthesisForTests()) {
        MOZ_ASSERT(aEvent->AsDragEvent());
        aEvent->AsDragEvent()->InitDropEffectForTests();
      }
      break;
    }
    case eKeyPress: {
      WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
      if ((keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eChrome) ||
           keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) &&
          // If the key binding of this event is a native key binding, we
          // prioritize it.
          !HasNativeKeyBindings(aTargetContent, keyEvent)) {
        // If the eKeyPress event will be sent to a remote process, this
        // process needs to wait reply from the remote process for checking if
        // preceding eKeyDown event is consumed.  If preceding eKeyDown event
        // is consumed in the remote process, BrowserChild won't send the event
        // back to this process.  So, only when this process receives a reply
        // eKeyPress event in BrowserParent, we should handle accesskey in this
        // process.
        if (IsTopLevelRemoteTarget(GetFocusedElement())) {
          // However, if there is no accesskey target for the key combination,
          // we don't need to wait reply from the remote process.  Otherwise,
          // Mark the event as waiting reply from remote process and stop
          // propagation in this process.
          if (CheckIfEventMatchesAccessKey(keyEvent, aPresContext)) {
            keyEvent->StopPropagation();
            keyEvent->MarkAsWaitingReplyFromRemoteProcess();
          }
        }
        // If the event target is in this process, we can handle accesskey now
        // since if preceding eKeyDown event was consumed, eKeyPress event
        // won't be dispatched by widget.  So, coming eKeyPress event means
        // that the preceding eKeyDown event wasn't consumed in this case.
        else {
          AutoTArray<uint32_t, 10> accessCharCodes;
          keyEvent->GetAccessKeyCandidates(accessCharCodes);

          if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes)) {
            *aStatus = nsEventStatus_eConsumeNoDefault;
          }
        }
      }
    }
      // then fall through...
      [[fallthrough]];
    case eKeyDown:
      if (aEvent->mMessage == eKeyDown) {
        NotifyTargetUserActivation(aEvent, aTargetContent);
      }
      [[fallthrough]];
    case eKeyUp: {
      Element* element = GetFocusedElement();
      if (element) {
        mCurrentTargetContent = element;
      }

      // NOTE: Don't refer TextComposition::IsComposing() since UI Events
      //       defines that KeyboardEvent.isComposing is true when it's
      //       dispatched after compositionstart and compositionend.
      //       TextComposition::IsComposing() is false even before
      //       compositionend if there is no composing string.
      //       And also don't expose other document's composition state.
      //       A native IME context is typically shared by multiple documents.
      //       So, don't use GetTextCompositionFor(nsIWidget*) here.
      RefPtr<TextComposition> composition =
          IMEStateManager::GetTextCompositionFor(aPresContext);
      aEvent->AsKeyboardEvent()->mIsComposing = !!composition;

      // Widget may need to perform default action for specific keyboard
      // event if it's not consumed.  In this case, widget has already marked
      // the event as "waiting reply from remote process".  However, we need
      // to reset it if the target (focused content) isn't in a remote process
      // because PresShell needs to check if it's marked as so before
      // dispatching events into the DOM tree.
      if (aEvent->IsWaitingReplyFromRemoteProcess() &&
          !aEvent->PropagationStopped() && !IsTopLevelRemoteTarget(element)) {
        aEvent->ResetWaitingReplyFromRemoteProcessState();
      }
    } break;
    case eWheel:
    case eWheelOperationStart:
    case eWheelOperationEnd: {
      NS_ASSERTION(aEvent->IsTrusted(),
                   "Untrusted wheel event shouldn't be here");
      using DeltaModeCheckingState = WidgetWheelEvent::DeltaModeCheckingState;

      if (Element* element = GetFocusedElement()) {
        mCurrentTargetContent = element;
      }

      if (aEvent->mMessage != eWheel) {
        break;
      }

      WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
      WheelPrefs::GetInstance()->ApplyUserPrefsToDelta(wheelEvent);

      // If we won't dispatch a DOM event for this event, nothing to do anymore.
      if (!wheelEvent->IsAllowedToDispatchDOMEvent()) {
        break;
      }

      if (StaticPrefs::dom_event_wheel_deltaMode_lines_always_disabled()) {
        wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Unchecked;
      } else if (ShouldAlwaysUseLineDeltas()) {
        wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Checked;
      } else {
        wheelEvent->mDeltaModeCheckingState = DeltaModeCheckingState::Unknown;
      }

      // Init lineOrPageDelta values for line scroll events for some devices
      // on some platforms which might dispatch wheel events which don't
      // have lineOrPageDelta values.  And also, if delta values are
      // customized by prefs, this recomputes them.
      DeltaAccumulator::GetInstance()->InitLineOrPageDelta(aTargetFrame, this,
                                                           wheelEvent);
    } break;
    case eSetSelection: {
      RefPtr<Element> focuedElement = GetFocusedElement();
      IMEStateManager::HandleSelectionEvent(aPresContext, focuedElement,
                                            aEvent->AsSelectionEvent());
      break;
    }
    case eContentCommandCut:
    case eContentCommandCopy:
    case eContentCommandPaste:
    case eContentCommandDelete:
    case eContentCommandUndo:
    case eContentCommandRedo:
    case eContentCommandPasteTransferable:
    case eContentCommandLookUpDictionary:
      DoContentCommandEvent(aEvent->AsContentCommandEvent());
      break;
    case eContentCommandInsertText:
      DoContentCommandInsertTextEvent(aEvent->AsContentCommandEvent());
      break;
    case eContentCommandReplaceText:
      DoContentCommandReplaceTextEvent(aEvent->AsContentCommandEvent());
      break;
    case eContentCommandScroll:
      DoContentCommandScrollEvent(aEvent->AsContentCommandEvent());
      break;
    case eCompositionStart:
      if (aEvent->IsTrusted()) {
        // If the event is trusted event, set the selected text to data of
        // composition event.
        WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
        WidgetQueryContentEvent querySelectedTextEvent(
            true, eQuerySelectedText, compositionEvent->mWidget);
        HandleQueryContentEvent(&querySelectedTextEvent);
        if (querySelectedTextEvent.FoundSelection()) {
          compositionEvent->mData = querySelectedTextEvent.mReply->DataRef();
        }
        NS_ASSERTION(querySelectedTextEvent.Succeeded(),
                     "Failed to get selected text");
      }
      break;
    case eTouchStart:
      SetGestureDownPoint(aEvent->AsTouchEvent());
      break;
    case eTouchEnd:
      if (!StaticPrefs::dom_popup_experimental()) {
        NotifyTargetUserActivation(aEvent, aTargetContent);
      }
      break;
    default:
      break;
  }
  return NS_OK;
}

// Returns true if this event is likely an user activation for a link or
// a link-like button, where modifier keys are likely be used for controlling
// where the link is opened.
//
// The modifiers associated with the user activation is used for controlling
// where the `window.open` is opened into.
static bool CanReflectModifiersToUserActivation(WidgetInputEvent* aEvent) {
  if (StaticPrefs::dom_popup_experimental()) {
    MOZ_ASSERT(aEvent->mMessage == eKeyDown ||
               aEvent->mMessage == ePointerDown ||
               aEvent->mMessage == ePointerUp);
  } else {
    MOZ_ASSERT(aEvent->mMessage == eKeyDown || aEvent->mMessage == eMouseDown ||
               aEvent->mMessage == ePointerDown ||
               aEvent->mMessage == eTouchEnd);
  }

  WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
  if (keyEvent) {
    return keyEvent->CanReflectModifiersToUserActivation();
  }

  return true;
}

void EventStateManager::NotifyTargetUserActivation(WidgetEvent* aEvent,
                                                   nsIContent* aTargetContent) {
  if (!aEvent->IsTrusted()) {
    return;
  }

  WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
  if (mouseEvent && !mouseEvent->IsReal()) {
    return;
  }

  nsCOMPtr<nsINode> node = aTargetContent;
  if (!node) {
    return;
  }

  Document* doc = node->OwnerDoc();
  if (!doc) {
    return;
  }

  // Don't gesture activate for key events for keys which are likely
  // to be interaction with the browser, OS.
  WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
  if (keyEvent && !keyEvent->CanUserGestureActivateTarget()) {
    return;
  }

  // Touch gestures that end outside the drag target were touches that turned
  // into scroll/pan/swipe actions. We don't want to gesture activate on such
  // actions, we want to only gesture activate on touches that are taps.
  // That is, touches that end in roughly the same place that they started.
  if ((aEvent->mMessage == eTouchEnd ||
       (aEvent->mMessage == ePointerUp &&
        aEvent->AsPointerEvent()->mInputSource ==
            MouseEvent_Binding::MOZ_SOURCE_TOUCH)) &&
      IsEventOutsideDragThreshold(aEvent->AsInputEvent())) {
    return;
  }

  // Do not treat the click on scrollbar as a user interaction with the web
  // content.
  if (StaticPrefs::dom_user_activation_ignore_scrollbars() &&
      ((StaticPrefs::dom_popup_experimental() &&
        (aEvent->mMessage == ePointerDown || aEvent->mMessage == ePointerUp)) ||
       (!StaticPrefs::dom_popup_experimental() &&
        (aEvent->mMessage == eMouseDown ||
         aEvent->mMessage == ePointerDown))) &&
      aTargetContent->IsInNativeAnonymousSubtree()) {
    nsIContent* current = aTargetContent;
    do {
      nsIContent* root = current->GetClosestNativeAnonymousSubtreeRoot();
      if (!root) {
        break;
      }
      if (root->IsXULElement(nsGkAtoms::scrollbar)) {
        return;
      }
      current = root->GetParent();
    } while (current);
  }

#ifdef DEBUG
  if (StaticPrefs::dom_popup_experimental()) {
    MOZ_ASSERT(aEvent->mMessage == eKeyDown ||
               aEvent->mMessage == ePointerDown ||
               aEvent->mMessage == ePointerUp);
  } else {
    MOZ_ASSERT(aEvent->mMessage == eKeyDown || aEvent->mMessage == eMouseDown ||
               aEvent->mMessage == ePointerDown ||
               aEvent->mMessage == eTouchEnd);
  }
#endif

  UserActivation::Modifiers modifiers;
  if (WidgetInputEvent* inputEvent = aEvent->AsInputEvent()) {
    if (CanReflectModifiersToUserActivation(inputEvent)) {
      if (inputEvent->IsShift()) {
        modifiers.SetShift();
      }
      if (inputEvent->IsMeta()) {
        modifiers.SetMeta();
      }
      if (inputEvent->IsControl()) {
        modifiers.SetControl();
      }
      if (inputEvent->IsAlt()) {
        modifiers.SetAlt();
      }

      WidgetMouseEvent* mouseEvent = inputEvent->AsMouseEvent();
      if (mouseEvent) {
        if (mouseEvent->mButton == MouseButton::eMiddle) {
          modifiers.SetMiddleMouse();
        }
      }
    }
  }
  doc->NotifyUserGestureActivation(modifiers);
}

// https://html.spec.whatwg.org/multipage/popover.html#popover-light-dismiss
void EventStateManager::LightDismissOpenPopovers(WidgetEvent* aEvent,
                                                 nsIContent* aTargetContent) {
  MOZ_ASSERT(aEvent->mMessage == ePointerDown || aEvent->mMessage == ePointerUp,
             "Light dismiss must be called for pointer up/down only");

  if (!aEvent->IsTrusted() || !aTargetContent) {
    return;
  }

  Element* topmostPopover = aTargetContent->OwnerDoc()->GetTopmostAutoPopover();
  if (!topmostPopover) {
    return;
  }

  // Pointerdown: set document's popover pointerdown target to the result of
  // running topmost clicked popover given target.
  if (aEvent->mMessage == ePointerDown) {
    mPopoverPointerDownTarget = aTargetContent->GetTopmostClickedPopover();
    return;
  }

  // Pointerup: hide open popovers.
  RefPtr<nsINode> ancestor = aTargetContent->GetTopmostClickedPopover();
  bool sameTarget = mPopoverPointerDownTarget == ancestor;
  mPopoverPointerDownTarget = nullptr;
  if (!sameTarget) {
    return;
  }

  if (!ancestor) {
    ancestor = aTargetContent->OwnerDoc();
  }
  RefPtr<Document> doc(ancestor->OwnerDoc());
  doc->HideAllPopoversUntil(*ancestor, falsetrue);
}

already_AddRefed<EventStateManager> EventStateManager::ESMFromContentOrThis(
    nsIContent* aContent) {
  if (aContent) {
    PresShell* presShell = aContent->OwnerDoc()->GetPresShell();
    if (presShell) {
      nsPresContext* prescontext = presShell->GetPresContext();
      if (prescontext) {
        RefPtr<EventStateManager> esm = prescontext->EventStateManager();
        if (esm) {
          return esm.forget();
        }
      }
    }
  }

  RefPtr<EventStateManager> esm = this;
  return esm.forget();
}

EventStateManager::LastMouseDownInfo& EventStateManager::GetLastMouseDownInfo(
    int16_t aButton) {
  switch (aButton) {
    case MouseButton::ePrimary:
      return mLastLeftMouseDownInfo;
    case MouseButton::eMiddle:
      return mLastMiddleMouseDownInfo;
    case MouseButton::eSecondary:
      return mLastRightMouseDownInfo;
    default:
      MOZ_ASSERT_UNREACHABLE("This button shouldn't use this method");
      return mLastLeftMouseDownInfo;
  }
}

void EventStateManager::HandleQueryContentEvent(
    WidgetQueryContentEvent* aEvent) {
  switch (aEvent->mMessage) {
    case eQuerySelectedText:
    case eQueryTextContent:
    case eQueryCaretRect:
    case eQueryTextRect:
    case eQueryEditorRect:
      if (!IsTargetCrossProcess(aEvent)) {
        break;
      }
      // Will not be handled locally, remote the event
      GetCrossProcessTarget()->HandleQueryContentEvent(*aEvent);
      return;
    // Following events have not been supported in e10s mode yet.
    case eQueryContentState:
    case eQuerySelectionAsTransferable:
    case eQueryCharacterAtPoint:
    case eQueryDOMWidgetHittest:
    case eQueryTextRectArray:
    case eQueryDropTargetHittest:
      break;
    default:
      return;
  }

  // If there is an IMEContentObserver, we need to handle QueryContentEvent
  // with it.
  // eQueryDropTargetHittest is not really an IME event, though
  if (mIMEContentObserver && aEvent->mMessage != eQueryDropTargetHittest) {
    RefPtr<IMEContentObserver> contentObserver = mIMEContentObserver;
    contentObserver->HandleQueryContentEvent(aEvent);
    return;
  }

  ContentEventHandler handler(mPresContext);
  handler.HandleQueryContentEvent(aEvent);
}

static AccessKeyType GetAccessKeyTypeFor(nsISupports* aDocShell) {
  nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell));
  if (!treeItem) {
    return AccessKeyType::eNone;
  }

  switch (treeItem->ItemType()) {
    case nsIDocShellTreeItem::typeChrome:
      return AccessKeyType::eChrome;
    case nsIDocShellTreeItem::typeContent:
      return AccessKeyType::eContent;
    default:
      return AccessKeyType::eNone;
  }
}

static bool IsAccessKeyTarget(Element* aElement, nsAString& aKey) {
  // Use GetAttr because we want Unicode case=insensitive matching
  // XXXbz shouldn't this be case-sensitive, per spec?
  nsString contentKey;
  if (!aElement || !aElement->GetAttr(nsGkAtoms::accesskey, contentKey) ||
      !contentKey.Equals(aKey, nsCaseInsensitiveStringComparator)) {
    return false;
  }

  if (!aElement->IsXULElement()) {
    return true;
  }

  // For XUL we do visibility checks.
  nsIFrame* frame = aElement->GetPrimaryFrame();
  if (!frame) {
    return false;
  }

  if (frame->IsFocusable()) {
    return true;
  }

  if (!frame->IsVisibleConsideringAncestors()) {
    return false;
  }

  // XUL controls can be activated.
  nsCOMPtr<nsIDOMXULControlElement> control = aElement->AsXULControl();
  if (control) {
    return true;
  }

  // XUL label elements are never focusable, so we need to check for them
  // explicitly before giving up.
  if (aElement->IsXULElement(nsGkAtoms::label)) {
    return true;
  }

  return false;
}

bool EventStateManager::CheckIfEventMatchesAccessKey(
    WidgetKeyboardEvent* aEvent, nsPresContext* aPresContext) {
  AutoTArray<uint32_t, 10> accessCharCodes;
  aEvent->GetAccessKeyCandidates(accessCharCodes);
  return WalkESMTreeToHandleAccessKey(aEvent, aPresContext, accessCharCodes,
                                      nullptr, eAccessKeyProcessingNormal,
                                      false);
}

bool EventStateManager::LookForAccessKeyAndExecute(
    nsTArray<uint32_t>& aAccessCharCodes, bool aIsTrustedEvent, bool aIsRepeat,
    bool aExecute) {
  int32_t count, start = -1;
  if (Element* focusedElement = GetFocusedElement()) {
    start = mAccessKeys.IndexOf(focusedElement);
    if (start == -1 && focusedElement->IsInNativeAnonymousSubtree()) {
      start = mAccessKeys.IndexOf(Element::FromNodeOrNull(
          focusedElement->GetClosestNativeAnonymousSubtreeRootParentOrHost()));
    }
  }
  RefPtr<Element> element;
  int32_t length = mAccessKeys.Count();
  for (uint32_t i = 0; i < aAccessCharCodes.Length(); ++i) {
    uint32_t ch = aAccessCharCodes[i];
    nsAutoString accessKey;
    AppendUCS4ToUTF16(ch, accessKey);
    for (count = 1; count <= length; ++count) {
      // mAccessKeys always stores Element instances.
      MOZ_DIAGNOSTIC_ASSERT(length == mAccessKeys.Count());
      element = mAccessKeys[(start + count) % length];
      if (IsAccessKeyTarget(element, accessKey)) {
        if (!aExecute) {
          return true;
        }
        Document* doc = element->OwnerDoc();
        const bool shouldActivate = [&] {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=87 H=97 G=91

¤ Dauer der Verarbeitung: 0.21 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Ergonomie der
Schnittstellen

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.