United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: 6480024 Stack overflow on mouse wheel moved
6480024 : Stack overflow on mouse wheel moved

Details
Type:
Bug
Submit Date:
2006-10-10
Status:
Resolved
Updated Date:
2011-02-16
Project Name:
JDK
Resolved Date:
2007-08-22
Component:
client-libs
OS:
solaris_9,windows_xp
Sub-Component:
java.awt
CPU:
x86,sparc
Priority:
P2
Resolution:
Fixed
Affected Versions:
5.0,6
Fixed Versions:
7

Related Reports
Backport:
Relates:

Sub Tasks

Description
FULL PRODUCT VERSION :
java version "1.6.0-beta2"
Java(TM) SE Runtime Environment (build 1.6.0-beta2-b86)
Java HotSpot(TM) Client VM (build 1.6.0-beta2-b86, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Versione 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
If you add a MouseWheelListener to a JFrame, and a MouseListener (or MouseMoveListener) to any of its children, and then run the application and move the mouse wheel over the frame (it is not necessary to move it while over the specific child) the window freezes for a while and then fires a StackOverflowError.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. extend a class from JFrame
2. in the constructor, use addMouseWheelListener to add whatever listener you like (even an empty one)
3. create a control of any kind (a JPanel, for example) and add it to the frame using add
4. use the addMouseListener to the created control to add a listener of any kind (once more, it works even with an empty one)
5. run the application and move the mouse wheel over the frame

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The application handles events the right way, executing the mouseWheelMoved implementation of the frame listener when you move the mouse wheel.
ACTUAL -
The mouseWheelMoved implementation of the frame listener is never called, the application goes in an infinite loop and after a while a StackOverflowError is fired.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
	at java.awt.Container.getMouseEventTargetImpl(Unknown Source)
	at java.awt.Container.getMouseEventTarget(Unknown Source)
	at java.awt.Container.getMouseEventTarget(Unknown Source)
	at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Window.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchMouseWheelToAncestor(Unknown Source)
	at java.awt.Component.dispatchEventImpl(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchEvent(Unknown Source)
	at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Window.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchMouseWheelToAncestor(Unknown Source)
	at java.awt.Component.dispatchEventImpl(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchEvent(Unknown Source)
	at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Window.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchMouseWheelToAncestor(Unknown Source)
	at java.awt.Component.dispatchEventImpl(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchEvent(Unknown Source)
	at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Window.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchMouseWheelToAncestor(Unknown Source)
	at java.awt.Component.dispatchEventImpl(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchEvent(Unknown Source)
	at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Window.dispatchEventImpl(Unknown Source)
(the same sequence of calls again and again)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.event.*;

import javax.swing.*;
import javax.swing.event.*;

public class TestFrame extends JFrame
{
	public TestFrame()
	{
		initialize();
	}

	private void initialize()
	{
		this.setSize(200, 200);
		this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		this.addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent e)
			{
			}
		});
		
		JPanel outputBox = new JPanel();
		outputBox.addMouseListener(new MouseInputAdapter() {});
				
		getContentPane().setLayout(new BorderLayout());
		getContentPane().add(outputBox, BorderLayout.CENTER);
		
		this.show();
	}
	public static void main(String[] args) 
	{
		TestFrame tf = new TestFrame();
	}
}

---------- END SOURCE ----------

                                    

Comments
EVALUATION

Component.dispatchMouseWheelToAncestor(MouseWheelEvent e) does following check:
                 if (!(anc instanceof Window)) {
                      anc = anc.getParent();
                  }
                  else {
                     break;
                  }
But if even the current value of arc variable is not Window, the next would Window and we enclose the circle.

Instead the statement may check 
                 if (!(anc.getParent() instanceof Window)) {
                                     
2007-07-05
EVALUATION

This is the repeatable dump.
	at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4249)
	at java.awt.LightweightDispatcher.processMouseEvent(Container.java:3942)
	at java.awt.LightweightDispatcher.dispatchEvent(Container.java:3843)
	at java.awt.Container.dispatchEventImpl(Container.java:2100)
	at java.awt.Window.dispatchEventImpl(Window.java:2442)
	at java.awt.Component.dispatchMouseWheelToAncestor(Component.java:4546)
	at java.awt.Component.dispatchEventImpl(Component.java:4287)
	at java.awt.Container.dispatchEventImpl(Container.java:2114)
	at java.awt.Component.dispatchEvent(Component.java:4231)

Starting from the last entry, 
1) JButton is getting the event with dispatchEvent()
2) As the JButton(or any other LW component inside any Window) is the child of Container, then invoking dispatchEventImpl() addresses the 
Container.dispatchEvent()
3) Now we see 
      super.dispatchEventImpl(e);
  That means that we go back to the Component.

