Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/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/GleanMetrics.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 "nsIWidget.h"
#include "nsLiteralString.h"
#include "nsPresContext.h"
#include "nsTArray.h"
#include "nsGkAtoms.h"
#include "nsIFormControl.h"
#include "nsComboboxControlFrame.h"
#include "nsIScrollableFrame.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
static AutoWeakFrame gLastCursorSourceFrame;
static TimeStamp gLastCursorUpdateTime;
static TimeStamp gTypingStartTime;
static TimeStamp gTypingEndTime;
static int32_t gTypingInteractionKeyPresses = 0;
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;
}
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
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()));
mDeepestEnterEventTarget = 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",
mDeepestEnterEventTargetIsOverEventTarget ? "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",
mDeepestEnterEventTargetIsOverEventTarget ? "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)",
mDeepestEnterEventTargetIsOverEventTarget ? "over" : "enter",
mDeepestEnterEventTarget.get(),
aContent.GetFlattenedTreeParent()
? ToString(*aContent.GetFlattenedTreeParent()).c_str()
: "nullptr",
aContent.GetFlattenedTreeParent()));
mDeepestEnterEventTarget = aContent.GetFlattenedTreeParent();
mDeepestEnterEventTargetIsOverEventTarget = false;
}
void OverOutElementsWrapper::DidDispatchOverAndEnterEvent(
nsIContent* aOriginalOverTargetInComposedDoc) {
mDispatchingOverEventTarget = nullptr;
// 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) ||
(!mDeepestEnterEventTargetIsOverEventTarget && mDeepestEnterEventTarget &&
nsContentUtils::ContentIsFlattenedTreeDescendantOf(
aOriginalOverTargetInComposedDoc, mDeepestEnterEventTarget))) {
mDeepestEnterEventTarget = aOriginalOverTargetInComposedDoc;
mDeepestEnterEventTargetIsOverEventTarget = true;
LogModule* const logModule = mType == BoundaryEventType::Mouse
? sMouseBoundaryLog
: sPointerBoundaryLog;
MOZ_LOG(logModule, LogLevel::Info,
("The \"over\" event target (%p) is restored",
mDeepestEnterEventTarget.get()));
}
}
/******************************************************************/
/* 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;
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);
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),
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 == eMouseAuxClick ||
aMessage == eMouseDoubleClick || aMessage == eMouseClick ||
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) {
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 =
Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint)
.extract();
sLastClientPoint = Event::GetClientCoords(
aPresContext, aEvent, aEvent->mRefPoint, CSSIntPoint(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);
}
}
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;
}
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::DispatchMouseOrPointerEvent (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 (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);
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) {
dragEvent->InitDropEffectForTests();
}
// Send the enter/exit events before eDrop.
GenerateDragDropEnterExit(aPresContext, dragEvent);
break;
}
case eDrop:
if (aEvent->mFlags.mIsSynthesizedForTests) {
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 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 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:
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) {
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->AsTouchEvent() &&
IsEventOutsideDragThreshold(aEvent->AsTouchEvent())) {
return;
}
// Do not treat the click on scrollbar as a user interaction with the web
// content.
if (StaticPrefs::dom_user_activation_ignore_scrollbars() &&
(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);
}
MOZ_ASSERT(aEvent->mMessage == eKeyDown || aEvent->mMessage == eMouseDown ||
aEvent->mMessage == ePointerDown || aEvent->mMessage == eTouchEnd);
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();
}
}
}
doc->NotifyUserGestureActivation(modifiers);
}
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 (!StaticPrefs::dom_element_popover_enabled() || !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, false, true);
}
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:
break;
default:
return;
}
// If there is an IMEContentObserver, we need to handle QueryContentEvent
// with it.
if (mIMEContentObserver) {
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 = [&] {
if (!StaticPrefs::accessibility_accesskeycausesactivation()) {
return false;
}
if (aIsRepeat && nsContentUtils::IsChromeDoc(doc)) {
return false;
}
// XXXedgar, Bug 1700646, maybe we could use other data structure to
// make searching target with same accesskey easier, and current setup
// could not ensure we cycle the target with tree order.
int32_t j = 0;
while (++j < length) {
Element* el = mAccessKeys[(start + count + j) % length];
if (IsAccessKeyTarget(el, accessKey)) {
return false;
}
}
return true;
}();
// TODO(bug 1641171): This shouldn't be needed if we considered the
// accesskey combination properly.
if (aIsTrustedEvent) {
doc->NotifyUserGestureActivation();
}
auto result =
element->PerformAccesskey(shouldActivate, aIsTrustedEvent);
if (result.isOk()) {
if (result.unwrap() && aIsTrustedEvent) {
// If this is a child process, inform the parent that we want the
// focus, but pass false since we don't want to change the window
// order.
nsIDocShell* docShell = mPresContext->GetDocShell();
nsCOMPtr<nsIBrowserChild> child =
docShell ? docShell->GetBrowserChild() : nullptr;
if (child) {
child->SendRequestFocus(false, CallerType::System);
}
}
return true;
}
}
}
}
return false;
}
// static
void EventStateManager::GetAccessKeyLabelPrefix(Element* aElement,
nsAString& aPrefix) {
aPrefix.Truncate();
nsAutoString separator, modifierText;
nsContentUtils::GetModifierSeparatorText(separator);
AccessKeyType accessKeyType =
GetAccessKeyTypeFor(aElement->OwnerDoc()->GetDocShell());
if (accessKeyType == AccessKeyType::eNone) {
return;
}
Modifiers modifiers = WidgetKeyboardEvent::AccessKeyModifiers(accessKeyType);
if (modifiers == MODIFIER_NONE) {
return;
}
if (modifiers & MODIFIER_CONTROL) {
nsContentUtils::GetControlText(modifierText);
aPrefix.Append(modifierText + separator);
}
if (modifiers & MODIFIER_META) {
nsContentUtils::GetCommandOrWinText(modifierText);
aPrefix.Append(modifierText + separator);
}
if (modifiers & MODIFIER_ALT) {
nsContentUtils::GetAltText(modifierText);
aPrefix.Append(modifierText + separator);
}
if (modifiers & MODIFIER_SHIFT) {
nsContentUtils::GetShiftText(modifierText);
aPrefix.Append(modifierText + separator);
}
}
struct MOZ_STACK_CLASS AccessKeyInfo {
WidgetKeyboardEvent* event;
nsTArray<uint32_t>& charCodes;
AccessKeyInfo(WidgetKeyboardEvent* aEvent, nsTArray<uint32_t>& aCharCodes)
: event(aEvent), charCodes(aCharCodes) {}
};
bool EventStateManager::WalkESMTreeToHandleAccessKey(
WidgetKeyboardEvent* aEvent, nsPresContext* aPresContext,
nsTArray<uint32_t>& aAccessCharCodes, nsIDocShellTreeItem* aBubbledFrom,
ProcessingAccessKeyState aAccessKeyState, bool aExecute) {
EnsureDocument(mPresContext);
nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) {
return false;
}
AccessKeyType accessKeyType = GetAccessKeyTypeFor(docShell);
if (accessKeyType == AccessKeyType::eNone) {
return false;
}
// Alt or other accesskey modifier is down, we may need to do an accesskey.
if (mAccessKeys.Count() > 0 &&
aEvent->ModifiersMatchWithAccessKey(accessKeyType)) {
// Someone registered an accesskey. Find and activate it.
if (LookForAccessKeyAndExecute(aAccessCharCodes, aEvent->IsTrusted(),
aEvent->mIsRepeat, aExecute)) {
return true;
}
}
int32_t childCount;
docShell->GetInProcessChildCount(&childCount);
for (int32_t counter = 0; counter < childCount; counter++) {
// Not processing the child which bubbles up the handling
nsCOMPtr<nsIDocShellTreeItem> subShellItem;
docShell->GetInProcessChildAt(counter, getter_AddRefs(subShellItem));
if (aAccessKeyState == eAccessKeyProcessingUp &&
subShellItem == aBubbledFrom) {
continue;
}
nsCOMPtr<nsIDocShell> subDS = do_QueryInterface(subShellItem);
if (subDS && IsShellVisible(subDS)) {
// Guarantee subPresShell lifetime while we're handling access key
// since somebody may assume that it won't be deleted before the
// corresponding nsPresContext and EventStateManager.
RefPtr<PresShell> subPresShell = subDS->GetPresShell();
// Docshells need not have a presshell (eg. display:none
// iframes, docshells in transition between documents, etc).
if (!subPresShell) {
// Oh, well. Just move on to the next child
continue;
}
RefPtr<nsPresContext> subPresContext = subPresShell->GetPresContext();
RefPtr<EventStateManager> esm =
static_cast<EventStateManager*>(subPresContext->EventStateManager());
if (esm && esm->WalkESMTreeToHandleAccessKey(
aEvent, subPresContext, aAccessCharCodes, nullptr,
eAccessKeyProcessingDown, aExecute)) {
return true;
}
}
} // if end . checking all sub docshell ends here.
// bubble up the process to the parent docshell if necessary
if (eAccessKeyProcessingDown != aAccessKeyState) {
nsCOMPtr<nsIDocShellTreeItem> parentShellItem;
docShell->GetInProcessParent(getter_AddRefs(parentShellItem));
nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parentShellItem);
if (parentDS) {
// Guarantee parentPresShell lifetime while we're handling access key
// since somebody may assume that it won't be deleted before the
// corresponding nsPresContext and EventStateManager.
RefPtr<PresShell> parentPresShell = parentDS->GetPresShell();
NS_ASSERTION(parentPresShell,
"Our PresShell exists but the parent's does not?");
RefPtr<nsPresContext> parentPresContext =
parentPresShell->GetPresContext();
NS_ASSERTION(parentPresContext, "PresShell without PresContext");
RefPtr<EventStateManager> esm = static_cast<EventStateManager*>(
parentPresContext->EventStateManager());
if (esm && esm->WalkESMTreeToHandleAccessKey(
aEvent, parentPresContext, aAccessCharCodes, docShell,
eAccessKeyProcessingDown, aExecute)) {
return true;
}
}
} // if end. bubble up process
// If the content access key modifier is pressed, try remote children
if (aExecute &&
aEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent) &&
mDocument && mDocument->GetWindow()) {
// If the focus is currently on a node with a BrowserParent, the key event
// should've gotten forwarded to the child process and HandleAccessKey
// called from there.
if (BrowserParent::GetFrom(GetFocusedElement())) {
// If access key may be only in remote contents, this method won't handle
// access key synchronously. In this case, only reply event should reach
// here.
MOZ_ASSERT(aEvent->IsHandledInRemoteProcess() ||
!aEvent->IsWaitingReplyFromRemoteProcess());
}
// If focus is somewhere else, then we need to check the remote children.
// However, if the event has already been handled in a remote process,
// then, focus is moved from the remote process after posting the event.
// In such case, we shouldn't retry to handle access keys in remote
// processes.
else if (!aEvent->IsHandledInRemoteProcess()) {
AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes);
nsContentUtils::CallOnAllRemoteChildren(
mDocument->GetWindow(),
[&accessKeyInfo](BrowserParent* aBrowserParent) -> CallState {
// Only forward accesskeys for the active tab.
if (aBrowserParent->GetDocShellIsActive()) {
// Even if there is no target for the accesskey in this process,
// the event may match with a content accesskey. If so, the
// keyboard event should be handled with reply event for
// preventing double action. (e.g., Alt+Shift+F on Windows may
// focus a content in remote and open "File" menu.)
accessKeyInfo.event->StopPropagation();
accessKeyInfo.event->MarkAsWaitingReplyFromRemoteProcess();
aBrowserParent->HandleAccessKey(*accessKeyInfo.event,
accessKeyInfo.charCodes);
return CallState::Stop;
}
return CallState::Continue;
});
}
}
return false;
} // end of HandleAccessKey
static BrowserParent* GetBrowserParentAncestor(BrowserParent* aBrowserParent) {
MOZ_ASSERT(aBrowserParent);
BrowserBridgeParent* bbp = aBrowserParent->GetBrowserBridgeParent();
if (!bbp) {
return nullptr;
}
return bbp->Manager();
}
static void DispatchCrossProcessMouseExitEvents(WidgetMouseEvent* aMouseEvent,
BrowserParent* aRemoteTarget,
BrowserParent* aStopAncestor,
bool aIsReallyExit) {
MOZ_ASSERT(aMouseEvent);
MOZ_ASSERT(aRemoteTarget);
MOZ_ASSERT(aRemoteTarget != aStopAncestor);
MOZ_ASSERT_IF(aStopAncestor, nsContentUtils::GetCommonBrowserParentAncestor(
aRemoteTarget, aStopAncestor));
while (aRemoteTarget != aStopAncestor) {
UniquePtr<WidgetMouseEvent> mouseExitEvent =
CreateMouseOrPointerWidgetEvent(aMouseEvent, eMouseExitFromWidget,
aMouseEvent->mRelatedTarget);
mouseExitEvent->mExitFrom =
Some(aIsReallyExit ? WidgetMouseEvent::ePuppet
: WidgetMouseEvent::ePuppetParentToPuppetChild);
aRemoteTarget->SendRealMouseEvent(*mouseExitEvent);
aRemoteTarget = GetBrowserParentAncestor(aRemoteTarget);
}
}
void EventStateManager::DispatchCrossProcessEvent(WidgetEvent* aEvent,
BrowserParent* aRemoteTarget,
nsEventStatus* aStatus) {
MOZ_ASSERT(aEvent);
MOZ_ASSERT(aRemoteTarget);
MOZ_ASSERT(aStatus);