Skip to content

Commit

Permalink
Make preventDefault() from onmousewheel disable two-finger back/forwa…
Browse files Browse the repository at this point in the history
…rd navigation on OS X Lion.

The code to trigger back/forward navigation from mouse wheel events is currently broken. It triggers on all unhandled wheel events including events with a delta of zero, even though JavaScript never gets a chance to handle them (WheelEventDispatchMediator ignores them). This CL fixes that by only reporting wheel events with non-zero deltas as unhandled. It also fixes a bug where the wrong wheel event is reported as unhandled if there are additional wheel events in the queue.

BUG=239731
TEST=Visit http://jsfiddle.net/Qs3JJ/, horizontal scroll with two fingers on trackpad, no history navigation should happen.

Review URL: https://chromiumcodereview.appspot.com/14795004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@199796 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
evan.exe@gmail.com committed May 13, 2013
1 parent 7a4a76f commit bcdc898
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 9 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,4 @@ Bem Jones-Bey <bjonesbe@adobe.com>
Aditya Bhargava <heuristicist@gmail.com>
John Yani <vanuan@gmail.com>
Kangil Han <kangil.han@samsung.com>
Evan Wallace <evan.exe@gmail.com>
9 changes: 6 additions & 3 deletions content/browser/renderer_host/render_widget_host_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1949,6 +1949,12 @@ void RenderWidgetHostImpl::ProcessWheelAck(bool processed) {
if (overscroll_controller_)
overscroll_controller_->ReceivedEventACK(current_wheel_event_, processed);

// Process the unhandled wheel event here before calling
// ForwardWheelEventWithLatencyInfo() since it will mutate
// current_wheel_event_.
if (!processed && !is_hidden_ && view_)
view_->UnhandledWheelEvent(current_wheel_event_);

// Now send the next (coalesced) mouse wheel event.
if (!coalesced_mouse_wheel_events_.empty()) {
MouseWheelEventWithLatencyInfo next_wheel_event =
Expand All @@ -1957,9 +1963,6 @@ void RenderWidgetHostImpl::ProcessWheelAck(bool processed) {
ForwardWheelEventWithLatencyInfo(next_wheel_event.event,
next_wheel_event.latency);
}

if (!processed && !is_hidden_ && view_)
view_->UnhandledWheelEvent(current_wheel_event_);
}

void RenderWidgetHostImpl::ProcessGestureAck(bool processed, int type) {
Expand Down
35 changes: 35 additions & 0 deletions content/browser/renderer_host/render_widget_host_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,10 @@ class TestView : public TestRenderWidgetHostView {
acked_event_count_ = 0;
}

const WebMouseWheelEvent& unhandled_wheel_event() const {
return unhandled_wheel_event_;
}

// RenderWidgetHostView override.
virtual gfx::Rect GetViewBounds() const OVERRIDE {
return bounds_;
Expand All @@ -516,8 +520,12 @@ class TestView : public TestRenderWidgetHostView {
acked_event_ = touch;
++acked_event_count_;
}
virtual void UnhandledWheelEvent(const WebMouseWheelEvent& event) OVERRIDE {
unhandled_wheel_event_ = event;
}

protected:
WebMouseWheelEvent unhandled_wheel_event_;
WebTouchEvent acked_event_;
int acked_event_count_;
gfx::Rect bounds_;
Expand Down Expand Up @@ -3816,4 +3824,31 @@ TEST_F(RenderWidgetHostTest, OverscrollDirectionChange) {
EXPECT_EQ(OVERSCROLL_NONE, host_->overscroll_mode());
}

TEST_F(RenderWidgetHostTest, UnhandledWheelEvent) {
process_->sink().ClearMessages();

// Simulate wheel events.
SimulateWheelEvent(0, -5, 0, false); // sent directly
SimulateWheelEvent(0, -10, 0, false); // enqueued

// Check that only the first event was sent.
EXPECT_EQ(1U, process_->sink().message_count());
EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
InputMsg_HandleInputEvent::ID));
process_->sink().ClearMessages();

// Indicate that the wheel event was unhandled.
SendInputEventACK(WebInputEvent::MouseWheel,
INPUT_EVENT_ACK_STATE_NOT_CONSUMED);

// Check that the second event was sent.
EXPECT_EQ(1U, process_->sink().message_count());
EXPECT_TRUE(process_->sink().GetUniqueMessageMatching(
InputMsg_HandleInputEvent::ID));
process_->sink().ClearMessages();

// Check that the correct unhandled wheel event was received.
EXPECT_EQ(view_->unhandled_wheel_event().deltaY, -5);
}

} // namespace content
3 changes: 2 additions & 1 deletion content/browser/renderer_host/render_widget_host_view_mac.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ class RenderWidgetHostViewMac : public RenderWidgetHostViewBase,

RenderWidgetHostViewCocoa* cocoa_view() const { return cocoa_view_; }

void SetDelegate(NSObject<RenderWidgetHostViewMacDelegate>* delegate);
CONTENT_EXPORT void SetDelegate(
NSObject<RenderWidgetHostViewMacDelegate>* delegate);
void SetAllowOverlappingViews(bool overlapping);

