Title: [134150] trunk
Revision
134150
Author
o...@chromium.org
Date
2012-11-09 21:16:00 -0800 (Fri, 09 Nov 2012)

Log Message

Should only fire a single set of mouse events and update hover state once when scrolling is done
https://bugs.webkit.org/show_bug.cgi?id=99940

Reviewed by Levi Weintraub.

Source/WebCore:

-Use a DeferrableOneShotTimer instead of a Timer. By resetting when the
timer is fired, we ensure the actual goal of not firing fake mouse events
until the scroll is completed. This is the core part of this change.
-Change our mouse event throttling to keep a running average of how long
mouse events take and adjust throttling appropriately.
Test: fast/scrolling/fake-mouse-event-throttling.html
-Maintain a minimum throttle of 100ms.

* page/EventHandler.cpp:
(WebCore):
(WebCore::RunningAverageDurationTracker::RunningAverageDurationTracker):
(WebCore::RunningAverageDurationTracker::~RunningAverageDurationTracker):
Keep track of a running average instead of max. This lets us adjust throttling
dynamically without punishing a page for having a single mouse event handler
that takes disproportionately long.
(RunningAverageDurationTracker):
(WebCore::EventHandler::EventHandler):
(WebCore::EventHandler::clear):
(WebCore::EventHandler::mouseMoved):
(WebCore::EventHandler::handleMouseMoveEvent):
(WebCore::EventHandler::dispatchFakeMouseMoveEventSoon):
(WebCore::EventHandler::fakeMouseMoveEventTimerFired):
* page/EventHandler.h:
(EventHandler):
* platform/Timer.h:
(WebCore::DeferrableOneShotTimer::setDelay):
(WebCore::DeferrableOneShotTimer::delay):
Add a way of adjusting the timer delay.

LayoutTests:

Tests basic throttling. I couldn't think of a good way to test changing
the delay that wouldn't make for a test that takes minutes to run.

* fast/scrolling/fake-mouse-event-throttling-expected.txt: Added.
* fast/scrolling/fake-mouse-event-throttling.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (134149 => 134150)


--- trunk/LayoutTests/ChangeLog	2012-11-10 05:12:55 UTC (rev 134149)
+++ trunk/LayoutTests/ChangeLog	2012-11-10 05:16:00 UTC (rev 134150)
@@ -1,3 +1,16 @@
+2012-11-09  Ojan Vafai  <o...@chromium.org>
+
+        Should only fire a single set of mouse events and update hover state once when scrolling is done
+        https://bugs.webkit.org/show_bug.cgi?id=99940
+
+        Reviewed by Levi Weintraub.
+
+        Tests basic throttling. I couldn't think of a good way to test changing
+        the delay that wouldn't make for a test that takes minutes to run.
+
+        * fast/scrolling/fake-mouse-event-throttling-expected.txt: Added.
+        * fast/scrolling/fake-mouse-event-throttling.html: Added.
+
 2012-11-09  Rick Byers  <rby...@chromium.org>
 
         [chromium] Move to USE(LAZY_NATIVE_CURSOR)

Added: trunk/LayoutTests/fast/scrolling/fake-mouse-event-throttling-expected.txt (0 => 134150)


--- trunk/LayoutTests/fast/scrolling/fake-mouse-event-throttling-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/scrolling/fake-mouse-event-throttling-expected.txt	2012-11-10 05:16:00 UTC (rev 134150)
@@ -0,0 +1,4 @@
+PASS numberOfMouseMoves is 0
+PASS numberOfMouseMoves is 1
+PASS numberOfMouseMoves is 2
+

Added: trunk/LayoutTests/fast/scrolling/fake-mouse-event-throttling.html (0 => 134150)