4) Now we come to 
        if (id == MouseEvent.MOUSE_WHEEL && 
            (!eventTypeEnabled(id)) &&
            (peer != null && !peer.handlesWheelScrolling()) &&
            (dispatchMouseWheelToAncestor((MouseWheelEvent)e)))
        {
            return;
        }

5) Looking on dispatchMouseWheelToAncestor((MouseWheelEvent)e)
there is a anc.dispatchEventImpl(newMWE); invocation. Where anc - is 
the nearest ancestor accepting mouse wheel events.

6) Here anc variable is the Window and it just invoke 
	super.dispatchEventImpl(e);


7) So we returned to the Container but now it has the LightweightDispatcher in it.
        if ((dispatcher != null) && dispatcher.dispatchEvent(e)) {

8) dispatchEvent() walks through the if-s and choose:
                ret = processMouseEvent(me);

9) That method finds the component under the mouse cursor 
     private boolean processMouseEvent(MouseEvent e) {
	int id = e.getID();
	Component mouseOver =	// sensitive to mouse events
            nativeContainer.getMouseEventTarget(e.getX(), e.getY(), 
                                                Container.INCLUDE_SELF);

10) And then 
        case MouseEvent.MOUSE_WHEEL:
            [skip]
            retargetMouseEvent(mouseOver, id, e);

Where mouseOver holds the JButton component. This encloses the loop - the same (actually cloned) event comes to the JButton again.

The event transfering machinery works from the top to bottom in the component hierarchy: events are transfered from the toplevel(window, frame, dialog) to all their children, including LW components. 
The idea of the fix is to introduce optionality to the 10-th step: if the component under the mouse cursor is unable to handle the event (enableEvent() for that event type returns false) or not interested in them we shouldn't post the event to that component.
Judging from the above we could introduce optionality to the 10-th step: if the component under the mouse cursor is unable to handle the event (enableEvent() for that event type returns false) or not interested in them we shouldn't post the event to that component. This obviously needs extensive testing.
                                     
2007-03-21
SUGGESTED FIX

http://sa.sfbay.sun.com/projects/awt_data/7/6480024/

+++ Component.java        2007-07-23 17:33:16.000000000 +0400
@@ -4560,11 +4560,16 @@
                                              e.isPopupTrigger(),
                                              e.getScrollType(),
                                              e.getScrollAmount(),
                                              e.getWheelRotation());
                 ((AWTEvent)e).copyPrivateDataInto(newMWE);
-                anc.dispatchEventImpl(newMWE);
+                // When dispatching a wheel event to 
+                // ancestor, there is no need trying to find descendant
+                // lightweights to dispatch event to. 
+                // If we dispatch the event to toplevel ancestor, 
+                // this could encolse the loop: 6480024.
+                anc.dispatchEventToSelf(newMWE);
             }
         }
         return true;
     }
                                     
2007-03-21
WORK AROUND

Add mouse wheel listener to the lightweight container:
		outputBox.addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent e){}
		});
                                     
2007-03-19
EVALUATION

There are two GUI instances to represent the issue: a JPanel and a Frame.
Once Panel get a WheelEvent then it tries to share it with it's ancestor. This machinery doesn't work very well on linux (see 4616935 for more info).
But if ancestor has LightweightDispatcher installed then it retarget the event back to Panel. This encloses the loop.
I'd say that issue should be addressed to LightweightDispatcher. It should decide if this event has already processed by some lightweight component inside assigned Container and consume the wheel event. Container.retargetMouseEvent() obtain Frame  as source and Panel as target.
                                     
2006-10-17
EVALUATION

Infinite loop is caused by LightweightDispatcher, Component.dispatchEventImpl and Component.dispatchMouseWheelToAncestor methods. We should probably dispatch mouse wheel events more carefully in LightweightDispatcher, or not dispatch them to ancestor component.

Adding MouseWheelListener to the component is significant as it leads to MouseWheelEvents to be enabled to the component. This check is then performed in Component.dispatchEventImpl and leads to the call to dispatchMouseWheelToAncestor.
                                     
2006-10-12



Hardware and Software, Engineered to Work Together