// RenderWidgetHostView implementation.
Expand Down
5 changes: 4 additions & 1 deletion content/browser/renderer_host/render_widget_host_view_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1423,7 +1423,10 @@ void DisablePasswordInput() {

void RenderWidgetHostViewMac::UnhandledWheelEvent(
const WebKit::WebMouseWheelEvent& event) {
[cocoa_view_ gotUnhandledWheelEvent];
// Only record a wheel event as unhandled if JavaScript handlers got a chance
// to see it (no-op wheel events are ignored by the event dispatcher)
if (event.deltaX || event.deltaY)
[cocoa_view_ gotUnhandledWheelEvent];
}

bool RenderWidgetHostViewMac::Send(IPC::Message* message) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "content/common/input_messages.h"
#include "content/common/view_messages.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_widget_host_view_mac_delegate.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_utils.h"
Expand Down Expand Up @@ -48,6 +49,7 @@ @interface MockPhaseMethods : NSObject {
}

- (NSEventPhase)phaseBegan;
- (NSEventPhase)phaseChanged;
- (NSEventPhase)phaseEnded;
@end

Expand All @@ -56,12 +58,35 @@ @implementation MockPhaseMethods
- (NSEventPhase)phaseBegan {
return NSEventPhaseBegan;
}
- (NSEventPhase)phaseChanged {
return NSEventPhaseChanged;
}
- (NSEventPhase)phaseEnded {
return NSEventPhaseEnded;
}

@end

@interface MockRenderWidgetHostViewMacDelegate
: NSObject<RenderWidgetHostViewMacDelegate> {
BOOL unhandledWheelEventReceived_;
}

@property(nonatomic) BOOL unhandledWheelEventReceived;

- (void)gotUnhandledWheelEvent;
@end

@implementation MockRenderWidgetHostViewMacDelegate

@synthesize unhandledWheelEventReceived = unhandledWheelEventReceived_;

- (void)gotUnhandledWheelEvent {
unhandledWheelEventReceived_ = true;
}

@end

namespace content {

namespace {
Expand Down Expand Up @@ -127,9 +152,9 @@ void GenerateCompositionRectArray(const gfx::Point& origin,

// Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
// correspond to a method in |MockPhaseMethods| that returns the desired phase.
NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector) {
NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
CGEventRef cg_event =
CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, 0, 0);
CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
CFRelease(cg_event);
method_setImplementation(
Expand Down Expand Up @@ -681,7 +706,7 @@ WindowedNotificationObserver observer(
RenderWidgetHostView::CreateViewForWidget(host));

// Send an initial wheel event with NSEventPhaseBegan to the view.
NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan));
NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0);
[view->cocoa_view() scrollWheel:event1];
ASSERT_EQ(1U, process_host->sink().message_count());

Expand All @@ -692,7 +717,7 @@ WindowedNotificationObserver observer(

// Post the NSEventPhaseEnded wheel event to NSApp and check whether the
// render view receives it.
NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded));
NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
[NSApp postEvent:event2 atStart:NO];
base::MessageLoop::current()->RunUntilIdle();
ASSERT_EQ(2U, process_host->sink().message_count());
Expand All @@ -701,4 +726,58 @@ WindowedNotificationObserver observer(
host->Shutdown();
}

TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
// This tests Lion+ functionality, so don't run the test pre-Lion.
if (!base::mac::IsOSLionOrLater())
return;

// Initialize the view associated with a MockRenderWidgetHostImpl, rather than
// the MockRenderProcessHost that is set up by the test harness which mocks
// out |OnMessageReceived()|.
TestBrowserContext browser_context;
MockRenderProcessHost* process_host =
new MockRenderProcessHost(&browser_context);
MockRenderWidgetHostDelegate delegate;
MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
&delegate, process_host, MSG_ROUTING_NONE);
RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
RenderWidgetHostView::CreateViewForWidget(host));

// Add a delegate to the view.
scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
[[MockRenderWidgetHostViewMacDelegate alloc] init]);
view->SetDelegate(view_delegate.get());

// Send an initial wheel event for scrolling by 3 lines.
NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3);
[view->cocoa_view() scrollWheel:event1];
ASSERT_EQ(1U, process_host->sink().message_count());
process_host->sink().ClearMessages();

// Indicate that the wheel event was unhandled.
scoped_ptr<IPC::Message> response1(new InputHostMsg_HandleInputEvent_ACK(0,
WebKit::WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_NOT_CONSUMED));
host->OnMessageReceived(*response1);

// Check that the view delegate got an unhandled wheel event.
ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
view_delegate.get().unhandledWheelEventReceived = NO;

// Send another wheel event, this time for scrolling by 0 lines (empty event).
NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
[view->cocoa_view() scrollWheel:event2];
ASSERT_EQ(1U, process_host->sink().message_count());

// Indicate that the wheel event was also unhandled.
scoped_ptr<IPC::Message> response2(new InputHostMsg_HandleInputEvent_ACK(0,
WebKit::WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_NOT_CONSUMED));
host->OnMessageReceived(*response2);

// Check that the view delegate ignored the empty unhandled wheel event.
ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);

// Clean up.
host->Shutdown();
}

} // namespace content

0 comments on commit bcdc898

Please sign in to comment.