--- trunk/LayoutTests/fast/scrolling/fake-mouse-event-throttling.html	                        (rev 0)
+++ trunk/LayoutTests/fast/scrolling/fake-mouse-event-throttling.html	2012-11-10 05:16:00 UTC (rev 134150)
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<style>
+body {
+    overflow: scroll;
+}
+div {
+    height: 100px;
+}
+.pointer {
+    cursor: pointer;
+    background-color: salmon;
+}
+.plain {
+    background-color: orange;
+}
+</style>
+<script src=""
+<body _onload_="runNextTest()">
+
+<script>
+for (var i = 0; i < 100; i++) {
+    document.write('<div class=pointer></div><div class=plain></div>');
+}
+
+if (window.testRunner)
+    testRunner.waitUntilDone();
+if (window.eventSender)
+    eventSender.mouseMoveTo(100, 50);
+
+var numberOfMouseMoves = 0;
+document.body._onmousemove_ = function() {
+    var start = Date.now();
+    while (Date.now() - start < mouseMoveDuration) {
+    }
+    numberOfMouseMoves++;
+
+    if (delays.length)
+        runNextTest();
+    else {
+        shouldBe("numberOfMouseMoves", String(expectedNumberOfMouseMoves));
+        if (window.testRunner)
+            testRunner.notifyDone();
+    }
+
+}
+
+var delays = [5, 200];
+var expectedNumberOfMouseMoves = delays.length;
+
+function runNextTest()
+{
+    shouldBe("numberOfMouseMoves", String(expectedNumberOfMouseMoves - delays.length));
+    mouseMoveDuration = delays.shift();
+    var position = 75 * delays.length;
+    window.scrollTo(0, position);
+    window.scrollTo(0, position + 25);
+    setTimeout(function() {
+        window.scrollTo(0, position + 50);
+    }, 0);
+}
+</script>
+</body>
+

Modified: trunk/LayoutTests/platform/mac/TestExpectations (134149 => 134150)


--- trunk/LayoutTests/platform/mac/TestExpectations	2012-11-10 05:12:55 UTC (rev 134149)
+++ trunk/LayoutTests/platform/mac/TestExpectations	2012-11-10 05:16:00 UTC (rev 134150)
@@ -1165,6 +1165,9 @@
 # TileCache tests that don't apply to WK1 (they are re-enabled via the WK2 TestExpectations file)
 platform/mac/tiled-drawing/ [ Skip ]
 
+# DRT is not firing fake mouse events. Safari fires them fine. Looks like a bug with Apple's Mac DRT.
+webkit.org/b/101808 fast/scrolling/fake-mouse-event-throttling.html [ Skip ]
+
 webkit.org/b/100846 inspector-protocol/debugger-pause-dedicated-worker.html [ Skip ]
 webkit.org/b/100846 inspector-protocol/debugger-terminate-dedicated-worker-while-paused.html [ Skip ]
 

Modified: trunk/Source/WebCore/ChangeLog (134149 => 134150)


--- trunk/Source/WebCore/ChangeLog	2012-11-10 05:12:55 UTC (rev 134149)
+++ trunk/Source/WebCore/ChangeLog	2012-11-10 05:16:00 UTC (rev 134150)
@@ -1,3 +1,39 @@
+2012-11-09  Ojan Vafai  <o...@chromium.org>
+
+        Should only fire a single set of mouse events and update hover state once when scrolling is done
+        https://bugs.webkit.org/show_bug.cgi?id=99940
+
+        Reviewed by Levi Weintraub.
+
+        -Use a DeferrableOneShotTimer instead of a Timer. By resetting when the
+        timer is fired, we ensure the actual goal of not firing fake mouse events
+        until the scroll is completed. This is the core part of this change.
+        -Change our mouse event throttling to keep a running average of how long
+        mouse events take and adjust throttling appropriately.
+        Test: fast/scrolling/fake-mouse-event-throttling.html
+        -Maintain a minimum throttle of 100ms.
+
+        * page/EventHandler.cpp:
+        (WebCore):
+        (WebCore::RunningAverageDurationTracker::RunningAverageDurationTracker):
+        (WebCore::RunningAverageDurationTracker::~RunningAverageDurationTracker):
+        Keep track of a running average instead of max. This lets us adjust throttling
+        dynamically without punishing a page for having a single mouse event handler
+        that takes disproportionately long.
+        (RunningAverageDurationTracker):
+        (WebCore::EventHandler::EventHandler):
+        (WebCore::EventHandler::clear):
+        (WebCore::EventHandler::mouseMoved):
+        (WebCore::EventHandler::handleMouseMoveEvent):
+        (WebCore::EventHandler::dispatchFakeMouseMoveEventSoon):
+        (WebCore::EventHandler::fakeMouseMoveEventTimerFired):
+        * page/EventHandler.h:
+        (EventHandler):
+        * platform/Timer.h:
+        (WebCore::DeferrableOneShotTimer::setDelay):
+        (WebCore::DeferrableOneShotTimer::delay):
+        Add a way of adjusting the timer delay.
+
 2012-11-09  Rick Byers  <rby...@chromium.org>
 
         Move chromium to USE(LAZY_NATIVE_CURSOR)

Modified: trunk/Source/WebCore/page/EventHandler.cpp (134149 => 134150)


--- trunk/Source/WebCore/page/EventHandler.cpp	2012-11-10 05:12:55 UTC (rev 134149)
+++ trunk/Source/WebCore/page/EventHandler.cpp	2012-11-10 05:16:00 UTC (rev 134150)
@@ -136,11 +136,17 @@
 // When the autoscroll or the panScroll is triggered when do the scroll every 0.05s to make it smooth
 const double autoscrollInterval = 0.05;
 
-// The amount of time to wait before sending a fake mouse event, triggered
-// during a scroll. The short interval is used if the content responds to the mouse events quickly enough,
-// otherwise the long interval is used.
-const double fakeMouseMoveShortInterval = 0.1;
-const double fakeMouseMoveLongInterval = 0.250;
+// The amount of time to wait before sending a fake mouse event, triggered during a scroll.
+const double fakeMouseMoveMinimumInterval = 0.1;
+// Amount to increase the fake mouse event throttling when the running average exceeds the delay.
+// Picked fairly arbitrarily.
+const double fakeMouseMoveIntervalIncrease = 0.05;
+const double fakeMouseMoveRunningAverageCount = 10;
+// Decrease the fakeMouseMoveInterval when the current delay is >2x the running average,
+// but only decrease to 3/4 the current delay to avoid too much thrashing.
+// Not sure this distinction really matters in practice.
+const double fakeMouseMoveIntervalReductionLimit = 0.5;
+const double fakeMouseMoveIntervalReductionFraction = 0.75;
 
 enum NoCursorChangeType { NoCursorChange };
 
@@ -157,21 +163,28 @@
     Cursor m_cursor;
 };
 
-class MaximumDurationTracker {
+class RunningAverageDurationTracker {
 public:
-    explicit MaximumDurationTracker(double *maxDuration)
-        : m_maxDuration(maxDuration)
+    RunningAverageDurationTracker(double* average, unsigned numberOfRunsToTrack)
+        : m_average(average)
+        , m_numberOfRunsToTrack(numberOfRunsToTrack)
         , m_start(monotonicallyIncreasingTime())
     {
     }
 
-    ~MaximumDurationTracker()
+    ~RunningAverageDurationTracker()
     {
-        *m_maxDuration = max(*m_maxDuration, monotonicallyIncreasingTime() - m_start);
+        double duration = monotonicallyIncreasingTime() - m_start;
+        if (!*m_average) {
+            *m_average = duration;
+            return;
+        }
+        *m_average = (*m_average * (m_numberOfRunsToTrack - 1) + (duration)) / m_numberOfRunsToTrack;
     }
 
 private:
-    double* m_maxDuration;
+    double* m_average;
+    unsigned m_numberOfRunsToTrack;
     double m_start;
 };
 
@@ -316,7 +329,7 @@
     , m_autoscrollInProgress(false)
     , m_mouseDownMayStartAutoscroll(false)
     , m_mouseDownWasInSubframe(false)
-    , m_fakeMouseMoveEventTimer(this, &EventHandler::fakeMouseMoveEventTimerFired)
+    , m_fakeMouseMoveEventTimer(this, &EventHandler::fakeMouseMoveEventTimerFired, fakeMouseMoveMinimumInterval)
 #if ENABLE(SVG)
     , m_svgPan(false)
 #endif
@@ -333,7 +346,7 @@
 #if ENABLE(TOUCH_EVENTS)
     , m_touchPressed(false)
 #endif
-    , m_maxMouseMovedDuration(0)
+    , m_mouseMovedDurationRunningAverage(0)
     , m_baseEventType(PlatformEvent::NoType)
 {
 }
@@ -385,7 +398,7 @@
 #if ENABLE(GESTURE_EVENTS)
     m_scrollGestureHandlingNode = 0;
 #endif
-    m_maxMouseMovedDuration = 0;
+    m_mouseMovedDurationRunningAverage = 0;
     m_baseEventType = PlatformEvent::NoType;
 }
 
