Source/WebCore: Updating mouse cursor on style changes without emitting fake mousemove event

https://bugs.webkit.org/show_bug.cgi?id=101857

Patch by Aivo Paas <aivopaas@gmail.com> on 2013-04-05
Reviewed by Allan Sandfeld Jensen.

Mouse cursor changes in styles used to be reflected in UI through dispatching a fake
mousemove event. The old approach has some flaws: it emits a mousemove event in
javascript when there is no mouse movement involved (bug 85343); the fake mousemove
event is cancelled while there is a mouse button held down - cursor won't change
until mouse is moved or the button released (bug 53341).

The new approach does not use the fake mousemove event. Instead, it uses only the logic
needed for the actual cursor change to happen.

EventHandler::selectCursor was refactored to not take a whole mouse event but instead work with
HitTestResult so that EventHandler::updateCursor must not create a useless PlatformEvent.

Fixes: https://bugs.webkit.org/show_bug.cgi?id=85343 (mousemove event on cursor change)
       https://bugs.webkit.org/show_bug.cgi?id=53341 (no cursor change when mouse button down)

Tests: fast/events/mouse-cursor-change.html
       fast/events/mouse-cursor-no-mousemove.html

* page/EventHandler.cpp:
(WebCore::EventHandler::EventHandler):
(WebCore::EventHandler::clear):
(WebCore::EventHandler::cursorUpdateTimerFired):
(WebCore::EventHandler::updateCursor):
(WebCore::EventHandler::selectCursor):
(WebCore::EventHandler::handleMouseMoveEvent):
(WebCore::EventHandler::scheduleCursorUpdate):
* page/EventHandler.h:
* page/FrameView.cpp:
(WebCore::FrameView::shouldSetCursor):
* page/FrameView.h:
* page/MouseEventWithHitTestResults.cpp:
(WebCore::MouseEventWithHitTestResults::isOverLink):
* rendering/HitTestResult.cpp:
(WebCore::HitTestResult::isOverLink):
* rendering/HitTestResult.h:
* rendering/RenderObject.cpp:
(WebCore::RenderObject::styleDidChange):

LayoutTests: Updating mouse cursor on style changes without emitting fake mousemove event
https://bugs.webkit.org/show_bug.cgi?id=101857
Changing CSS cursor should work no matter is mouse button is pressed or not
https://bugs.webkit.org/show_bug.cgi?id=53341

Patch by Aivo Paas <aivopaas@gmail.com> on 2013-04-05
Reviewed by Allan Sandfeld Jensen.

Added tests for changing cursor on mousemove, mousedown, mouseup and mousemove
while mouse button being held down. Also added test to verify that a mousemove
event is not fired for changing cursor when mouse is not moving.

* fast/events/mouse-cursor-change-expected.txt: Added.
* fast/events/mouse-cursor-change.html: Added.
* fast/events/mouse-cursor-no-mousemove-expected.txt: Added.
* fast/events/mouse-cursor-no-mousemove.html: Added.
* platform/mac/TestExpectations:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@147739 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 6e2b90e2
2013-04-05 Aivo Paas <aivopaas@gmail.com>
Updating mouse cursor on style changes without emitting fake mousemove event
https://bugs.webkit.org/show_bug.cgi?id=101857
Changing CSS cursor should work no matter is mouse button is pressed or not
https://bugs.webkit.org/show_bug.cgi?id=53341
Reviewed by Allan Sandfeld Jensen.
Added tests for changing cursor on mousemove, mousedown, mouseup and mousemove
while mouse button being held down. Also added test to verify that a mousemove
event is not fired for changing cursor when mouse is not moving.
* fast/events/mouse-cursor-change-expected.txt: Added.
* fast/events/mouse-cursor-change.html: Added.
* fast/events/mouse-cursor-no-mousemove-expected.txt: Added.
* fast/events/mouse-cursor-no-mousemove.html: Added.
* platform/mac/TestExpectations:
2013-04-05 Zan Dobersek <zdobersek@igalia.com>
Unreviewed GTK gardening.
Test that mouse cursors are changed correctly on mouse events.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
Bug 53341
Mouse move
Cursor Info: type=Hand hotSpot=0,0
Mouse down
Cursor Info: type=Progress hotSpot=0,0
Mouse hold down, move
Cursor Info: type=Hand hotSpot=0,0
Mouse up
Cursor Info: type=Help hotSpot=0,0
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<html>
<head>
<script src="../js/resources/js-test-pre.js"></script>
<style type="text/css">
</style>
</head>
<body>
<p id="description"></p>
<p><a href="https://bugs.webkit.org/show_bug.cgi?id=53341">Bug 53341</a></p>
<div id="test-container">
<div id="target" onMouseDown="style.cursor='progress';event.preventDefault();" onMouseMove="style.cursor='pointer';" onMouseUp="style.cursor='help';" style="cursor:pointer;">Play with mouse on this element. Cursors change on events - mousemove: pointer(hand), mousedown: progress, mouseup: help.</div>
</div>
<br/>
<div id="console"></div>
<script>
var CURSOR_UPDATE_DELAY = 50;
description("Test that mouse cursors are changed correctly on mouse events.");
if (!window.eventSender) {
testFailed('This test requires DumpRenderTree');
}
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
window.jsTestIsAsync = true;
}
function runTest(prepare, next) {
prepare();
setTimeout(function() {
debug('Cursor Info: ' + window.internals.getCurrentCursorInfo(document));
debug('');
next();
}, CURSOR_UPDATE_DELAY);
}
function testsDone() {
// This text is redundant with the test output - hide it
document.getElementById('test-container').style.display = 'none';
finishJSTest();
}
// Can't do anything useful here without eventSender
if (window.eventSender) {
var target = document.getElementById('target');
eventSender.dragMode = false;
var tests = [
function() {
debug('Mouse move');
eventSender.mouseMoveTo(target.offsetLeft + 3, target.offsetTop + 3);
}, function() {
debug('Mouse down');
eventSender.mouseDown();
}, function() {
debug('Mouse hold down, move');
eventSender.mouseMoveTo(target.offsetLeft + 13, target.offsetTop + 3);
}, function() {
debug('Mouse up');
eventSender.mouseUp();
}
];
var i = 0;
function nextTest() {
if (i < tests.length) {
runTest(tests[i++], nextTest);
} else {
testsDone();
}
}
nextTest();
}
</script>
<script src="../../fast/js/resources/js-test-post.js"></script>
</body>
</html>
Test that there is no mousemove event fired when changing cursor.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
Bug 85343
TEST CASE: Mouse idle, change cursor should not fire mousemove event
Cursor Info: type=Pointer hotSpot=0,0
Cursor Info: type=Help hotSpot=0,0
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<html>
<head>
<script src="../js/resources/js-test-pre.js"></script>
<style type="text/css">
</style>
</head>
<body>
<p id="description"></p>
<p><a href="https://bugs.webkit.org/show_bug.cgi?id=85343">Bug 85343</a></p>
<div id="test-container">
<div id="target" style="cursor:default">Mouse idle, change cursor should not fire mousemove event</div>
</div>
<br/>
<div id="console"></div>
<script>
var CURSOR_UPDATE_DELAY = 50;
description("Test that there is no mousemove event fired when changing cursor.");
if (!window.eventSender) {
testFailed('This test requires DumpRenderTree');
}
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
window.jsTestIsAsync = true;
}
// Can't do anything useful here without eventSender
if (window.eventSender) {
var node = document.getElementById('target');
debug('TEST CASE: ' + node.textContent);
eventSender.mouseMoveTo(node.offsetLeft + 3, node.offsetTop + 3);
debug('Cursor Info: ' + window.internals.getCurrentCursorInfo(document));
node.addEventListener('mousemove', function() {
testFailed('Mousemove event should not be fired when changing cursor');
finishJSTest();
});
node.style.cursor = 'help';
setTimeout(function() {
debug('Cursor Info: ' + window.internals.getCurrentCursorInfo(document));
debug('');
}, CURSOR_UPDATE_DELAY);
// Give some time for the change to resolve. Fake mousemove event that caused bug, is using a timer
setTimeout(function() {
document.getElementById('test-container').style.display = 'none';
finishJSTest();
}, 150);
}
</script>
<script src="../../fast/js/resources/js-test-post.js"></script>
</body>
</html>
......@@ -1500,3 +1500,5 @@ webkit.org/b/113227 transitions/cross-fade-border-image.html [ Pass Failure ]
webkit.org/b/113227 transitions/default-timing-function.html [ Pass Failure ]
webkit.org/b/113227 transitions/svg-text-shadow-transition.html [ Pass Failure ]
# New test failing, needs investigation.
webkit.org/b/103857 fast/events/mouse-cursor-change.html
2013-04-05 Aivo Paas <aivopaas@gmail.com>
Updating mouse cursor on style changes without emitting fake mousemove event
https://bugs.webkit.org/show_bug.cgi?id=101857
Reviewed by Allan Sandfeld Jensen.
Mouse cursor changes in styles used to be reflected in UI through dispatching a fake
mousemove event. The old approach has some flaws: it emits a mousemove event in
javascript when there is no mouse movement involved (bug 85343); the fake mousemove
event is cancelled while there is a mouse button held down - cursor won't change
until mouse is moved or the button released (bug 53341).
The new approach does not use the fake mousemove event. Instead, it uses only the logic
needed for the actual cursor change to happen.
EventHandler::selectCursor was refactored to not take a whole mouse event but instead work with
HitTestResult so that EventHandler::updateCursor must not create a useless PlatformEvent.
Fixes: https://bugs.webkit.org/show_bug.cgi?id=85343 (mousemove event on cursor change)
https://bugs.webkit.org/show_bug.cgi?id=53341 (no cursor change when mouse button down)
Tests: fast/events/mouse-cursor-change.html
fast/events/mouse-cursor-no-mousemove.html
* page/EventHandler.cpp:
(WebCore::EventHandler::EventHandler):
(WebCore::EventHandler::clear):
(WebCore::EventHandler::cursorUpdateTimerFired):
(WebCore::EventHandler::updateCursor):
(WebCore::EventHandler::selectCursor):
(WebCore::EventHandler::handleMouseMoveEvent):
(WebCore::EventHandler::scheduleCursorUpdate):
* page/EventHandler.h:
* page/FrameView.cpp:
(WebCore::FrameView::shouldSetCursor):
* page/FrameView.h:
* page/MouseEventWithHitTestResults.cpp:
(WebCore::MouseEventWithHitTestResults::isOverLink):
* rendering/HitTestResult.cpp:
(WebCore::HitTestResult::isOverLink):
* rendering/HitTestResult.h:
* rendering/RenderObject.cpp:
(WebCore::RenderObject::styleDidChange):
2013-04-05 Jocelyn Turcotte <jocelyn.turcotte@digia.com>
[Qt] PluginsX11: exposedRect offset is applied twice when painting windowless
......@@ -144,6 +144,10 @@ using namespace SVGNames;
const double fakeMouseMoveShortInterval = 0.1;
const double fakeMouseMoveLongInterval = 0.250;
// The amount of time to wait for a cursor update on style and layout changes
// Set to 50Hz, no need to be faster than common screen refresh rate
const double cursorUpdateInterval = 0.02;
const int maximumCursorSize = 128;
#if ENABLE(MOUSE_CURSOR_SCALE)
// It's pretty unlikely that a scale of less than one would ever be used. But all we really
......@@ -319,6 +323,7 @@ EventHandler::EventHandler(Frame* frame)
, m_mouseDownWasSingleClickInSelection(false)
, m_selectionInitiationState(HaveNotStartedSelection)
, m_hoverTimer(this, &EventHandler::hoverTimerFired)
, m_cursorUpdateTimer(this, &EventHandler::cursorUpdateTimerFired)
, m_autoscrollController(adoptPtr(new AutoscrollController))
, m_mouseDownMayStartAutoscroll(false)
, m_mouseDownWasInSubframe(false)
......@@ -374,6 +379,7 @@ DragState& EventHandler::dragState()
void EventHandler::clear()
{
m_hoverTimer.stop();
m_cursorUpdateTimer.stop();
m_fakeMouseMoveEventTimer.stop();
#if ENABLE(CURSOR_VISIBILITY)
cancelAutoHideCursorTimer();
......@@ -1241,7 +1247,50 @@ bool EventHandler::useHandCursor(Node* node, bool isOverLink, bool shiftKey)
return ((isOverLink || isSubmitImage(node)) && (!editable || editableLinkEnabled));
}
OptionalCursor EventHandler::selectCursor(const MouseEventWithHitTestResults& event, Scrollbar* scrollbar)
void EventHandler::cursorUpdateTimerFired(Timer<EventHandler>*)
{
ASSERT(m_frame);
ASSERT(m_frame->document());
updateCursor();
}
void EventHandler::updateCursor()
{
if (m_mousePositionIsUnknown)
return;
FrameView* view = m_frame->view();
if (!view)
return;
RenderView* renderView = view->renderView();
if (!renderView)
return;
if (!view->shouldSetCursor())
return;
bool shiftKey;
bool ctrlKey;
bool altKey;
bool metaKey;
PlatformKeyboardEvent::getCurrentModifierState(shiftKey, ctrlKey, altKey, metaKey);
m_frame->document()->updateLayout();
HitTestRequest request(HitTestRequest::ReadOnly);
HitTestResult result(view->windowToContents(m_lastKnownMousePosition));
renderView->hitTest(request, result);
OptionalCursor optionalCursor = selectCursor(result, shiftKey);
if (optionalCursor.isCursorChange()) {
m_currentMouseCursor = optionalCursor.cursor();
view->setCursor(m_currentMouseCursor);
}
}
OptionalCursor EventHandler::selectCursor(const HitTestResult& result, bool shiftKey)
{
if (m_resizeLayer && m_resizeLayer->inResizeMode())
return NoCursorChange;
......@@ -1254,8 +1303,11 @@ OptionalCursor EventHandler::selectCursor(const MouseEventWithHitTestResults& ev
return NoCursorChange;
#endif
Node* node = event.targetNode();
RenderObject* renderer = node ? node->renderer() : 0;
Node* node = result.targetNode();
if (!node)
return NoCursorChange;
RenderObject* renderer = node->renderer();
RenderStyle* style = renderer ? renderer->style() : 0;
bool horizontalText = !style || style->isHorizontalWritingMode();
const Cursor& iBeam = horizontalText ? iBeamCursor() : verticalTextCursor();
......@@ -1279,7 +1331,7 @@ OptionalCursor EventHandler::selectCursor(const MouseEventWithHitTestResults& ev
if (renderer) {
Cursor overrideCursor;
switch (renderer->getCursor(roundedIntPoint(event.localPoint()), overrideCursor)) {
switch (renderer->getCursor(roundedIntPoint(result.localPoint()), overrideCursor)) {
case SetCursorBasedOnStyle:
break;
case SetCursor:
......@@ -1326,19 +1378,19 @@ OptionalCursor EventHandler::selectCursor(const MouseEventWithHitTestResults& ev
switch (style ? style->cursor() : CURSOR_AUTO) {
case CURSOR_AUTO: {
bool editable = (node && node->rendererIsEditable());
bool editable = node->rendererIsEditable();
if (useHandCursor(node, event.isOverLink(), event.event().shiftKey()))
if (useHandCursor(node, result.isOverLink(), shiftKey))
return handCursor();
bool inResizer = false;
if (renderer) {
if (RenderLayer* layer = renderer->enclosingLayer()) {
if (FrameView* view = m_frame->view())
inResizer = layer->isPointInResizeControl(view->windowToContents(event.event().position()));
inResizer = layer->isPointInResizeControl(view->windowToContents(roundedIntPoint(result.localPoint())));
}
}
if ((editable || (renderer && renderer->isText() && node->canStartSelection())) && !inResizer && !scrollbar)
if ((editable || (renderer && renderer->isText() && node->canStartSelection())) && !inResizer && !result.scrollbar())
return iBeam;
return pointerCursor();
}
......@@ -1706,6 +1758,8 @@ bool EventHandler::handleMouseMoveEvent(const PlatformMouseEvent& mouseEvent, Hi
if (m_hoverTimer.isActive())
m_hoverTimer.stop();
m_cursorUpdateTimer.stop();
cancelFakeMouseMoveEvent();
#if ENABLE(SVG)
......@@ -1778,7 +1832,7 @@ bool EventHandler::handleMouseMoveEvent(const PlatformMouseEvent& mouseEvent, Hi
if (scrollbar && !m_mousePressed)
scrollbar->mouseMoved(mouseEvent); // Handle hover effects on platforms that support visual feedback on scrollbar hovering.
if (FrameView* view = m_frame->view()) {
OptionalCursor optionalCursor = selectCursor(mev, scrollbar);
OptionalCursor optionalCursor = selectCursor(mev.hitTestResult(), mouseEvent.shiftKey());
if (optionalCursor.isCursorChange()) {
m_currentMouseCursor = optionalCursor.cursor();
view->setCursor(m_currentMouseCursor);
......@@ -2998,6 +3052,12 @@ void EventHandler::scheduleHoverStateUpdate()
m_hoverTimer.startOneShot(0);
}
void EventHandler::scheduleCursorUpdate()
{
if (!m_cursorUpdateTimer.isActive())
m_cursorUpdateTimer.startOneShot(cursorUpdateInterval);
}
void EventHandler::dispatchFakeMouseMoveEventSoon()
{
if (m_mousePressed)
......
......@@ -147,6 +147,7 @@ public:
#endif
void scheduleHoverStateUpdate();
void scheduleCursorUpdate();
void setResizingFrameSet(HTMLFrameSetElement*);
......@@ -254,6 +255,7 @@ public:
#endif
bool useHandCursor(Node*, bool isOverLink, bool shiftKey);
void updateCursor();
private:
#if ENABLE(DRAG_SUPPORT)
......@@ -280,8 +282,10 @@ private:
#endif
bool handleMouseReleaseEvent(const MouseEventWithHitTestResults&);
OptionalCursor selectCursor(const MouseEventWithHitTestResults&, Scrollbar*);
OptionalCursor selectCursor(const HitTestResult&, bool shiftKey);
void hoverTimerFired(Timer<EventHandler>*);
void cursorUpdateTimerFired(Timer<EventHandler>*);
bool logicalScrollOverflow(ScrollLogicalDirection, ScrollGranularity, Node* startingNode = 0);
......@@ -416,6 +420,7 @@ private:
bool m_panScrollButtonPressed;
Timer<EventHandler> m_hoverTimer;
Timer<EventHandler> m_cursorUpdateTimer;
OwnPtr<AutoscrollController> m_autoscrollController;
bool m_mouseDownMayStartAutoscroll;
......
......@@ -1624,6 +1624,12 @@ IntPoint FrameView::lastKnownMousePosition() const
return m_frame ? m_frame->eventHandler()->lastKnownMousePosition() : IntPoint();
}
bool FrameView::shouldSetCursor() const
{
Page* page = frame()->page();
return page && page->isOnscreen() && page->focusController()->isActive();
}
bool FrameView::scrollContentsFastPath(const IntSize& scrollDelta, const IntRect& rectToScroll, const IntRect& clipRect)
{
if (!m_viewportConstrainedObjects || m_viewportConstrainedObjects->isEmpty()) {
......
......@@ -340,6 +340,7 @@ public:
static void setRepaintThrottlingDeferredRepaintDelayIncrementDuringLoading(double p);
virtual IntPoint lastKnownMousePosition() const;
bool shouldSetCursor() const;
virtual bool scrollbarsCanBeActive() const OVERRIDE;
......
......@@ -35,7 +35,7 @@ MouseEventWithHitTestResults::MouseEventWithHitTestResults(const PlatformMouseEv
bool MouseEventWithHitTestResults::isOverLink() const
{
return m_hitTestResult.URLElement() && m_hitTestResult.URLElement()->isLink();
return m_hitTestResult.isOverLink();
}
}
......@@ -512,6 +512,11 @@ bool HitTestResult::isLiveLink() const
return false;
}
bool HitTestResult::isOverLink() const
{
return m_innerURLElement && m_innerURLElement->isLink();
}
String HitTestResult::titleDisplayString() const
{
if (!m_innerURLElement)
......
......@@ -108,6 +108,7 @@ public:
KURL absoluteLinkURL() const;
String textContent() const;
bool isLiveLink() const;
bool isOverLink() const;
bool isContentEditable() const;
void toggleMediaControlsDisplay() const;
void toggleMediaLoopPlayback() const;
......
......@@ -2003,7 +2003,7 @@ void RenderObject::styleDidChange(StyleDifference diff, const RenderStyle* oldSt
if (oldStyle && !areCursorsEqual(oldStyle, style())) {
if (Frame* frame = this->frame())
frame->eventHandler()->dispatchFakeMouseMoveEventSoon();
frame->eventHandler()->scheduleCursorUpdate();
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment