diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 79e8cc1c9..8d7d73a41 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -2396,6 +2396,56 @@ nsContentUtils::GetCommonAncestor(nsINode* aNode1, return parent; } +// static +nsINode* +nsContentUtils::GetCommonAncestorUnderInteractiveContent(nsINode* aNode1, + nsINode* aNode2) +{ + if (!aNode1 || !aNode2) { + return nullptr; + } + + if (aNode1 == aNode2) { + return aNode1; + } + + // Build the chain of parents + AutoTArray parents1; + do { + parents1.AppendElement(aNode1); + if (aNode1->IsElement() && + aNode1->AsElement()->IsInteractiveHTMLContent(true)) { + break; + } + aNode1 = aNode1->GetFlattenedTreeParentNode(); + } while (aNode1); + + AutoTArray parents2; + do { + parents2.AppendElement(aNode2); + if (aNode2->IsElement() && + aNode2->AsElement()->IsInteractiveHTMLContent(true)) { + break; + } + aNode2 = aNode2->GetFlattenedTreeParentNode(); + } while (aNode2); + + // 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; + } + parent = child1; + } + + return parent; +} + /* static */ bool nsContentUtils::PositionIsBefore(nsINode* aNode1, nsINode* aNode2) diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 8f8fb0133..7ee862ca0 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -342,6 +342,15 @@ public: static nsINode* GetCommonAncestor(nsINode* aNode1, nsINode* aNode2); + /** + * Returns the common ancestor under interactive content, if any. + * If neither one has interactive content as ancestor, common ancestor will be + * returned. If only one has interactive content as ancestor, null will be + * returned. If the nodes are the same, that node is returned. + */ + static nsINode* GetCommonAncestorUnderInteractiveContent(nsINode* aNode1, + nsINode* aNode2); + /** * Returns true if aNode1 is before aNode2 in the same connected * tree. diff --git a/dom/events/Event.cpp b/dom/events/Event.cpp index c4072baa2..9bedf20d0 100644 --- a/dom/events/Event.cpp +++ b/dom/events/Event.cpp @@ -205,6 +205,11 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Event) default: break; } + + if (WidgetMouseEvent* mouseEvent = tmp->mEvent->AsMouseEvent()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mClickTarget"); + cb.NoteXPCOMChild(mouseEvent->mClickTarget); + } } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExplicitOriginalTarget) diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index b31cbd11f..bed127b35 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -433,11 +433,8 @@ NS_IMPL_CYCLE_COLLECTION(EventStateManager, mGestureDownContent, mGestureDownFrameOwner, mLastLeftMouseDownContent, - mLastLeftMouseDownContentParent, mLastMiddleMouseDownContent, - mLastMiddleMouseDownContentParent, mLastRightMouseDownContent, - mLastRightMouseDownContentParent, mActiveContent, mHoverContent, mURLTargetContent, @@ -4540,16 +4537,13 @@ EventStateManager::SetClickCount(WidgetMouseEvent* aEvent, nsEventStatus* aStatus) { nsCOMPtr mouseContent; - nsIContent* mouseContentParent = nullptr; if (mCurrentTarget) { mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(mouseContent)); } - if (mouseContent) { - if (mouseContent->IsNodeOfType(nsINode::eTEXT)) { - mouseContent = mouseContent->GetParent(); - } - if (mouseContent && mouseContent->IsRootOfNativeAnonymousSubtree()) { - mouseContentParent = mouseContent->GetParent(); + if (mouseContent && mouseContent->IsNodeOfType(nsINode::eTEXT)) { + nsINode* parent = mouseContent->GetFlattenedTreeParentNode(); + if (parent && parent->IsContent()) { + mouseContent = parent->AsContent(); } } @@ -4557,54 +4551,51 @@ EventStateManager::SetClickCount(WidgetMouseEvent* aEvent, case WidgetMouseEvent::eLeftButton: if (aEvent->mMessage == eMouseDown) { mLastLeftMouseDownContent = mouseContent; - mLastLeftMouseDownContentParent = mouseContentParent; } else if (aEvent->mMessage == eMouseUp) { - if (mLastLeftMouseDownContent == mouseContent || - mLastLeftMouseDownContentParent == mouseContent || - mLastLeftMouseDownContent == mouseContentParent) { + aEvent->mClickTarget = + nsContentUtils::GetCommonAncestorUnderInteractiveContent( + mouseContent, mLastLeftMouseDownContent); + if (aEvent->mClickTarget) { aEvent->mClickCount = mLClickCount; mLClickCount = 0; } else { aEvent->mClickCount = 0; } mLastLeftMouseDownContent = nullptr; - mLastLeftMouseDownContentParent = nullptr; } break; case WidgetMouseEvent::eMiddleButton: if (aEvent->mMessage == eMouseDown) { mLastMiddleMouseDownContent = mouseContent; - mLastMiddleMouseDownContentParent = mouseContentParent; } else if (aEvent->mMessage == eMouseUp) { - if (mLastMiddleMouseDownContent == mouseContent || - mLastMiddleMouseDownContentParent == mouseContent || - mLastMiddleMouseDownContent == mouseContentParent) { + aEvent->mClickTarget = + nsContentUtils::GetCommonAncestorUnderInteractiveContent( + mouseContent, mLastMiddleMouseDownContent); + if (aEvent->mClickTarget) { aEvent->mClickCount = mMClickCount; mMClickCount = 0; } else { aEvent->mClickCount = 0; } mLastMiddleMouseDownContent = nullptr; - mLastMiddleMouseDownContentParent = nullptr; } break; case WidgetMouseEvent::eRightButton: if (aEvent->mMessage == eMouseDown) { mLastRightMouseDownContent = mouseContent; - mLastRightMouseDownContentParent = mouseContentParent; } else if (aEvent->mMessage == eMouseUp) { - if (mLastRightMouseDownContent == mouseContent || - mLastRightMouseDownContentParent == mouseContent || - mLastRightMouseDownContent == mouseContentParent) { + aEvent->mClickTarget = + nsContentUtils::GetCommonAncestorUnderInteractiveContent( + mouseContent, mLastRightMouseDownContent); + if (aEvent->mClickTarget) { aEvent->mClickCount = mRClickCount; mRClickCount = 0; } else { aEvent->mClickCount = 0; } mLastRightMouseDownContent = nullptr; - mLastRightMouseDownContentParent = nullptr; } break; } @@ -4626,7 +4617,7 @@ EventStateManager::EventCausesClickEvents(const WidgetMouseEvent& aMouseEvent) } // If mouse is still over same element, clickcount will be > 1. // If it has moved it will be zero, so no click. - if (!aMouseEvent.mClickCount) { + if (!aMouseEvent.mClickCount || !aMouseEvent.mClickTarget) { return false; } // Check that the window isn't disabled before firing a click @@ -4645,7 +4636,7 @@ EventStateManager::InitAndDispatchClickEvent(WidgetMouseEvent* aMouseUpEvent, { MOZ_ASSERT(aMouseUpEvent); MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent)); - MOZ_ASSERT(aMouseUpContent || aCurrentTarget || aOverrideClickTarget); + MOZ_ASSERT(aMouseUpContent || aCurrentTarget); WidgetMouseEvent event(aMouseUpEvent->IsTrusted(), aMessage, aMouseUpEvent->mWidget, WidgetMouseEvent::eReal); @@ -4660,6 +4651,10 @@ EventStateManager::InitAndDispatchClickEvent(WidgetMouseEvent* aMouseUpEvent, event.pointerId = aMouseUpEvent->pointerId; event.inputSource = aMouseUpEvent->inputSource; + if (!aMouseUpContent->IsInComposedDoc()) { + return NS_OK; + } + // Use local event status for each click event dispatching since it'll be // cleared by EventStateManager::PreHandleEvent(). Therefore, dispatching // an event means that previous event status will be ignored. @@ -4695,22 +4690,12 @@ EventStateManager::PostHandleMouseUp(WidgetMouseEvent* aMouseUpEvent, return NS_OK; } - nsCOMPtr mouseUpContent = GetEventTargetContent(aMouseUpEvent); - // Click events apply to *elements* not nodes. At this point the target - // content may have been reset to some non-element content, and so we need - // to walk up the closest ancestor element, just like we do in - // nsPresShell::HandlePositionedEvent. - while (mouseUpContent && !mouseUpContent->IsElement()) { - mouseUpContent = mouseUpContent->GetFlattenedTreeParent(); - } - - if (!mouseUpContent && !mCurrentTarget) { - return NS_OK; - } + nsCOMPtr clickTarget = do_QueryInterface(aMouseUpEvent->mClickTarget); + NS_ENSURE_STATE(clickTarget); // Fire click events if the event target is still available. nsresult rv = DispatchClickEvents(presShell, aMouseUpEvent, aStatus, - mouseUpContent); + clickTarget); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -4721,13 +4706,13 @@ nsresult EventStateManager::DispatchClickEvents(nsIPresShell* aPresShell, WidgetMouseEvent* aMouseUpEvent, nsEventStatus* aStatus, - nsIContent* aMouseUpContent) + nsIContent* aClickTarget) { MOZ_ASSERT(aPresShell); MOZ_ASSERT(aMouseUpEvent); MOZ_ASSERT(EventCausesClickEvents(*aMouseUpEvent)); MOZ_ASSERT(aStatus); - MOZ_ASSERT(aMouseUpContent || mCurrentTarget || aOverrideClickTarget); + MOZ_ASSERT(aClickTarget); bool notDispatchToContents = (aMouseUpEvent->button == WidgetMouseEvent::eMiddleButton || @@ -4735,12 +4720,10 @@ EventStateManager::DispatchClickEvents(nsIPresShell* aPresShell, bool fireAuxClick = notDispatchToContents; - - // HandleEvent clears out mCurrentTarget which we might need again - nsWeakFrame currentTarget = mCurrentTarget; + nsWeakFrame currentTarget = aClickTarget->GetPrimaryFrame(); nsresult ret = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseClick, - aPresShell, aMouseUpContent, currentTarget, + aPresShell, aClickTarget, currentTarget, notDispatchToContents); if (NS_WARN_IF(NS_FAILED(ret))) { return ret; @@ -4748,21 +4731,20 @@ EventStateManager::DispatchClickEvents(nsIPresShell* aPresShell, // Fire double click event if click count is 2. if (aMouseUpEvent->mClickCount == 2 && - aMouseUpContent && aMouseUpContent->IsInComposedDoc()) { + aClickTarget && aClickTarget->IsInComposedDoc()) { ret = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseDoubleClick, - aPresShell, aMouseUpContent, currentTarget, + aPresShell, aClickTarget, currentTarget, notDispatchToContents); if (NS_WARN_IF(NS_FAILED(ret))) { return ret; - - } - } + } + } // Fire auxclick even if necessary. if (fireAuxClick && - aMouseUpContent && aMouseUpContent->IsInComposedDoc()) { + aClickTarget && aClickTarget->IsInComposedDoc()) { ret = InitAndDispatchClickEvent(aMouseUpEvent, aStatus, eMouseAuxClick, - aPresShell, aMouseUpContent, currentTarget, + aPresShell, aClickTarget, currentTarget, false); NS_WARNING_ASSERTION(NS_SUCCEEDED(ret), "Failed to dispatch eMouseAuxClick"); } diff --git a/dom/events/EventStateManager.h b/dom/events/EventStateManager.h index 89eb37935..0190ee109 100644 --- a/dom/events/EventStateManager.h +++ b/dom/events/EventStateManager.h @@ -1015,11 +1015,8 @@ private: uint16_t mGestureDownButtons; nsCOMPtr mLastLeftMouseDownContent; - nsCOMPtr mLastLeftMouseDownContentParent; nsCOMPtr mLastMiddleMouseDownContent; - nsCOMPtr mLastMiddleMouseDownContentParent; nsCOMPtr mLastRightMouseDownContent; - nsCOMPtr mLastRightMouseDownContentParent; nsCOMPtr mActiveContent; nsCOMPtr mHoverContent; diff --git a/widget/MouseEvents.h b/widget/MouseEvents.h index 57bdd2bbf..bd579cfb0 100644 --- a/widget/MouseEvents.h +++ b/widget/MouseEvents.h @@ -287,6 +287,10 @@ public: return result; } + // If during mouseup handling we detect that click event might need to be + // dispatched, this is setup to be the target of the click event. + nsCOMPtr mClickTarget; + // mReason indicates the reason why the event is fired: // - Representing mouse operation. // - Synthesized for emulating mousemove event when the content under the