@@ -1732,7 +1745,7 @@
 bool EventHandler::mouseMoved(const PlatformMouseEvent& event)
 {
     RefPtr<FrameView> protector(m_frame->view());
-    MaximumDurationTracker maxDurationTracker(&m_maxMouseMovedDuration);
+    RunningAverageDurationTracker durationTracker(&m_mouseMovedDurationRunningAverage, fakeMouseMoveRunningAverageCount);
 
 
 #if ENABLE(TOUCH_EVENTS)
@@ -2863,18 +2876,15 @@
     if (settings && !settings->deviceSupportsMouse())
         return;
 
-    // If the content has ever taken longer than fakeMouseMoveShortInterval we
-    // reschedule the timer and use a longer time. This will cause the content
-    // to receive these moves only after the user is done scrolling, reducing
-    // pauses during the scroll.
-    if (m_maxMouseMovedDuration > fakeMouseMoveShortInterval) {
-        if (m_fakeMouseMoveEventTimer.isActive())
-            m_fakeMouseMoveEventTimer.stop();
-        m_fakeMouseMoveEventTimer.startOneShot(fakeMouseMoveLongInterval);
-    } else {
-        if (!m_fakeMouseMoveEventTimer.isActive())
-            m_fakeMouseMoveEventTimer.startOneShot(fakeMouseMoveShortInterval);
-    }
+    // Adjust the mouse move throttling so that it's roughly around our running average of the duration of mousemove events.
+    // This will cause the content to receive these moves only after the user is done scrolling, reducing pauses during the scroll.
+    // This will only measure the duration of the mousemove event though (not for example layouts),
+    // so maintain at least a minimum interval.
+    if (m_mouseMovedDurationRunningAverage > m_fakeMouseMoveEventTimer.delay())
+        m_fakeMouseMoveEventTimer.setDelay(m_mouseMovedDurationRunningAverage + fakeMouseMoveIntervalIncrease);
+    else if (m_mouseMovedDurationRunningAverage < fakeMouseMoveIntervalReductionLimit * m_fakeMouseMoveEventTimer.delay())
+        m_fakeMouseMoveEventTimer.setDelay(max(fakeMouseMoveMinimumInterval, fakeMouseMoveIntervalReductionFraction * m_fakeMouseMoveEventTimer.delay()));
+    m_fakeMouseMoveEventTimer.restart();
 }
 
 void EventHandler::dispatchFakeMouseMoveEventSoonInQuad(const FloatQuad& quad)
@@ -2894,7 +2904,7 @@
     m_fakeMouseMoveEventTimer.stop();
 }
 
-void EventHandler::fakeMouseMoveEventTimerFired(Timer<EventHandler>* timer)
+void EventHandler::fakeMouseMoveEventTimerFired(DeferrableOneShotTimer<EventHandler>* timer)
 {
     ASSERT_UNUSED(timer, timer == &m_fakeMouseMoveEventTimer);
     ASSERT(!m_mousePressed);

Modified: trunk/Source/WebCore/page/EventHandler.h (134149 => 134150)


--- trunk/Source/WebCore/page/EventHandler.h	2012-11-10 05:12:55 UTC (rev 134149)
+++ trunk/Source/WebCore/page/EventHandler.h	2012-11-10 05:16:00 UTC (rev 134150)
@@ -282,7 +282,7 @@
     static bool isKeyboardOptionTab(KeyboardEvent*);
     static bool eventInvertsTabsToLinksClientCallResult(KeyboardEvent*);
 
-    void fakeMouseMoveEventTimerFired(Timer<EventHandler>*);
+    void fakeMouseMoveEventTimerFired(DeferrableOneShotTimer<EventHandler>*);
     void cancelFakeMouseMoveEvent();
 
 #if ENABLE(TOUCH_EVENTS)
@@ -405,7 +405,7 @@
     bool m_mouseDownMayStartAutoscroll;
     bool m_mouseDownWasInSubframe;
 
-    Timer<EventHandler> m_fakeMouseMoveEventTimer;
+    DeferrableOneShotTimer<EventHandler> m_fakeMouseMoveEventTimer;
 
 #if ENABLE(SVG)
     bool m_svgPan;
@@ -462,7 +462,7 @@
     RefPtr<Node> m_scrollGestureHandlingNode;
 #endif
 
-    double m_maxMouseMovedDuration;
+    double m_mouseMovedDurationRunningAverage;
     PlatformEvent::Type m_baseEventType;
 };
 

Modified: trunk/Source/WebCore/platform/Timer.h (134149 => 134150)


--- trunk/Source/WebCore/platform/Timer.h	2012-11-10 05:12:55 UTC (rev 134149)
+++ trunk/Source/WebCore/platform/Timer.h	2012-11-10 05:16:00 UTC (rev 134150)
@@ -140,6 +140,17 @@
         startOneShot(m_delay);
     }
 
+    void setDelay(double delay)
+    {
+        m_delay = delay;
+        if (isActive()) {
+            stop();
+            restart();
+        }
+    }
+
+    double delay() const { return m_delay; }
+
     using TimerBase::stop;
     using TimerBase::isActive;
 private:
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to