Reviewed by Darin.
authorap <ap@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 20 Mar 2006 20:46:24 +0000 (20:46 +0000)
committerap <ap@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 20 Mar 2006 20:46:24 +0000 (20:46 +0000)
        - http://bugzilla.opendarwin.org/show_bug.cgi?id=3439
         mouseover effects can get stuck sometimes due to missing events
        - http://bugzilla.opendarwin.org/show_bug.cgi?id=7701
         mouseout sent to the wrong element when layout changes simultaneously

        Implemented saving the previous node under the mouse, so that we don't need to
        recalculate it, which is slow and even not really possible. This has fixed a number
        of issues with mouse event dispatching when the content changes.
        The code still needs refactoring and cleanup, see bug 3439 for comments.

        Tests (both files perform multiple checks):
        - fast/events/mouseover-mouseout.html
        - fast/events/mouseover-mouseout2.html

        * page/Frame.h: Added a Frame parameter to passSubframeEventToSubframe(),
        used for mouseMoved events.
        * bridge/mac/FrameMac.h: Ditto.
        * bridge/mac/FrameMac.mm:
        (WebCore::FrameMac::passSubframeEventToSubframe): Use the passed subframe to target
        NSMouseMoved events.
        * page/FrameView.h: Added a prepareMouseEvent() helper that does viewportToContents translation.
        * page/FrameView.cpp: Added data members for storing the previous node and subframe
        under the mouse to FrameViewPrivate. Removed the now unused prevMouseX/prevMouseY.
        (WebCore::FrameViewPrivate::reset): Reset the new data members.
        (WebCore::subframeForEvent): A temporary place for the code that extracts a subframe
        pointer from MouseEventWithHitTestResults, moved from FrameMac::passSubframeEventToSubframe().
        (WebCore::FrameView::prepareMouseEvent): The new helper.
        (WebCore::FrameView::handleMousePressEvent): Use the new helper.
        (WebCore::FrameView::handleMouseDoubleClickEvent): Ditto.
        (WebCore::FrameView::handleMouseReleaseEvent): Ditto.
        (WebCore::FrameView::updateDragAndDrop): Ditto.
        (WebCore::FrameView::hoverTimerFired): Ditto.
        (WebCore::FrameView::dispatchMouseEvent): Store and use the oldUnder node, don't store
        or use prevMouseX/Y.
        (WebCore::FrameView::handleMouseMoveEvent): Rewrote dispatching events to subframes using
        a stored oldSubframe reference. Protect "this" from being removed while in this function.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@13402 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/events/mouseover-mouseout-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/mouseover-mouseout.html [new file with mode: 0644]
LayoutTests/fast/events/mouseover-mouseout2-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/mouseover-mouseout2.html [new file with mode: 0644]
WebCore/ChangeLog
WebCore/bridge/mac/FrameMac.h
WebCore/bridge/mac/FrameMac.mm
WebCore/page/Frame.h
WebCore/page/FrameView.cpp
WebCore/page/FrameView.h

index 66d57f2..4a287ca 100644 (file)
@@ -1,3 +1,19 @@
+2006-03-20  Alexey Proskuryakov  <ap@nypop.com>
+
+        Reviewed by Darin.
+
+        - http://bugzilla.opendarwin.org/show_bug.cgi?id=3439
+         mouseover effects can get stuck sometimes due to missing events
+        - http://bugzilla.opendarwin.org/show_bug.cgi?id=5764
+         DIV mouseOver and mouseOut triggered too often
+        - http://bugzilla.opendarwin.org/show_bug.cgi?id=7701
+         mouseout sent to the wrong element when layout changes simultaneously
+
+        * fast/events/mouseover-mouseout-expected.txt: Added.
+        * fast/events/mouseover-mouseout.html: Added.
+        * fast/events/mouseover-mouseout2-expected.txt: Added.
+        * fast/events/mouseover-mouseout2.html: Added.
+
 2006-03-20  Beth Dakin  <bdakin@apple.com>
 
         Reviewed by Adele
diff --git a/LayoutTests/fast/events/mouseover-mouseout-expected.txt b/LayoutTests/fast/events/mouseover-mouseout-expected.txt
new file mode 100644 (file)
index 0000000..71d0645
--- /dev/null
@@ -0,0 +1,61 @@
+EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 0 of P > BODY > HTML > #document to 0 of P > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+Tests for bugs 3439, 5764, 7701 - Mouse events vs. DOM manipulation.
+
+Move the mouse pointer from left to right:
+
+1              
+1              
+2
+3
+4
+5
+
+Log    Expected results
+mouseover on t1_1
+mouseout on t1_1
+mouseover on t1_2
+mouseout on t1_2
+mouseover on t2_1
+mouseout on t2_1
+mouseover on t2_2
+mouseout on t2_2
+mouseover on t3_1
+mouseover on t3_2
+mouseout on t3_2
+mouseover on t4_1
+mouseover on t4_2
+mouseout on t4_2
+mouseover on t5_1
+mouseout on t5_1
+mouseover on t5_2
+mouseout on t5_2
+mouseover on frame6
+mouseover on t6
+mouseout on t6
+mouseout on frame6
+mouseover on t1_1
+mouseout on t1_1
+mouseover on t1_2
+mouseout on t1_2
+mouseover on t2_1
+mouseout on t2_1
+mouseover on t2_2
+mouseout on t2_2
+mouseover on t3_1
+mouseover on t3_2
+mouseout on t3_2
+mouseover on t4_1
+mouseover on t4_2
+mouseout on t4_2
+mouseover on t5_1
+mouseout on t5_1
+mouseover on t5_2
+mouseout on t5_2
+mouseover on frame6
+mouseover on t6
+mouseout on t6
+mouseout on frame6
+
+SUCCESS
+
diff --git a/LayoutTests/fast/events/mouseover-mouseout.html b/LayoutTests/fast/events/mouseover-mouseout.html
new file mode 100644 (file)
index 0000000..9ca8592
--- /dev/null
@@ -0,0 +1,179 @@
+<html>
+<body onload="autoTest()">
+<script>
+    function log(message)
+    {
+        var item = document.createElement("li");
+        item.appendChild(document.createTextNode(message));
+        document.getElementById('log').appendChild(item);
+    }
+    
+    function logMouseEvent(evt)
+    {
+        target = (evt.target) ? evt.target : evt.srcElement;
+        log(evt.type + " on " + target.id);
+    }
+    
+</script>
+<p>Tests for bugs 
+<a href="http://bugzilla.opendarwin.org/show_bug.cgi?id=3439">3439</a>, 
+<a href="http://bugzilla.opendarwin.org/show_bug.cgi?id=5764">5764</a>,
+<a href="http://bugzilla.opendarwin.org/show_bug.cgi?id=7701">7701</a> -
+Mouse events vs. DOM manipulation.</p>
+<p>Move the mouse pointer from left to right:</p>
+
+<!-- 1: Show an element under the mouse -->
+<div style='height: 50; width: 50; background:red;top:100;left:100; position:absolute;' id='t1_1' 
+    onMouseOver="logMouseEvent(event); document.getElementById('t1_2').style.display = 'block'"
+    onMouseOut="logMouseEvent(event);">1&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; </div>
+<div style='display: none; height: 50; width: 50; background:white;top:100;left:100; position:absolute;' id='t1_2' 
+    onMouseOver="logMouseEvent(event)" 
+    onMouseOut="logMouseEvent(event); document.getElementById('t1_2').style.backgroundColor = 'gray'">1&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; </div>
+
+<!-- 2: Hide an element under the mouse -->
+<div style='height: 50; width: 50; background:white;top:100;left:150; position:absolute;' id='t2_2' 
+    onMouseOver="logMouseEvent(event)" 
+    onMouseOut="logMouseEvent(event); document.getElementById('t2_2').style.backgroundColor = 'gray'">2</div>
+<div style='height: 50; width: 50; background:orange;top:100;left:150; position:absolute;' id='t2_1' 
+    onMouseOver="logMouseEvent(event); document.getElementById('t2_1').style.display = 'none'" 
+    onMouseOut="logMouseEvent(event)">2</div>
+
+<!-- 3: Move the element under the mouse into another document -->
+<div style='height: 50; width: 50; background:white;top:100;left:200; position:absolute;' id='t3_2' 
+    onMouseOver="logMouseEvent(event)" 
+    onMouseOut="logMouseEvent(event); document.getElementById('t3_2').style.backgroundColor = 'gray'">3</div>
+<div style='height: 50; width: 50; background:yellow;top:100;left:200; position:absolute;' id='t3_1' 
+    onMouseOver="
+        logMouseEvent(event);
+        newDoc = document.getElementById('invisible_frame').document;
+        if (!newDoc)
+            newDoc = document.getElementById('invisible_frame').contentDocument;
+        try {
+            newDoc.adoptNode(document.getElementById('t3_1'));
+        } catch (e) {
+            newDoc.documentElement.appendChild(document.getElementById('t3_1'));
+        }
+        " 
+    onMouseOut="logMouseEvent(event)">3</div>
+
+<!-- 4: Move the element under the mouse into another document, and append it -->
+<div style='height: 50; width: 50; background:white;top:100;left:250; position:absolute;' id='t4_2' 
+    onMouseOver="logMouseEvent(event)" 
+    onMouseOut="logMouseEvent(event); document.getElementById('t4_2').style.backgroundColor = 'gray'">4</div>
+<div style='height: 50; width: 50; background:green;top:100;left:250; position:absolute;' id='t4_1' 
+    onMouseOver="
+        logMouseEvent(event);
+        newDoc = document.getElementById('invisible_frame').document;
+        if (!newDoc)
+            newDoc = document.getElementById('invisible_frame').contentDocument;
+        try {
+            newDoc.documentElement.appendChild(newDoc.adoptNode(document.getElementById('t4_1')));
+        } catch (ex) {
+            newDoc.documentElement.appendChild(document.getElementById('t4_1'));
+        }
+        " 
+    onMouseOut="logMouseEvent(event)">4</div>
+
+<!-- 5: Remove the element under the mouse -->
+<div style='height: 50; width: 50; background:white;top:100;left:300; position:absolute;' id='t5_2' 
+    onMouseOver="logMouseEvent(event)" 
+    onMouseOut="logMouseEvent(event); document.getElementById('t5_2').style.backgroundColor = 'gray'">5</div>
+<div style='height: 50; width: 50; background:blue;top:100;left:300; position:absolute;' id='t5_1' 
+    onMouseOver="
+        logMouseEvent(event);
+        document.body.removeChild(document.getElementById('t5_1'));
+        "
+    onMouseOut="logMouseEvent(event)">5</div>
+
+<!-- 6: Enter a different document -->
+<iframe style='height: 50; width: 50; top:100;left:350; position:absolute; border-width:0' src='data:text/html,
+    <body style="margin-left:0; margin-top:0">
+        <script>
+            function log(message)
+            {
+                var item = top.document.createElement("li");
+                item.appendChild(document.createTextNode(message));
+                top.document.getElementById("log").appendChild(item);
+            }
+            
+            function logMouseEvent(evt)
+            {
+                target = (evt.target) ? evt.target : evt.srcElement;
+                log(evt.type + " on " + target.id);
+            }
+            
+            function doOnMouseOver(evt)
+            {
+                logMouseEvent(evt);
+                document.getElementById("t6").style.backgroundColor = "white";
+            }
+
+            function doOnMouseOut(evt)
+            {
+                logMouseEvent(evt);
+                document.getElementById("t6").style.backgroundColor = "gray";
+            }
+            
+        </script>
+        <div style="height: 50; width: 50; background:violet; top:0;left:0; position:absolute;" id="t6" 
+            onMouseOver="doOnMouseOver(event);" 
+            onMouseOut="doOnMouseOut(event)">6</div>
+    </body>'
+        onMouseOver="logMouseEvent(event)"
+        onMouseOut="
+            logMouseEvent(event);
+            if (top.document.getElementById('log').innerHTML == top.document.getElementById('exp').innerHTML) {
+                top.document.getElementById('success').style.display = 'block';
+            }
+            "
+        id="frame6"
+    ></iframe>
+
+<iframe id=invisible_frame style="width:0; height:0; border-width:0"></iframe>
+
+<table border=1 width="100%" style='top:200; position:absolute'>
+<tr><td width="50%">Log</td><td>Expected results</td></tr>
+<tr>
+    <td id=log style="vertical-align:top"></td>
+    <td id=exp style="vertical-align:top"><li>mouseover on t1_1</li><li>mouseout on t1_1</li><li>mouseover on t1_2</li><li>mouseout on t1_2</li><li>mouseover on t2_1</li><li>mouseout on t2_1</li><li>mouseover on t2_2</li><li>mouseout on t2_2</li><li>mouseover on t3_1</li><li>mouseover on t3_2</li><li>mouseout on t3_2</li><li>mouseover on t4_1</li><li>mouseover on t4_2</li><li>mouseout on t4_2</li><li>mouseover on t5_1</li><li>mouseout on t5_1</li><li>mouseover on t5_2</li><li>mouseout on t5_2</li><li>mouseover on frame6</li><li>mouseover on t6</li><li>mouseout on t6</li><li>mouseout on frame6</li></td>
+</tr>
+</table>
+
+<div style='height: 50; width: 300; top:100;left:100; position:absolute; display:none' id='success'><br><center>SUCCESS</center></div>
+
+<script>
+function autoTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+    
+        eventSender.mouseMoveTo(1,1);
+        eventSender.mouseDown();
+        eventSender.mouseUp();
+
+        eventSender.mouseMoveTo(125, 125);
+        eventSender.mouseMoveTo(130, 125);
+        eventSender.mouseMoveTo(135, 125);
+
+        eventSender.mouseMoveTo(175, 125);
+        eventSender.mouseMoveTo(180, 125);
+
+        eventSender.mouseMoveTo(225, 125);
+        eventSender.mouseMoveTo(230, 125);
+
+        eventSender.mouseMoveTo(275, 125);
+        eventSender.mouseMoveTo(280, 125);
+
+        eventSender.mouseMoveTo(325, 125);
+        eventSender.mouseMoveTo(330, 125);
+
+        eventSender.mouseMoveTo(375, 125);
+        eventSender.mouseMoveTo(380, 125);
+        eventSender.mouseMoveTo(385, 125);
+
+        eventSender.mouseMoveTo(1, 1);
+    }
+}
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/fast/events/mouseover-mouseout2-expected.txt b/LayoutTests/fast/events/mouseover-mouseout2-expected.txt
new file mode 100644 (file)
index 0000000..f700bc7
--- /dev/null
@@ -0,0 +1,68 @@
+EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 0 of P > BODY > HTML > #document to 0 of P > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+Tests for bugs 3439, 5764, 7701 - Mouse events vs. DOM manipulation.
+
+Move the mouse pointer from left to right:
+
+
+3
+4
+
+5
+6
+Log    Expected results
+mouseover on frame1
+mouseover on t1
+mouseout on t1
+mouseout on frame1
+mouseover on frame2
+mouseover on t2
+mouseout on t2
+mouseout on frame2
+mouseover on frame3
+mouseover on t3_1
+mouseout on frame3
+mouseover on t3_2
+mouseout on t3_2
+mouseover on t4_2
+mouseout on t4_2
+mouseover on frame4
+mouseover on t4_1
+mouseout on t4_1
+mouseout on frame4
+mouseover on frame5
+mouseover on t5_1
+mouseout on frame5
+mouseover on t5_2
+mouseout on t5_2
+mouseover on t6
+mouseout on t6
+mouseover on frame1
+mouseover on t1
+mouseout on t1
+mouseout on frame1
+mouseover on frame2
+mouseover on t2
+mouseout on t2
+mouseout on frame2
+mouseover on frame3
+mouseover on t3_1
+mouseout on frame3
+mouseover on t3_2
+mouseout on t3_2
+mouseover on t4_2
+mouseout on t4_2
+mouseover on frame4
+mouseover on t4_1
+mouseout on t4_1
+mouseout on frame4
+mouseover on frame5
+mouseover on t5_1
+mouseout on frame5
+mouseover on t5_2
+mouseout on t5_2
+mouseover on t6
+mouseout on t6
+
+SUCCESS
+
diff --git a/LayoutTests/fast/events/mouseover-mouseout2.html b/LayoutTests/fast/events/mouseover-mouseout2.html
new file mode 100644 (file)
index 0000000..93ce3a7
--- /dev/null
@@ -0,0 +1,269 @@
+<html>
+<body onload="autoTest()">
+<script>
+    function log(message)
+    {
+        var item = document.createElement("li");
+        item.appendChild(document.createTextNode(message));
+        document.getElementById('log').appendChild(item);
+    }
+    
+    function logMouseEvent(evt)
+    {
+        target = (evt.target) ? evt.target : evt.srcElement;
+        log(evt.type + " on " + target.id);
+    }
+    
+</script>
+<p>Tests for bugs 
+<a href="http://bugzilla.opendarwin.org/show_bug.cgi?id=3439">3439</a>, 
+<a href="http://bugzilla.opendarwin.org/show_bug.cgi?id=5764">5764</a>,
+<a href="http://bugzilla.opendarwin.org/show_bug.cgi?id=7701">7701</a> -
+Mouse events vs. DOM manipulation.</p>
+<p>Move the mouse pointer from left to right:</p>
+
+<!-- 1: Enter a subframe -->
+<iframe style='height: 50; width: 50; top:100;left:100; position:absolute; border-width:0' src='data:text/html,
+    <body style="margin-left:0; margin-top:0">
+        <script>
+            function log(message)
+            {
+                var item = top.document.createElement("li");
+                item.appendChild(document.createTextNode(message));
+                top.document.getElementById("log").appendChild(item);
+            }
+            
+            function logMouseEvent(evt)
+            {
+                target = (evt.target) ? evt.target : evt.srcElement;
+                log(evt.type + " on " + target.id);
+            }
+            
+            function doOnMouseOver(evt)
+            {
+                logMouseEvent(evt);
+                document.getElementById("t1").style.backgroundColor = "white";
+            }
+
+            function doOnMouseOut(evt)
+            {
+                logMouseEvent(evt);
+                document.getElementById("t1").style.backgroundColor = "gray";
+            }
+            
+        </script>
+        <div style="height: 50; width: 50; background:red; top:0;left:0; position:absolute;" id="t1" 
+            onMouseOver="doOnMouseOver(event);" 
+            onMouseOut="doOnMouseOut(event)">1</div>
+    </body>'
+        onMouseOver="logMouseEvent(event)"
+        onMouseOut="logMouseEvent(event);"
+        id="frame1"
+    ></iframe>
+
+<!-- 2: Move from one subframe to another -->
+<iframe style='height: 50; width: 50; top:100;left:150; position:absolute; border-width:0' src='data:text/html,
+    <body style="margin-left:0; margin-top:0">
+        <script>
+            function log(message)
+            {
+                var item = top.document.createElement("li");
+                item.appendChild(document.createTextNode(message));
+                top.document.getElementById("log").appendChild(item);
+            }
+            
+            function logMouseEvent(evt)
+            {
+                target = (evt.target) ? evt.target : evt.srcElement;
+                log(evt.type + " on " + target.id);
+            }
+            
+            function doOnMouseOver(evt)
+            {
+                logMouseEvent(evt);
+                document.getElementById("t2").style.backgroundColor = "white";
+            }
+
+            function doOnMouseOut(evt)
+            {
+                logMouseEvent(evt);
+                document.getElementById("t2").style.backgroundColor = "gray";
+            }
+            
+        </script>
+        <div style="height: 50; width: 50; background:orange; top:0;left:0; position:absolute;" id="t2" 
+            onMouseOver="doOnMouseOver(event);" 
+            onMouseOut="doOnMouseOut(event)">2</div>
+    </body>'
+        onMouseOver="logMouseEvent(event)"
+        onMouseOut="logMouseEvent(event)"
+        id="frame2"
+    ></iframe>
+
+<!-- 3: Hide a subframe under the mouse -->
+<div style='height: 50; width: 50; background:white;top:100;left:200; position:absolute;' id='t3_2' 
+    onMouseOver="logMouseEvent(event)" 
+    onMouseOut="logMouseEvent(event); document.getElementById('t3_2').style.backgroundColor = 'gray'">3</div>
+<iframe style='height: 50; width: 50; top:100;left:200; position:absolute; border-width:0' src='data:text/html,
+    <body style="margin-left:0; margin-top:0">
+        <script>
+            function log(message)
+            {
+                var item = top.document.createElement("li");
+                item.appendChild(document.createTextNode(message));
+                top.document.getElementById("log").appendChild(item);
+            }
+            
+            function logMouseEvent(evt)
+            {
+                target = (evt.target) ? evt.target : evt.srcElement;
+                log(evt.type + " on " + target.id);
+            }
+            
+            function doOnMouseOver(evt)
+            {
+                logMouseEvent(evt);
+                top.document.getElementById("frame3").style.display = "none";
+            }
+            
+        </script>
+        <div style="height: 50; width: 50; background:yellow; top:0;left:0; position:absolute;" id="t3_1" 
+            onMouseOver="doOnMouseOver(event);" 
+            onMouseOut="logMouseEvent(event)">3</div>
+    </body>'
+        onMouseOver="logMouseEvent(event)"
+        onMouseOut="logMouseEvent(event)"
+        id="frame3"
+    ></iframe>
+
+<!-- 4: Show a subframe under the mouse -->
+<div style='height: 50; width: 50; background:green;top:100;left:250; position:absolute;' id='t4_2' 
+    onMouseOver="logMouseEvent(event); document.getElementById('frame4').style.visibility = 'visible'"
+    onMouseOut="logMouseEvent(event)">4</div>
+<iframe style='height: 50; width: 50; top:100;left:250; position:absolute; border-width:0; visibility:hidden' src='data:text/html,
+    <body style="margin-left:0; margin-top:0">
+        <script>
+            function log(message)
+            {
+                var item = top.document.createElement("li");
+                item.appendChild(document.createTextNode(message));
+                top.document.getElementById("log").appendChild(item);
+            }
+            
+            function logMouseEvent(evt)
+            {
+                target = (evt.target) ? evt.target : evt.srcElement;
+                log(evt.type + " on " + target.id);
+            }
+            
+            function doOnMouseOut(evt)
+            {
+                logMouseEvent(evt);
+                document.getElementById("t4_1").style.backgroundColor = "gray";
+            }
+            
+        </script>
+        <div style="height: 50; width: 50; background:white; top:0;left:0; position:absolute;" id="t4_1" 
+            onMouseOver="logMouseEvent(event);" 
+            onMouseOut="doOnMouseOut(event)">4</div>
+    </body>'
+        onMouseOver="logMouseEvent(event)"
+        onMouseOut="logMouseEvent(event)"
+        id="frame4"
+    ></iframe>
+
+<!-- 5: Remove the subframe under the mouse -->
+<div style='height: 50; width: 50; background:white;top:100;left:300; position:absolute;' id='t5_2' 
+    onMouseOver="logMouseEvent(event)" 
+    onMouseOut="logMouseEvent(event); document.getElementById('t5_2').style.backgroundColor = 'gray'">5</div>
+<iframe style='height: 50; width: 50; top:100;left:300; position:absolute; border-width:0' src='data:text/html,
+    <body style="margin-left:0; margin-top:0">
+        <script>
+            function log(message)
+            {
+                var item = top.document.createElement("li");
+                item.appendChild(document.createTextNode(message));
+                top.document.getElementById("log").appendChild(item);
+            }
+            
+            function logMouseEvent(evt)
+            {
+                target = (evt.target) ? evt.target : evt.srcElement;
+                log(evt.type + " on " + target.id);
+            }
+            
+            function doOnMouseOver(evt)
+            {
+                logMouseEvent(evt);
+                top.document.body.removeChild(top.document.getElementById("frame5"));
+            }
+
+        </script>
+        <div style="height: 50; width: 50; background:blue; top:0;left:0; position:absolute;" id="t5_1" 
+            onMouseOver="doOnMouseOver(event);" 
+            onMouseOut="logMouseEvent(event)">5</div>
+    </body>'
+        onMouseOver="logMouseEvent(event)"
+        onMouseOut="logMouseEvent(event)"
+        id="frame5"
+    ></iframe>
+
+<!-- 6: Nothing but a rainbow end -->
+<div style='height: 50; width: 50; background:violet;top:100;left:350; position:absolute;' id='t6' 
+    onMouseOver="logMouseEvent(event); document.getElementById('t6').style.backgroundColor = 'white'" 
+    onMouseOut="
+        logMouseEvent(event);
+        document.getElementById('t6').style.backgroundColor = 'gray';
+        if (top.document.getElementById('log').innerHTML == top.document.getElementById('exp').innerHTML) {
+            top.document.getElementById('success').style.display = 'block';
+        }
+        ">6</div>
+
+<table border=1 width="100%" style='top:200; position:absolute'>
+<tr><td width="50%">Log</td><td>Expected results</td></tr>
+<tr>
+    <td id=log style="vertical-align:top"></td>
+    <td id=exp style="vertical-align:top"><LI>mouseover on frame1</LI><LI>mouseover on t1</LI><LI>mouseout on t1</LI><LI>mouseout on frame1</LI><LI>mouseover on frame2</LI><LI>mouseover on t2</LI><LI>mouseout on t2</LI><LI>mouseout on frame2</LI><LI>mouseover on frame3</LI><LI>mouseover on t3_1</LI><LI>mouseout on frame3</LI><LI>mouseover on t3_2</LI><LI>mouseout on t3_2</LI><LI>mouseover on t4_2</LI><LI>mouseout on t4_2</LI><LI>mouseover on frame4</LI><LI>mouseover on t4_1</LI><LI>mouseout on t4_1</LI><LI>mouseout on frame4</LI><LI>mouseover on frame5</LI><LI>mouseover on t5_1</LI><LI>mouseout on frame5</LI><LI>mouseover on t5_2</LI><LI>mouseout on t5_2</LI><LI>mouseover on t6</LI><LI>mouseout on t6</LI></td>
+</tr>
+</table>
+
+<div style='height: 50; width: 300; top:100;left:100; position:absolute; display:none' id='success'><br><center>SUCCESS</center></div>
+
+<script>
+function autoTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+
+        eventSender.mouseMoveTo(1,1);
+        eventSender.mouseDown();
+        eventSender.mouseUp();
+
+        eventSender.mouseMoveTo(125, 125);
+        eventSender.mouseMoveTo(130, 125);
+        eventSender.mouseMoveTo(135, 125);
+
+        eventSender.mouseMoveTo(175, 125);
+        eventSender.mouseMoveTo(180, 125);
+
+        eventSender.mouseMoveTo(225, 125);
+        eventSender.mouseMoveTo(230, 125);
+
+        eventSender.mouseMoveTo(275, 125);
+        eventSender.mouseMoveTo(280, 125);
+
+        eventSender.mouseMoveTo(325, 125);
+        eventSender.mouseMoveTo(330, 125);
+
+        eventSender.mouseMoveTo(375, 125);
+        eventSender.mouseMoveTo(380, 125);
+        eventSender.mouseMoveTo(385, 125);
+
+        eventSender.mouseMoveTo(1, 1);
+        layoutTestController.notifyDone()
+    }
+}
+</script>
+
+</body>
+</html>
index 8b13a75..eda1835 100644 (file)
@@ -1,3 +1,44 @@
+2006-03-20  Alexey Proskuryakov  <ap@nypop.com>
+
+        Reviewed by Darin.
+
+        - http://bugzilla.opendarwin.org/show_bug.cgi?id=3439
+         mouseover effects can get stuck sometimes due to missing events
+        - http://bugzilla.opendarwin.org/show_bug.cgi?id=7701
+         mouseout sent to the wrong element when layout changes simultaneously
+        
+        Implemented saving the previous node under the mouse, so that we don't need to 
+        recalculate it, which is slow and even not really possible. This has fixed a number 
+        of issues with mouse event dispatching when the content changes.
+        The code still needs refactoring and cleanup, see bug 3439 for comments.
+
+        Tests (both files perform multiple checks):
+        - fast/events/mouseover-mouseout.html
+        - fast/events/mouseover-mouseout2.html
+
+        * page/Frame.h: Added a Frame parameter to passSubframeEventToSubframe(),
+        used for mouseMoved events.
+        * bridge/mac/FrameMac.h: Ditto.
+        * bridge/mac/FrameMac.mm: 
+        (WebCore::FrameMac::passSubframeEventToSubframe): Use the passed subframe to target 
+        NSMouseMoved events.
+        * page/FrameView.h: Added a prepareMouseEvent() helper that does viewportToContents translation.
+        * page/FrameView.cpp: Added data members for storing the previous node and subframe 
+        under the mouse to FrameViewPrivate. Removed the now unused prevMouseX/prevMouseY.
+        (WebCore::FrameViewPrivate::reset): Reset the new data members.
+        (WebCore::subframeForEvent): A temporary place for the code that extracts a subframe
+        pointer from MouseEventWithHitTestResults, moved from FrameMac::passSubframeEventToSubframe().
+        (WebCore::FrameView::prepareMouseEvent): The new helper.
+        (WebCore::FrameView::handleMousePressEvent): Use the new helper.
+        (WebCore::FrameView::handleMouseDoubleClickEvent): Ditto.
+        (WebCore::FrameView::handleMouseReleaseEvent): Ditto.
+        (WebCore::FrameView::updateDragAndDrop): Ditto.
+        (WebCore::FrameView::hoverTimerFired): Ditto.
+        (WebCore::FrameView::dispatchMouseEvent): Store and use the oldUnder node, don't store 
+        or use prevMouseX/Y.
+        (WebCore::FrameView::handleMouseMoveEvent): Rewrote dispatching events to subframes using 
+        a stored oldSubframe reference. Protect "this" from being removed while in this function.
+
 2006-03-20  Darin Adler  <darin@apple.com>
 
         Reviewed by Anders.
index 6e9fdd0..0ff36fc 100644 (file)
@@ -229,7 +229,7 @@ public:
     bool sendContextMenuEvent(NSEvent *);
 
     virtual bool passMouseDownEventToWidget(Widget *);
-    virtual bool passSubframeEventToSubframe(MouseEventWithHitTestResults &);
+    virtual bool passSubframeEventToSubframe(MouseEventWithHitTestResults&, Frame* subframePart);
     virtual bool passWheelEventToChildWidget(Node *);
     
     NSString *searchForLabelsAboveCell(RegularExpression *regExp, HTMLTableCellElement *cell);
index a02685d..f6f0996 100644 (file)
@@ -1831,24 +1831,13 @@ void FrameMac::handleMouseReleaseEvent(const MouseEventWithHitTestResults& event
     _sendingEventToSubview = false;
 }
 
-bool FrameMac::passSubframeEventToSubframe(MouseEventWithHitTestResults &event)
+bool FrameMac::passSubframeEventToSubframe(MouseEventWithHitTestResults& event, Frame* subframePart)
 {
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
 
     switch ([_currentEvent type]) {
         case NSMouseMoved: {
-            Node *node = event.innerNode();
-            if (!node)
-                return false;
-            RenderObject *renderer = node->renderer();
-            if (!renderer || !renderer->isWidget())
-                return false;
-            Widget *widget = static_cast<RenderWidget *>(renderer)->widget();
-            if (!widget || !widget->isFrameView())
-                return false;
-            Frame *subframePart = static_cast<FrameView *>(widget)->frame();
-            if (!subframePart)
-                return false;
+            ASSERT(subframePart);
             [Mac(subframePart)->bridge() mouseMoved:_currentEvent];
             return true;
         }
index 716504b..d97ace6 100644 (file)
@@ -591,7 +591,7 @@ public:
   virtual void openURLRequest(const ResourceRequest&) = 0;
   virtual void submitForm(const ResourceRequest&) = 0;
   virtual void urlSelected(const ResourceRequest&) = 0;
-  virtual bool passSubframeEventToSubframe(MouseEventWithHitTestResults&) = 0;
+  virtual bool passSubframeEventToSubframe(MouseEventWithHitTestResults&, Frame* subframePart = 0) = 0;
   virtual bool passWheelEventToChildWidget(Node *) = 0;
   virtual bool lastEventIsMouseUp() const = 0;
   virtual DeprecatedString overrideMediaType() const = 0;
index 55230fd..a793b52 100644 (file)
@@ -76,6 +76,8 @@ public:
     void reset()
     {
         underMouse = 0;
+        oldUnder = 0;
+        oldSubframe = 0;
         linkPressed = false;
         useSlowRepaints = false;
         dragTarget = 0;
@@ -84,8 +86,6 @@ public:
         ignoreWheelEvents = false;
         borderX = 30;
         borderY = 30;
-        prevMouseX = -1;
-        prevMouseY = -1;
         clickCount = 0;
         clickNode = 0;
         scrollingSelf = false;
@@ -104,6 +104,8 @@ public:
     }
 
     RefPtr<Node> underMouse;
+    RefPtr<Node> oldUnder;
+    RefPtr<Frame> oldSubframe;
 
     bool borderTouched : 1;
     bool borderStart : 1;
@@ -121,7 +123,6 @@ public:
     int clickCount;
     RefPtr<Node> clickNode;
 
-    int prevMouseX, prevMouseY;
     bool scrollingSelf;
     Timer<FrameView> layoutTimer;
     bool delayedLayout;
@@ -463,6 +464,22 @@ void FrameView::layout()
 //
 /////////////////
 
+static Frame* subframeForEvent(const MouseEventWithHitTestResults& mev)
+{
+    if (!mev.innerNode())
+        return 0;
+
+    RenderObject* renderer = mev.innerNode()->renderer();
+    if (!renderer || !renderer->isWidget())
+        return 0;
+
+    Widget* widget = static_cast<RenderWidget*>(renderer)->widget();
+    if (!widget || !widget->isFrameView())
+        return 0;
+
+    return static_cast<FrameView*>(widget)->frame();
+}
+
 void FrameView::handleMousePressEvent(const PlatformMouseEvent& mouseEvent)
 {
     if (!m_frame->document())
@@ -470,12 +487,9 @@ void FrameView::handleMousePressEvent(const PlatformMouseEvent& mouseEvent)
 
     RefPtr<FrameView> protector(this);
 
-    int xm, ym;
-    viewportToContents(mouseEvent.x(), mouseEvent.y(), xm, ym);
-
     d->mousePressed = true;
 
-    MouseEventWithHitTestResults mev = m_frame->document()->prepareMouseEvent(false, true, false, xm, ym, mouseEvent);
+    MouseEventWithHitTestResults mev = prepareMouseEvent(false, true, false, mouseEvent);
 
     if (m_frame->passSubframeEventToSubframe(mev)) {
         invalidateClick();
@@ -506,13 +520,10 @@ void FrameView::handleMouseDoubleClickEvent(const PlatformMouseEvent& mouseEvent
 
     RefPtr<FrameView> protector(this);
 
-    int xm, ym;
-    viewportToContents(mouseEvent.x(), mouseEvent.y(), xm, ym);
-
     // We get this instead of a second mouse-up 
     d->mousePressed = false;
 
-    MouseEventWithHitTestResults mev = m_frame->document()->prepareMouseEvent(false, true, false, xm, ym, mouseEvent);
+    MouseEventWithHitTestResults mev = prepareMouseEvent(false, true, false, mouseEvent);
 
     if (m_frame->passSubframeEventToSubframe(mev))
         return;
@@ -601,12 +612,11 @@ void FrameView::handleMouseMoveEvent(const PlatformMouseEvent& mouseEvent)
     if (!m_frame || !m_frame->document())
         return;
 
+    RefPtr<FrameView> protector(this);
+    
     if (d->hoverTimer.isActive())
         d->hoverTimer.stop();
 
-    int xm, ym;
-    viewportToContents(mouseEvent.x(), mouseEvent.y(), xm, ym);
-
     if (d->resizingFrameSet) {
         dispatchMouseEvent(mousemoveEvent, d->resizingFrameSet.get(), false, 0, mouseEvent, false);
         return;
@@ -616,15 +626,24 @@ void FrameView::handleMouseMoveEvent(const PlatformMouseEvent& mouseEvent)
     // if we are allowed to select.
     // This means that :hover and :active freeze in the state they were in when the mouse
     // was pressed, rather than updating for nodes the mouse moves over as you hold the mouse down.
-    MouseEventWithHitTestResults mev = m_frame->document()->prepareMouseEvent(d->mousePressed && m_frame->mouseDownMayStartSelect(),
-        d->mousePressed, true, xm, ym, mouseEvent);
+    MouseEventWithHitTestResults mev = prepareMouseEvent(d->mousePressed && m_frame->mouseDownMayStartSelect(),
+        d->mousePressed, true, mouseEvent);
+
+    if (d->oldSubframe)
+        m_frame->passSubframeEventToSubframe(mev, d->oldSubframe.get());
 
-    if (!m_frame->passSubframeEventToSubframe(mev))
-        setCursor(selectCursor(mev, m_frame.get(), d->mousePressed));
-        
     bool swallowEvent = dispatchMouseEvent(mousemoveEvent, mev.innerNode(), false, 0, mouseEvent, true);
     if (!swallowEvent)
         m_frame->handleMouseMoveEvent(mev);
+    
+    RefPtr<Frame> newSubframe = subframeForEvent(mev);
+    
+    if (newSubframe && d->oldSubframe != newSubframe)
+        m_frame->passSubframeEventToSubframe(mev, newSubframe.get());
+    else
+        setCursor(selectCursor(mev, m_frame.get(), d->mousePressed));
+    
+    d->oldSubframe = newSubframe;
 }
 
 void FrameView::invalidateClick()
@@ -640,9 +659,6 @@ void FrameView::handleMouseReleaseEvent(const PlatformMouseEvent& mouseEvent)
 
     RefPtr<FrameView> protector(this);
 
-    int xm, ym;
-    viewportToContents(mouseEvent.x(), mouseEvent.y(), xm, ym);
-
     d->mousePressed = false;
 
     if (d->resizingFrameSet) {
@@ -650,7 +666,7 @@ void FrameView::handleMouseReleaseEvent(const PlatformMouseEvent& mouseEvent)
         return;
     }
 
-    MouseEventWithHitTestResults mev = m_frame->document()->prepareMouseEvent(false, false, false, xm, ym, mouseEvent);
+    MouseEventWithHitTestResults mev = prepareMouseEvent(false, false, false, mouseEvent);
 
     if (m_frame->passSubframeEventToSubframe(mev))
         return;
@@ -685,9 +701,8 @@ bool FrameView::dispatchDragEvent(const AtomicString &eventType, Node *dragTarge
 bool FrameView::updateDragAndDrop(const PlatformMouseEvent& event, Clipboard* clipboard)
 {
     bool accept = false;
-    int xm, ym;
-    viewportToContents(event.x(), event.y(), xm, ym);
-    MouseEventWithHitTestResults mev = m_frame->document()->prepareMouseEvent(true, false, false, xm, ym, PlatformMouseEvent());
+
+    MouseEventWithHitTestResults mev = prepareMouseEvent(true, false, false, PlatformMouseEvent());
 
     // Drag events should never go to text nodes (following IE, and proper mouseover/out dispatch)
     Node* newTarget = mev.innerNode();
@@ -924,6 +939,17 @@ void FrameView::setResizingFrameSet(HTMLFrameSetElement *frameSet)
     d->resizingFrameSet = frameSet;
 }
 
+MouseEventWithHitTestResults FrameView::prepareMouseEvent(bool readonly, bool active, bool mouseMove, const PlatformMouseEvent& mev)
+{
+    ASSERT(m_frame);
+    ASSERT(m_frame->document());
+    
+    int xm, ym;
+    viewportToContents(mev.x(), mev.y(), xm, ym);
+
+    return m_frame->document()->prepareMouseEvent(readonly, active, mouseMove, xm, ym, mev);
+}
+
 bool FrameView::dispatchMouseEvent(const AtomicString& eventType, Node* targetNode, bool cancelable,
     int clickCount, const PlatformMouseEvent& mouseEvent, bool setUnder)
 {
@@ -934,31 +960,18 @@ bool FrameView::dispatchMouseEvent(const AtomicString& eventType, Node* targetNo
 
     // mouseout/mouseover
     if (setUnder) {
-        int clientX, clientY;
-        viewportToContents(mouseEvent.x(), mouseEvent.y(), clientX, clientY);
-        if (d->prevMouseX != clientX || d->prevMouseY != clientY) {
-            // ### this code sucks. we should save the oldUnder instead of calculating
-            // it again. calculating is expensive! (Dirk)
-            // Also, there's no guarantee that the old under node is even around any more,
-            // so we could be sending a mouseout to a node that never got a mouseover.
-            RefPtr<Node> oldUnder;
-            if (d->prevMouseX >= 0) {
-                oldUnder = m_frame->document()->prepareMouseEvent(true, false, true,
-                    d->prevMouseX, d->prevMouseY, mouseEvent).innerNode();
-                if (oldUnder && oldUnder->isTextNode())
-                    oldUnder = oldUnder->parentNode();
-            }
-            d->prevMouseX = clientX;
-            d->prevMouseY = clientY;
-            if (oldUnder != targetNode) {
-                // send mouseout event to the old node
-                if (oldUnder)
-                    EventTargetNodeCast(oldUnder.get())->dispatchMouseEvent(mouseEvent, mouseoutEvent, 0, targetNode);
-                // send mouseover event to the new node
-                if (targetNode)
-                    EventTargetNodeCast(targetNode)->dispatchMouseEvent(mouseEvent, mouseoverEvent, 0, oldUnder.get());
-            }
+        if (d->oldUnder && d->oldUnder->getDocument() != frame()->document())
+            d->oldUnder = 0;
+
+        if (d->oldUnder != targetNode) {
+            // send mouseout event to the old node
+            if (d->oldUnder)
+                EventTargetNodeCast(d->oldUnder.get())->dispatchMouseEvent(mouseEvent, mouseoutEvent, 0, targetNode);
+            // send mouseover event to the new node
+            if (targetNode)
+                EventTargetNodeCast(targetNode)->dispatchMouseEvent(mouseEvent, mouseoverEvent, 0, d->oldUnder.get());
         }
+        d->oldUnder = targetNode;
     }
 
     bool swallowEvent = false;
@@ -1051,7 +1064,7 @@ void FrameView::layoutTimerFired(Timer<FrameView>*)
 void FrameView::hoverTimerFired(Timer<FrameView>*)
 {
     d->hoverTimer.stop();
-    m_frame->document()->prepareMouseEvent(false, false, true, d->prevMouseX, d->prevMouseY, PlatformMouseEvent());
+    prepareMouseEvent(false, false, true, PlatformMouseEvent());
 }
 
 void FrameView::scheduleRelayout()
index 4b9d337..88d76f8 100644 (file)
@@ -54,6 +54,7 @@ class IntRect;
 class PlatformKeyboardEvent;
 class FrameMac;
 class PlatformMouseEvent;
+class MouseEventWithHitTestResults;
 class Node;
 class RenderBox;
 class RenderCanvas;
@@ -215,6 +216,8 @@ private:
     DeprecatedStringList formCompletionItems(const DeprecatedString &name) const;
     void addFormCompletionItem(const DeprecatedString &name, const DeprecatedString &value);
 
+    MouseEventWithHitTestResults prepareMouseEvent(bool readonly, bool active, bool mouseMove, const PlatformMouseEvent&);
+
     bool dispatchMouseEvent(const AtomicString& eventType, Node* target,
         bool cancelable, int clickCount, const PlatformMouseEvent&, bool setUnder);
     bool dispatchDragEvent(const AtomicString& eventType, Node* target,