Skip to content

Commit

Permalink
Give blink a chance to consume ctrl+wheel events before zooming
Browse files Browse the repository at this point in the history
Today chromium consumes all mouse wheel events when the control key is pressed for the purposes of browser zoom (except on MacOS).  With this change we give the web page a chance to get and consume the wheel event first, in case it wants to override the behavior.  This makes Chrome consistent with IE and Firefox on Windows.  See http://crbug.com/289887 and the linked chromium-dev thread for discussion.

Apparently there were no tests at all for this functionality.  I've added new unit tests at RenderWidgetHost and WebContents layers.

Depends on blink change http://src.chromium.org/viewvc/blink?view=rev&rev=168088

BUG=289887

Review URL: https://codereview.chromium.org/177213016

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@254559 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
rbyers@chromium.org committed Mar 3, 2014
1 parent 5f6fe59 commit d92026e
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ bool RenderWidgetHostDelegate::PreHandleKeyboardEvent(
return false;
}

bool RenderWidgetHostDelegate::PreHandleWheelEvent(
bool RenderWidgetHostDelegate::HandleWheelEvent(
const blink::WebMouseWheelEvent& event) {
return false;
}
Expand Down
8 changes: 4 additions & 4 deletions content/browser/renderer_host/render_widget_host_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ class CONTENT_EXPORT RenderWidgetHostDelegate {
// event (used for keyboard shortcuts).
virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {}

// Callback to give the browser a chance to handle the specified mouse wheel
// event before sending it to the renderer.
// Returns true if the |event| was handled.
virtual bool PreHandleWheelEvent(const blink::WebMouseWheelEvent& event);
// Callback to inform the browser that the renderer did not process the
// specified mouse wheel event. Returns true if the browser has handled
// the event itself.
virtual bool HandleWheelEvent(const blink::WebMouseWheelEvent& event);

// Callback to give the browser a chance to handle the specified gesture
// event before sending it to the renderer.
Expand Down
9 changes: 4 additions & 5 deletions content/browser/renderer_host/render_widget_host_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -973,9 +973,6 @@ void RenderWidgetHostImpl::ForwardWheelEventWithLatencyInfo(
if (IgnoreInputEvents())
return;

if (delegate_->PreHandleWheelEvent(wheel_event))
return;

input_router_->SendWheelEvent(MouseWheelEventWithLatencyInfo(wheel_event,
latency_info));
}
Expand Down Expand Up @@ -2062,8 +2059,10 @@ void RenderWidgetHostImpl::OnWheelEventAck(
}

const bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result);
if (!processed && !is_hidden() && view_)
view_->UnhandledWheelEvent(wheel_event.event);
if (!processed && !is_hidden() && view_) {
if (!delegate_->HandleWheelEvent(wheel_event.event))
view_->UnhandledWheelEvent(wheel_event.event);
}
}

void RenderWidgetHostImpl::OnGestureEventAck(
Expand Down
51 changes: 50 additions & 1 deletion content/browser/renderer_host/render_widget_host_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ class TestView : public TestRenderWidgetHostView {
public:
explicit TestView(RenderWidgetHostImpl* rwh)
: TestRenderWidgetHostView(rwh),
unhandled_wheel_event_count_(0),
acked_event_count_(0),
gesture_event_type_(-1),
use_fake_physical_backing_size_(false),
Expand All @@ -450,6 +451,9 @@ class TestView : public TestRenderWidgetHostView {
const WebMouseWheelEvent& unhandled_wheel_event() const {
return unhandled_wheel_event_;
}
int unhandled_wheel_event_count() const {
return unhandled_wheel_event_count_;
}
int gesture_event_type() const { return gesture_event_type_; }
InputEventAckState ack_result() const { return ack_result_; }

Expand All @@ -471,6 +475,7 @@ class TestView : public TestRenderWidgetHostView {
++acked_event_count_;
}
virtual void UnhandledWheelEvent(const WebMouseWheelEvent& event) OVERRIDE {
unhandled_wheel_event_count_++;
unhandled_wheel_event_ = event;
}
virtual void GestureEventAck(const WebGestureEvent& event,
Expand All @@ -486,6 +491,7 @@ class TestView : public TestRenderWidgetHostView {

protected:
WebMouseWheelEvent unhandled_wheel_event_;
int unhandled_wheel_event_count_;
WebTouchEvent acked_event_;
int acked_event_count_;
int gesture_event_type_;
Expand All @@ -506,7 +512,9 @@ class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
prehandle_keyboard_event_called_(false),
prehandle_keyboard_event_type_(WebInputEvent::Undefined),
unhandled_keyboard_event_called_(false),
unhandled_keyboard_event_type_(WebInputEvent::Undefined) {
unhandled_keyboard_event_type_(WebInputEvent::Undefined),
handle_wheel_event_(false),
handle_wheel_event_called_(false) {
}
virtual ~MockRenderWidgetHostDelegate() {}

Expand All @@ -532,6 +540,14 @@ class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
prehandle_keyboard_event_ = handle;
}

void set_handle_wheel_event(bool handle) {
handle_wheel_event_ = handle;
}

bool handle_wheel_event_called() {
return handle_wheel_event_called_;
}

protected:
virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
bool* is_keyboard_shortcut) OVERRIDE {
Expand All @@ -546,13 +562,22 @@ class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
unhandled_keyboard_event_called_ = true;
}

virtual bool HandleWheelEvent(
const blink::WebMouseWheelEvent& event) OVERRIDE {
handle_wheel_event_called_ = true;
return handle_wheel_event_;
}

private:
bool prehandle_keyboard_event_;
bool prehandle_keyboard_event_called_;
WebInputEvent::Type prehandle_keyboard_event_type_;

bool unhandled_keyboard_event_called_;
WebInputEvent::Type unhandled_keyboard_event_type_;

bool handle_wheel_event_;
bool handle_wheel_event_called_;
};

// RenderWidgetHostTest --------------------------------------------------------
Expand Down Expand Up @@ -1150,9 +1175,33 @@ TEST_F(RenderWidgetHostTest, UnhandledWheelEvent) {
// Send the simulated response from the renderer back.
SendInputEventACK(WebInputEvent::MouseWheel,
INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
EXPECT_TRUE(delegate_->handle_wheel_event_called());
EXPECT_EQ(1, view_->unhandled_wheel_event_count());
EXPECT_EQ(-5, view_->unhandled_wheel_event().deltaX);
}

TEST_F(RenderWidgetHostTest, HandleWheelEvent) {
// Indicate that we're going to handle this wheel event
delegate_->set_handle_wheel_event(true);

SimulateWheelEvent(-5, 0, 0, true);

// Make sure we sent the input event to the renderer.
EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
InputMsg_HandleInputEvent::ID));
process_->sink().ClearMessages();

// Send the simulated response from the renderer back.
SendInputEventACK(WebInputEvent::MouseWheel,
INPUT_EVENT_ACK_STATE_NOT_CONSUMED);

// ensure the wheel event handler was invoked
EXPECT_TRUE(delegate_->handle_wheel_event_called());

// and that it suppressed the unhandled wheel event handler.
EXPECT_EQ(0, view_->unhandled_wheel_event_count());
}

TEST_F(RenderWidgetHostTest, UnhandledGestureEvent) {
SimulateGestureEvent(WebInputEvent::GestureTwoFingerTap,
WebGestureEvent::Touchscreen);
Expand Down
3 changes: 1 addition & 2 deletions content/browser/web_contents/web_contents_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1164,7 +1164,7 @@ void WebContentsImpl::HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {
delegate_->HandleKeyboardEvent(this, event);
}

bool WebContentsImpl::PreHandleWheelEvent(
bool WebContentsImpl::HandleWheelEvent(
const blink::WebMouseWheelEvent& event) {
#if !defined(OS_MACOSX)
// On platforms other than Mac, control+mousewheel changes zoom. On Mac, this
Expand All @@ -1180,7 +1180,6 @@ bool WebContentsImpl::PreHandleWheelEvent(
return true;
}
#endif

return false;
}

Expand Down
2 changes: 1 addition & 1 deletion content/browser/web_contents/web_contents_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ class CONTENT_EXPORT WebContentsImpl
bool* is_keyboard_shortcut) OVERRIDE;
virtual void HandleKeyboardEvent(
const NativeWebKeyboardEvent& event) OVERRIDE;
virtual bool PreHandleWheelEvent(
virtual bool HandleWheelEvent(
const blink::WebMouseWheelEvent& event) OVERRIDE;
virtual bool PreHandleGestureEvent(
const blink::WebGestureEvent& event) OVERRIDE;
Expand Down
91 changes: 91 additions & 0 deletions content/browser/web_contents/web_contents_impl_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
#include "content/browser/site_instance_impl.h"
#include "content/browser/webui/web_ui_controller_factory_registry.h"
#include "content/common/frame_messages.h"
#include "content/common/input/synthetic_web_input_event_builders.h"
#include "content/common/view_messages.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/interstitial_page_delegate.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_ui_controller.h"
#include "content/public/common/bindings_policy.h"
Expand Down Expand Up @@ -2232,4 +2234,93 @@ TEST_F(WebContentsImplTest, GetLastActiveTime) {
EXPECT_FALSE(contents()->GetLastActiveTime().is_null());
}

class ContentsZoomChangedDelegate : public WebContentsDelegate {
public:
ContentsZoomChangedDelegate() :
contents_zoom_changed_call_count_(0),
last_zoom_in_(false) {
}

int GetAndResetContentsZoomChangedCallCount() {
int count = contents_zoom_changed_call_count_;
contents_zoom_changed_call_count_ = 0;
return count;
}

bool last_zoom_in() const {
return last_zoom_in_;
}

// WebContentsDelegate:
virtual void ContentsZoomChange(bool zoom_in) OVERRIDE {
contents_zoom_changed_call_count_++;
last_zoom_in_ = zoom_in;
}

private:
int contents_zoom_changed_call_count_;
bool last_zoom_in_;

DISALLOW_COPY_AND_ASSIGN(ContentsZoomChangedDelegate);
};

// Tests that some mouseehweel events get turned into browser zoom requests.
TEST_F(WebContentsImplTest, HandleWheelEvent) {
using blink::WebInputEvent;

scoped_ptr<ContentsZoomChangedDelegate> delegate(
new ContentsZoomChangedDelegate());
contents()->SetDelegate(delegate.get());

int modifiers = 0;
float dy = 1;
// Verify that normal mouse wheel events do nothing to change the zoom level.
blink::WebMouseWheelEvent event =
SyntheticWebMouseWheelEventBuilder::Build(0, dy, modifiers, false);
EXPECT_FALSE(contents()->HandleWheelEvent(event));
EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());

modifiers = WebInputEvent::ShiftKey | WebInputEvent::AltKey;
event = SyntheticWebMouseWheelEventBuilder::Build(0, dy, modifiers, false);
EXPECT_FALSE(contents()->HandleWheelEvent(event));
EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());

// But whenever the ctrl modifier is applied, they can increase/decrease zoom.
// Except on MacOS where we never want to adjust zoom with mousewheel.
modifiers = WebInputEvent::ControlKey;
event = SyntheticWebMouseWheelEventBuilder::Build(0, dy, modifiers, false);
bool handled = contents()->HandleWheelEvent(event);
#if defined(OS_MACOSX)
EXPECT_FALSE(handled);
EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
#else
EXPECT_TRUE(handled);
EXPECT_EQ(1, delegate->GetAndResetContentsZoomChangedCallCount());
EXPECT_TRUE(delegate->last_zoom_in());
#endif

modifiers = WebInputEvent::ControlKey | WebInputEvent::ShiftKey |
WebInputEvent::AltKey;
dy = -5;
event = SyntheticWebMouseWheelEventBuilder::Build(2, dy, modifiers, false);
handled = contents()->HandleWheelEvent(event);
#if defined(OS_MACOSX)
EXPECT_FALSE(handled);
EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
#else
EXPECT_TRUE(handled);
EXPECT_EQ(1, delegate->GetAndResetContentsZoomChangedCallCount());
EXPECT_FALSE(delegate->last_zoom_in());
#endif

// Unless there is no vertical movement.
dy = 0;
event = SyntheticWebMouseWheelEventBuilder::Build(2, dy, modifiers, false);
EXPECT_FALSE(contents()->HandleWheelEvent(event));
EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());

// Ensure pointers to the delegate aren't kept beyond it's lifetime.
contents()->SetDelegate(NULL);
}

} // namespace content
5 changes: 5 additions & 0 deletions content/renderer/input/input_handler_proxy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleInputEvent(
// thread, so punt it to the main thread. http://crbug.com/236639
return DID_NOT_HANDLE;
}
if (wheel_event.modifiers & WebInputEvent::ControlKey) {
// Wheel events involving the control key never trigger scrolling, only
// event handlers. Forward to the main thread.
return DID_NOT_HANDLE;
}
cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin(
gfx::Point(wheel_event.x, wheel_event.y), cc::InputHandler::Wheel);
switch (scroll_status) {
Expand Down
27 changes: 21 additions & 6 deletions content/renderer/input/input_handler_proxy_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,16 @@ TEST_F(InputHandlerProxyTest, MouseWheelByPageMainThread) {
testing::Mock::VerifyAndClearExpectations(&mock_client_);
}

TEST_F(InputHandlerProxyTest, MouseWheelWithCtrl) {
expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE;
WebMouseWheelEvent wheel;
wheel.type = WebInputEvent::MouseWheel;
wheel.modifiers = WebInputEvent::ControlKey;

EXPECT_EQ(expected_disposition_, input_handler_->HandleInputEvent(wheel));
testing::Mock::VerifyAndClearExpectations(&mock_client_);
}

TEST_F(InputHandlerProxyTest, GestureScrollStarted) {
// We shouldn't send any events to the widget for this gesture.
expected_disposition_ = InputHandlerProxy::DID_HANDLE;
Expand Down Expand Up @@ -468,7 +478,9 @@ TEST_F(InputHandlerProxyTest, GestureFlingAnimatesTouchpad) {
WebFloatPoint fling_delta = WebFloatPoint(1000, 0);
WebPoint fling_point = WebPoint(7, 13);
WebPoint fling_global_point = WebPoint(17, 23);
int modifiers = 7;
// Note that for trackpad, wheel events with the Control modifier are
// special (reserved for zoom), so don't set that here.
int modifiers = WebInputEvent::ShiftKey | WebInputEvent::AltKey;
gesture_.data.flingStart.velocityX = fling_delta.x;
gesture_.data.flingStart.velocityY = fling_delta.y;
gesture_.sourceDevice = WebGestureEvent::Touchpad;
Expand Down Expand Up @@ -576,7 +588,9 @@ TEST_F(InputHandlerProxyTest, GestureFlingTransferResetsTouchpad) {
WebFloatPoint fling_delta = WebFloatPoint(1000, 0);
WebPoint fling_point = WebPoint(7, 13);
WebPoint fling_global_point = WebPoint(17, 23);
int modifiers = 1;
// Note that for trackpad, wheel events with the Control modifier are
// special (reserved for zoom), so don't set that here.
int modifiers = WebInputEvent::ShiftKey | WebInputEvent::AltKey;
gesture_.data.flingStart.velocityX = fling_delta.x;
gesture_.data.flingStart.velocityY = fling_delta.y;
gesture_.sourceDevice = WebGestureEvent::Touchpad;
Expand Down Expand Up @@ -682,7 +696,7 @@ TEST_F(InputHandlerProxyTest, GestureFlingTransferResetsTouchpad) {
fling_delta = WebFloatPoint(0, -1000);
fling_point = WebPoint(95, 87);
fling_global_point = WebPoint(32, 71);
modifiers = 2;
modifiers = WebInputEvent::AltKey;
gesture_.data.flingStart.velocityX = fling_delta.x;
gesture_.data.flingStart.velocityY = fling_delta.y;
gesture_.sourceDevice = WebGestureEvent::Touchpad;
Expand Down Expand Up @@ -861,7 +875,8 @@ TEST_F(InputHandlerProxyTest, GestureFlingAnimatesTouchscreen) {
WebFloatPoint fling_delta = WebFloatPoint(1000, 0);
WebPoint fling_point = WebPoint(7, 13);
WebPoint fling_global_point = WebPoint(17, 23);
int modifiers = 7;
// Note that for touchscreen the control modifier is not special.
int modifiers = WebInputEvent::ControlKey;
gesture_.data.flingStart.velocityX = fling_delta.x;
gesture_.data.flingStart.velocityY = fling_delta.y;
gesture_.sourceDevice = WebGestureEvent::Touchscreen;
Expand Down Expand Up @@ -924,7 +939,7 @@ TEST_F(InputHandlerProxyTest, GestureFlingWithValidTimestamp) {
WebFloatPoint fling_delta = WebFloatPoint(1000, 0);
WebPoint fling_point = WebPoint(7, 13);
WebPoint fling_global_point = WebPoint(17, 23);
int modifiers = 7;
int modifiers = WebInputEvent::ControlKey;
gesture_.timeStampSeconds = startTimeOffset.InSecondsF();
gesture_.data.flingStart.velocityX = fling_delta.x;
gesture_.data.flingStart.velocityY = fling_delta.y;
Expand Down Expand Up @@ -985,7 +1000,7 @@ TEST_F(InputHandlerProxyTest,
WebFloatPoint fling_delta = WebFloatPoint(1000, 0);
WebPoint fling_point = WebPoint(7, 13);
WebPoint fling_global_point = WebPoint(17, 23);
int modifiers = 7;
int modifiers = WebInputEvent::ControlKey | WebInputEvent::AltKey;
gesture_.data.flingStart.velocityX = fling_delta.x;
gesture_.data.flingStart.velocityY = fling_delta.y;
gesture_.sourceDevice = WebGestureEvent::Touchscreen;
Expand Down

0 comments on commit d92026e

Please sign in to comment.