Parent OnPointerExit fires when child is deleted from under the pointer

They both have Raycast Target checked.

Here is a video of the mysterious behaviour:

First I open the context menu UI and move my pointer out to show the expected behavior. The menu closes when the pointer exits. Strangely the EventSystem says that the pointer is still over the context menu background image when OnPointerExit is called. (not in the video: I also checked the event data and can see the context menu image’s GameObject in the list of hovered GameObjects at that time as well) But whatever, it works.

I then show the code I am using on the same GameObject as the context menu’s background Image.

Next I mouse over a green image that is a descendant of the context menu’s GameObject and you can see that the context menu immediately closes. Now the EventSystem says that the pointer is NOT over the context menu background image when OnPointerExit is called.

Notice how I can move the pointer over other UI elements in the context menu without the menu closing (images, buttons, sliders) but if I move my pointer over that one green image then OnPointerExit is called on the context menu.

Then I pause play mode to examine the UI. I open up the items in the hierarchy. You can see two GameObjects in under the Bar Container GameObject. FilledBar is part of a prefab which is instantiated to populate the context menu. It has a white Image component with Raycast Target checked. Resource Row Promise Item(Clone) is also has an Image with Raycast Target checked but is green. It was created by the script that manages the Bar Container contents. Despite being nearly identical the white image does not cause the context menu to close but the green one does.

Does anyone have any ideas about the difference in behavior?

Found the issue. It is a problem with the BaseInputModule from the Unity UI package in a particular scenario. I have submitted a bug report (IN-75258) with a minimal repro project.

The issue happens when you set up a parent object and a child object which are both raycast targets. If you place the pointer over the child and destroy it the parent’s OnPointerExit() is called even though the pointer never actually exits the parent. This is because the code in BaseInputModule forcibly calls OnPointerExit in the entire stack of hovered objects and clears the stack if it detects that the previously hovered object was destroyed. The fix here is to replace that check with a ReferenceEquals() to null and continuing with the normal logic in the method. I provide a version of the Unity UI package with that fix in the repro project.

Generally this isn’t an issue if the operations being called in the hover stack’s OnPointerExit() methods are reversable since their OnPointerEnter() methods are called right after, even if that is slightly wasteful.

However, the current behavior is bad if the actions are not reversible. For example, if the parent is programmed to hide when the pointer exits. With the current logic the parent would hide itself and not become visible again if a child object was deleted from under the pointer.

For anyone looking at this later:

The bug report was verified and confirmed then closed as “Won’t Fix” to “focus on other high priority issues and delivering the new UI system”.

Looks like I’ll be maintaining a local fork. Sad that the LTS isn’t getting love. ;(