- 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: