Bug 1905267 - part 2: Make PresShell not use non-element event target when target frame is destroyed and the event wants Element target r=smaug

When event target frame is destroyed, `PresShell::NotifyDestroyingFrame` updates
current event target content to the frame content.  However, event target of
some DOM events need to be `Element`.  Therefore,
`PresShell::mCurrentEventTarget` needs to store at least the handling
`EventMessage` to consider whether the event target can be non-element content
node or only element node.  So, we need to make `PresShell::EventTargetInfo`
store `EventMessage`.  Then, we can make `PresShell::NotifyDestroyingFrame`
prefer inclusive ancestor element as new event target from the `EventMessage`.

Although I don't understand what's going on the reported case, but I found the
simple case when event target is changed to a `Text` with the assertion in
`PresShell::DispatchEventToDOM`.  Therefore, this patch has a new test.

Note that if target frame is a frame for element, e.g., when event target is
a focused element, `IsForbiddenDispatchingToNonElementContent` result won't
change the behavior.  Therefore, its implementation is not optimized for
non-user input events.

Differential Revision: https://phabricator.services.mozilla.com/D217205
This commit is contained in:
Masayuki Nakano
2024-07-30 00:06:10 +00:00
parent 66321dce9c
commit 16398887a4
8 changed files with 268 additions and 40 deletions

View File

@@ -39,6 +39,7 @@
#include "mozilla/dom/DOMIntersectionObserver.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/FontFaceSet.h"
#include "mozilla/dom/FragmentDirective.h"
#include "mozilla/dom/HTMLAreaElement.h"
@@ -2148,7 +2149,7 @@ void PresShell::NativeAnonymousContentRemoved(nsIContent* aAnonContent) {
if (nsIContent* root =
GetNativeAnonymousSubtreeRoot(mCurrentEventTarget.mContent)) {
if (aAnonContent == root) {
mCurrentEventTarget.SetFrameAndContent(
mCurrentEventTarget.UpdateFrameAndContent(
nullptr, aAnonContent->GetFlattenedTreeParent());
}
}
@@ -2156,7 +2157,7 @@ void PresShell::NativeAnonymousContentRemoved(nsIContent* aAnonContent) {
for (EventTargetInfo& eventTargetInfo : mCurrentEventTargetStack) {
nsIContent* anon = GetNativeAnonymousSubtreeRoot(eventTargetInfo.mContent);
if (aAnonContent == anon) {
eventTargetInfo.SetFrameAndContent(
eventTargetInfo.UpdateFrameAndContent(
nullptr, aAnonContent->GetFlattenedTreeParent());
}
}
@@ -2189,15 +2190,29 @@ void PresShell::NotifyDestroyingFrame(nsIFrame* aFrame) {
// Remove frame properties
aFrame->RemoveAllProperties();
const auto ComputeTargetContent =
[&aFrame](const EventTargetInfo& aEventTargetInfo) -> nsIContent* {
if (!IsForbiddenDispatchingToNonElementContent(
aEventTargetInfo.mEventMessage)) {
return aFrame->GetContent();
}
return aFrame->GetContent()
? aFrame->GetContent()
->GetInclusiveFlattenedTreeAncestorElement()
: nullptr;
};
if (aFrame == mCurrentEventTarget.mFrame) {
mCurrentEventTarget.SetFrameAndContent(nullptr, aFrame->GetContent());
mCurrentEventTarget.UpdateFrameAndContent(
nullptr, ComputeTargetContent(mCurrentEventTarget));
}
for (EventTargetInfo& eventTargetInfo : mCurrentEventTargetStack) {
if (aFrame == eventTargetInfo.mFrame) {
// One of our stack frames was deleted. Get its content so that when we
// pop it we can still get its new frame from its content
eventTargetInfo.SetFrameAndContent(nullptr, aFrame->GetContent());
eventTargetInfo.UpdateFrameAndContent(
nullptr, ComputeTargetContent(eventTargetInfo));
}
}
@@ -7297,7 +7312,8 @@ nsresult PresShell::EventHandler::HandleEventUsingCoordinates(
// must have been captured by us or some ancestor shell and we
// now ask the subshell to dispatch it normally.
EventHandler eventHandler(*eventTargetData.mPresShell);
AutoCurrentEventInfoSetter eventInfoSetter(eventHandler, eventTargetData);
AutoCurrentEventInfoSetter eventInfoSetter(eventHandler, aGUIEvent->mMessage,
eventTargetData);
// eventTargetData is on the stack and is guaranteed to keep its
// mOverrideClickTarget alive, so we can just use MOZ_KnownLive here.
nsresult rv = eventHandler.HandleEventWithCurrentEventInfo(
@@ -8218,8 +8234,8 @@ nsresult PresShell::EventHandler::HandleEventAtFocusedContent(
// If we cannot handle the event with mPresShell, let's try to handle it
// with parent PresShell.
mPresShell->mCurrentEventTarget.SetFrameAndContent(nullptr,
eventTargetElement);
mPresShell->mCurrentEventTarget.SetFrameAndContent(
aGUIEvent->mMessage, nullptr, eventTargetElement);
if (!mPresShell->GetCurrentEventContent() ||
!mPresShell->GetCurrentEventFrame() ||
InZombieDocument(mPresShell->mCurrentEventTarget.mContent)) {
@@ -8321,7 +8337,7 @@ nsresult PresShell::EventHandler::HandleEventWithFrameForPresShell(
MOZ_ASSERT(aEventStatus);
AutoCurrentEventInfoSetter eventInfoSetter(
*this, EventTargetInfo(aFrameForPresShell, nullptr));
*this, EventTargetInfo(aGUIEvent->mMessage, aFrameForPresShell, nullptr));
nsresult rv = NS_OK;
if (mPresShell->GetCurrentEventFrame()) {
@@ -8387,7 +8403,8 @@ nsresult PresShell::EventHandler::HandleEventWithTarget(
AutoPointerEventTargetUpdater updater(mPresShell, aEvent, aNewEventFrame,
aNewEventContent, aTargetContent);
AutoCurrentEventInfoSetter eventInfoSetter(
*this, EventTargetInfo(aNewEventFrame, aNewEventContent));
*this,
EventTargetInfo(aEvent->mMessage, aNewEventFrame, aNewEventContent));
nsresult rv = HandleEventWithCurrentEventInfo(aEvent, aEventStatus, false,
aOverrideClickTarget);
return rv;
@@ -8997,6 +9014,18 @@ nsresult PresShell::EventHandler::DispatchEventToDOM(
} else {
if (aEvent->IsMouseEventClassOrHasClickRelatedPointerEvent()) {
PresShell::sMouseButtons = aEvent->AsMouseEvent()->mButtons;
#ifdef DEBUG
if (eventTarget->IsContent() && !eventTarget->IsElement()) {
NS_WARNING(nsPrintfCString(
"%s (IsReal()=%s) target is not an elemnet content "
"node, %s\n",
ToChar(aEvent->mMessage),
aEvent->AsMouseEvent()->IsReal() ? "true" : "false",
ToString(*eventTarget).c_str())
.get());
MOZ_CRASH("MouseEvent target must be an element");
}
#endif // #ifdef DEBUG
}
RefPtr<nsPresContext> presContext = GetPresContext();
EventDispatcher::Dispatch(eventTarget, presContext, aEvent, nullptr,
@@ -9057,8 +9086,8 @@ void PresShell::EventHandler::DispatchTouchEventToDOM(
if (contentPresShell) {
// XXXsmaug huge hack. Pushing possibly capturing content,
// even though event target is something else.
contentPresShell->PushCurrentEventInfo(
EventTargetInfo(content->GetPrimaryFrame(), content));
contentPresShell->PushCurrentEventInfo(EventTargetInfo(
newEvent.mMessage, content->GetPrimaryFrame(), content));
}
}
@@ -9101,7 +9130,8 @@ nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
nsEventStatus* aStatus) {
nsresult rv = NS_OK;
PushCurrentEventInfo(EventTargetInfo(nullptr, aTargetContent));
PushCurrentEventInfo(
EventTargetInfo(aEvent->mMessage, nullptr, aTargetContent));
// Bug 41013: Check if the event should be dispatched to content.
// It's possible that we are in the middle of destroying the window
@@ -9125,7 +9155,8 @@ nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
nsEventStatus* aStatus) {
nsresult rv = NS_OK;
PushCurrentEventInfo(EventTargetInfo(nullptr, aTargetContent));
PushCurrentEventInfo(EventTargetInfo(aEvent->WidgetEventPtr()->mMessage,
nullptr, aTargetContent));
nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
if (container) {
rv = EventDispatcher::DispatchDOMEvent(aTargetContent, nullptr, aEvent,
@@ -9157,7 +9188,11 @@ bool PresShell::EventHandler::AdjustContextMenuKeyEvent(
widgetPoint;
mPresShell->mCurrentEventTarget.SetFrameAndContent(
itemFrame, itemFrame->GetContent());
aMouseEvent->mMessage, itemFrame,
itemFrame->GetContent()
? itemFrame->GetContent()
->GetInclusiveFlattenedTreeAncestorElement()
: nullptr);
return true;
}
@@ -9220,8 +9255,8 @@ bool PresShell::EventHandler::AdjustContextMenuKeyEvent(
currentFocus, getter_AddRefs(currentPointElement),
aMouseEvent->mRefPoint, MOZ_KnownLive(aMouseEvent->mWidget));
if (currentPointElement) {
mPresShell->mCurrentEventTarget.SetFrameAndContent(nullptr,
currentPointElement);
mPresShell->mCurrentEventTarget.SetFrameAndContent(
aMouseEvent->mMessage, nullptr, currentPointElement);
mPresShell->GetCurrentEventFrame();
}
}
@@ -11841,10 +11876,9 @@ nsIContent* PresShell::EventHandler::GetOverrideClickTarget(
}
nsIContent* overrideClickTarget = target->GetContent();
while (overrideClickTarget && !overrideClickTarget->IsElement()) {
overrideClickTarget = overrideClickTarget->GetFlattenedTreeParent();
}
return overrideClickTarget;
return overrideClickTarget
? overrideClickTarget->GetInclusiveFlattenedTreeAncestorElement()
: nullptr;
}
/******************************************************************************
@@ -11907,15 +11941,7 @@ void PresShell::EventHandler::EventTargetData::
return;
}
const Element* const closestInclusiveAncestorElement =
[&]() -> const Element* {
for (const nsIContent* const content :
mFrame->GetContent()->InclusiveFlatTreeAncestorsOfType<nsIContent>()) {
if (content->IsElement()) {
return content->AsElement();
}
}
return nullptr;
}();
mFrame->GetContent()->GetInclusiveFlattenedTreeAncestorElement();
if (closestInclusiveAncestorElement == mContent) {
return;
}
@@ -11999,11 +12025,7 @@ bool PresShell::EventHandler::EventTargetData::ComputeElementFromFrame(
//
// We use weak pointers because during this tight loop, the node
// will *not* go away. And this happens on every mousemove.
nsIContent* content = mContent;
while (content && !content->IsElement()) {
content = content->GetFlattenedTreeParent();
}
mContent = content;
mContent = mContent->GetInclusiveFlattenedTreeAncestorElement();
// If we found an element, target it. Otherwise, target *nothing*.
return !!mContent;