/* -*- 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/. */
/** * 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;
}
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());
}
// 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;
}
// 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.
}
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.
}
// 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.
// 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)) { returntrue;
} if (!keyEvent->CanTreatAsUserInput() || keyEvent->IsControl() ||
keyEvent->IsMeta() || keyEvent->IsAlt()) { returnfalse;
} // 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: returnfalse; default: returntrue;
}
}
staticvoid 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()));
}
staticvoid 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
// 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;
// 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;
}
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);
}
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);
}
} elseif (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;
}
// 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. staticbool 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();
}
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);
}
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;
}
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;
}
switch (treeItem->ItemType()) { case nsIDocShellTreeItem::typeChrome: return AccessKeyType::eChrome; case nsIDocShellTreeItem::typeContent: return AccessKeyType::eContent; default: return AccessKeyType::eNone;
}
}
staticbool 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)) { returnfalse;
}
if (!aElement->IsXULElement()) { returntrue;
}
// For XUL we do visibility checks.
nsIFrame* frame = aElement->GetPrimaryFrame(); if (!frame) { returnfalse;
}
if (frame->IsFocusable()) { returntrue;
}
if (!frame->IsVisibleConsideringAncestors()) { returnfalse;
}
// XUL controls can be activated.
nsCOMPtr<nsIDOMXULControlElement> control = aElement->AsXULControl(); if (control) { returntrue;
}
// XUL label elements are never focusable, so we need to check for them // explicitly before giving up. if (aElement->IsXULElement(nsGkAtoms::label)) { returntrue;
}
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.