// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <Cocoa/Cocoa.h>
#include "base/apple/scoped_cftyperef.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/test/simple_test_tick_clock.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/ocmock_extensions.h"
#include "ui/events/base_event_utils.h"
#include "url/gurl.h"
namespace {
// Refers to how the event is going to be sent to the NSView. There are 3
// relevant sets of APIs. The current code relies on all three sets of APIs.
// There is significant information duplication between the three sets of APIs,
// but the timing of the callbacks of the three APIs differ significantly.
enum Deployment {
// -[NSView touchesBeganWithEvent:]
DEPLOYMENT_TOUCHES_BEGAN,
// -[NSView touchesMovedWithEvent:]
DEPLOYMENT_TOUCHES_MOVED,
// -[NSView touchesEndedWithEvent:]
DEPLOYMENT_TOUCHES_ENDED,
// -[NSView scrollWheel:]
DEPLOYMENT_SCROLL_WHEEL,
// -[NSView beginGestureWithEvent:]
DEPLOYMENT_GESTURE_BEGIN,
// -[NSView endGestureWithEvent:]
DEPLOYMENT_GESTURE_END,
};
} // namespace
// A wrapper object for events queued for replay.
@interface QueuedEvent : NSObject
// Whether the message loop should be run after this event has been replayed.
@property(nonatomic, assign) BOOL runMessageLoop;
// How this event should be replayed.
@property(nonatomic, assign) Deployment deployment;
// The event to be replayed.
@property(nonatomic, strong) NSEvent* event;
@end
@implementation QueuedEvent
@synthesize deployment = _deployment;
@synthesize event = _event;
@synthesize runMessageLoop = _runMessageLoop;
@end
class ChromeRenderWidgetHostViewMacHistorySwiperTest
: public InProcessBrowserTest {
public:
ChromeRenderWidgetHostViewMacHistorySwiperTest()
: event_queue_(), touch_(CGPointMake(0, 0)) {
const base::FilePath base_path(FILE_PATH_LITERAL("scroll"));
url1_ = ui_test_utils::GetTestUrl(
base_path, base::FilePath(FILE_PATH_LITERAL("text.html")));
url2_ = ui_test_utils::GetTestUrl(
base_path, base::FilePath(FILE_PATH_LITERAL("blank.html")));
url_iframe_ = ui_test_utils::GetTestUrl(
base_path, base::FilePath(FILE_PATH_LITERAL("iframe.html")));
}
ChromeRenderWidgetHostViewMacHistorySwiperTest(
const ChromeRenderWidgetHostViewMacHistorySwiperTest&) = delete;
ChromeRenderWidgetHostViewMacHistorySwiperTest& operator=(
const ChromeRenderWidgetHostViewMacHistorySwiperTest&) = delete;
void SetUpOnMainThread() override {
event_queue_ = [[NSMutableArray alloc] init];
touch_ = CGPointMake(0.5, 0.5);
// Ensure that the navigation stack is not empty.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1_));
ASSERT_EQ(url1_, GetWebContents()->GetURL());
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url2_));
ASSERT_EQ(url2_, GetWebContents()->GetURL());
mock_clock_.Advance(base::Milliseconds(100));
ui::SetEventTickClockForTesting(&mock_clock_);
}
void TearDownOnMainThread() override {
ui::SetEventTickClockForTesting(nullptr);
event_queue_ = nil;
}
protected:
// Returns the active web contents.
content::WebContents* GetWebContents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
// Returns the value of |query| from Javascript as an int.
int GetScriptIntValue(const std::string& query) {
return content::EvalJs(GetWebContents(), query).ExtractInt();
}
// Returns the vertical scroll offset of the current page.
int GetScrollTop() {
return GetScriptIntValue("document.body.scrollTop");
}
// Create mock events --------------------------------------------------------
// Create a gesture event with no useful data. Used to create Begin and End
// events.
id MockGestureEvent(NSEventType type) {
id event = [OCMockObject mockForClass:[NSEvent class]];
NSPoint locationInWindow = NSMakePoint(0, 0);
CGFloat deltaX = 0;
CGFloat deltaY = 0;
NSTimeInterval timestamp = 0;
NSUInteger modifierFlags = 0;
[(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
[(NSEvent*)[[event stub]
andReturnValue:OCMOCK_VALUE(locationInWindow)] locationInWindow];
[(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaX)] deltaX];
[(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaY)] deltaY];
[(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
[(NSEvent*)[[event stub]
andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
return event;
}
// Creates a mock scroll wheel event that is backed by a real CGEvent.
id MockScrollWheelEvent(NSPoint delta, NSEventType type) {
base::apple::ScopedCFTypeRef<CGEventRef> cg_event(
CGEventCreateScrollWheelEvent(nullptr, kCGScrollEventUnitLine, 2, 0,
0));
CGEventSetIntegerValueField(cg_event.get(), kCGScrollWheelEventIsContinuous,
1);
CGEventSetIntegerValueField(cg_event.get(),
kCGScrollWheelEventPointDeltaAxis2, delta.x);
CGEventSetIntegerValueField(cg_event.get(),
kCGScrollWheelEventPointDeltaAxis1, delta.y);
NSEvent* event = [NSEvent eventWithCGEvent:cg_event.get()];
id mock_event = [OCMockObject partialMockForObject:event];
[[[mock_event stub] andReturnBool:NO] isDirectionInvertedFromDevice];
NSTimeInterval timestamp = 0;
[(NSEvent*)[[mock_event stub]
andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
[(NSEvent*)[[mock_event stub] andReturnValue:OCMOCK_VALUE(type)] type];
// We need to assign a locationInWindow for the event so that the wheel
// event happens inside the page.
NSPoint locationInWindow = NSMakePoint(400, 400);
[(NSEvent*)[[mock_event stub] andReturnValue:OCMOCK_VALUE(locationInWindow)]
locationInWindow];
return mock_event;
}
// Returns a scroll wheel event with the given parameters.
id ScrollWheelEventWithPhase(NSEventPhase phase,
NSEventPhase momentum_phase,
CGFloat scrolling_delta_x,
CGFloat scrolling_delta_y) {
id event =
MockScrollWheelEvent(NSMakePoint(scrolling_delta_x, scrolling_delta_y),
NSEventTypeScrollWheel);
[(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(phase)] phase];
[(NSEvent*)[[event stub]
andReturnValue:OCMOCK_VALUE(momentum_phase)] momentumPhase];
[(NSEvent*)[[event stub]
andReturnValue:OCMOCK_VALUE(scrolling_delta_x)] scrollingDeltaX];
[(NSEvent*)[[event stub]
andReturnValue:OCMOCK_VALUE(scrolling_delta_y)] scrollingDeltaY];
NSUInteger modifierFlags = 0;
[(NSEvent*)[[event stub]
andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
NSTimeInterval timestamp = 0;
[(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
NSView* view = GetWebContents()
->GetRenderViewHost()
->GetWidget()
->GetView()
->GetNativeView()
.GetNativeNSView();
NSWindow* window = [view window];
[(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(window)] window];
return event;
}
// Queue events for playback -------------------------------------------------
void QueueEvent(id event, Deployment deployment, BOOL run_message_loop) {
QueuedEvent* queued_event = [[QueuedEvent alloc] init];
queued_event.event = event;
queued_event.deployment = deployment;
queued_event.runMessageLoop = run_message_loop;
[event_queue_ addObject:queued_event];
}
// Queues a trackpad scroll event (e.g. [NSView scrollWheel:])
void QueueTrackpadScroll(int dx,
int dy,
NSEventPhase phase,
BOOL run_message_loop) {
id event = ScrollWheelEventWithPhase(phase, NSEventPhaseNone, dx, dy);
QueueEvent(event, DEPLOYMENT_SCROLL_WHEEL, run_message_loop);
}
// Queues a gesture begin event (e.g. [NSView gestureDidBegin:])
void QueueGestureBegin() {
QueueEvent(MockGestureEvent(NSEventTypeBeginGesture),
DEPLOYMENT_GESTURE_BEGIN, NO);
}
// Queues a gesture end event (e.g. [NSView gestureDidEnd:])
void QueueGestureEnd() {
QueueEvent(MockGestureEvent(NSEventTypeEndGesture),
DEPLOYMENT_GESTURE_BEGIN, NO);
}
// Queues a touch event with absolute coordinates |x| and |y|.
void QueueTouch(CGFloat x,
CGFloat y,
Deployment deployment,
NSEventType type,
short subtype,
BOOL run_message_loop) {
id event = [OCMockObject mockForClass:[NSEvent class]];
[(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
[(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(subtype)] subtype];
id mock_touch = [OCMockObject mockForClass:[NSTouch class]];
[[[mock_touch stub] andReturnNSPoint:NSMakePoint(x, y)] normalizedPosition];
NSArray* touches = @[ mock_touch ];
[[[event stub] andReturn:touches] touchesMatchingPhase:NSTouchPhaseAny
inView:[OCMArg any]];
[[[event stub] andReturnBool:NO] isDirectionInvertedFromDevice];
NSTimeInterval timestamp = 0;
[(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
QueueEvent(event, deployment, run_message_loop);
}
// Convenience methods for event queuing -------------------------------------
// Trackpad scroll events are roughly related to touch events. Given a
// trackpad scroll delta, approximate the change to the touch event.
void UpdateTouchLocationFromTrackpadScroll(int dx, int dy) {
touch_.x -= dx * 0.001;
touch_.y -= dy * 0.001;
}
// Queue the typical events at the beginning of a new swipe gesture. The
// ordering and values were determined by recording real swipe events.
void QueueBeginningEvents(int dx, int dy) {
QueueTouch(DEPLOYMENT_TOUCHES_BEGAN, NSEventTypeGesture,
NSEventSubtypeMouseEvent, NO);
QueueTrackpadScroll(0, 0, NSEventPhaseMayBegin, YES);
QueueTouch(DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeMouseEvent, NO);
QueueTrackpadScroll(dx, dy, NSEventPhaseBegan, NO);
QueueGestureBegin();
QueueTouch(DEPLOYMENT_TOUCHES_MOVED, NSEventTypeBeginGesture,
NSEventSubtypeTouch, NO);
QueueTouch(DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
UpdateTouchLocationFromTrackpadScroll(dx, dy);
QueueTouch(DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, NO);
}
// Queue the typical events at the end of a new swipe gesture. The ordering
// and values were determined by recording real swipe events.
void QueueEndEvents() {
QueueTouch(DEPLOYMENT_TOUCHES_MOVED, NSEventTypeEndGesture,
NSEventSubtypeMouseEvent, NO);
QueueTouch(DEPLOYMENT_TOUCHES_ENDED, NSEventTypeEndGesture,
NSEventSubtypeMouseEvent, NO);
QueueGestureEnd();
QueueTrackpadScroll(0, 0, NSEventPhaseEnded, YES);
}
// Queues a trackpad scroll movement and a touch movement event.
void QueueScrollAndTouchMoved(int dx, int dy) {
QueueTrackpadScroll(dx, dy, NSEventPhaseChanged, NO);
UpdateTouchLocationFromTrackpadScroll(dx, dy);
QueueTouch(DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
}
// Queues a touch event with the stored touch coordinates.
void QueueTouch(Deployment deployment,
NSEventType type,
short subtype,
BOOL run_message_loop) {
QueueTouch(touch_.x, touch_.y, deployment, type, subtype, run_message_loop);
}
// Replays the events from the queue.
void RunQueuedEvents() {
while ([event_queue_ count] > 0) {
QueuedEvent* queued_event = [event_queue_ firstObject];
NSEvent* event = queued_event.event;
NSView* view = GetWebContents()
->GetRenderViewHost()
->GetWidget()
->GetView()
->GetNativeView()
.GetNativeNSView();
BOOL run_loop = queued_event.runMessageLoop;
switch (queued_event.deployment) {
case DEPLOYMENT_GESTURE_BEGIN:
[view beginGestureWithEvent:event];
break;
case DEPLOYMENT_GESTURE_END:
[view endGestureWithEvent:event];
break;
case DEPLOYMENT_SCROLL_WHEEL:
[view scrollWheel:event];
break;
case DEPLOYMENT_TOUCHES_BEGAN:
[view touchesBeganWithEvent:event];
break;
case DEPLOYMENT_TOUCHES_ENDED:
[view touchesEndedWithEvent:event];
break;
case DEPLOYMENT_TOUCHES_MOVED:
[view touchesMovedWithEvent:event];
break;
}
[event_queue_ removeObjectAtIndex:0];
if (!run_loop)
continue;
// Give time for the IPC to make it to the renderer process. If the IPC
// doesn't have time to make it to the renderer process, that's okay,
// since that simulates realistic conditions.
[[NSRunLoop currentRunLoop]
runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]];
// The renderer process returns an IPC, which needs to be handled.
base::RunLoop().RunUntilIdle();
}
}
void ExpectUrlAndOffset(const GURL& url, int offset) {
EXPECT_TRUE(content::WaitForLoadStop(GetWebContents()));
EXPECT_EQ(url, GetWebContents()->GetURL());
const int scroll_offset = GetScrollTop();
EXPECT_EQ(offset, scroll_offset);
}
base::SimpleTestTickClock mock_clock_;
GURL url1_;
GURL url2_;
GURL url_iframe_;
NSMutableArray* __strong event_queue_;
// The current location of the user's fingers on the track pad.
CGPoint touch_;
};
// The ordering, timing, and parameters of the events was determined by
// recording a real swipe.
IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
DISABLED_TestBackwardsHistoryNavigationRealData) {
QueueTouch(0.510681, 0.444672, DEPLOYMENT_TOUCHES_BEGAN, NSEventTypeGesture,
NSEventSubtypeMouseEvent, NO);
QueueTrackpadScroll(0, 0, NSEventPhaseMayBegin, YES);
QueueTouch(0.510681, 0.444672, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeMouseEvent, NO);
QueueTrackpadScroll(1, 0, NSEventPhaseBegan, NO);
QueueGestureBegin();
QueueTouch(0.510681, 0.444672, DEPLOYMENT_TOUCHES_MOVED,
NSEventTypeBeginGesture, NSEventSubtypeTouch, NO);
QueueTouch(0.510681, 0.444672, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTouch(0.507019, 0.444092, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, NO);
QueueTrackpadScroll(3, 0, NSEventPhaseChanged, YES);
QueueTrackpadScroll(3, -1, NSEventPhaseChanged, NO);
QueueTouch(0.502861, 0.443512, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(6, -1, NSEventPhaseChanged, NO);
QueueTouch(0.497002, 0.44294, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(5, -1, NSEventPhaseChanged, NO);
QueueTouch(0.487236, 0.44149, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(8, -1, NSEventPhaseChanged, NO);
QueueTouch(0.480392, 0.440628, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, NO);
QueueTouch(0.475266, 0.440338, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(6, -1, NSEventPhaseChanged, NO);
QueueTrackpadScroll(10, -1, NSEventPhaseChanged, NO);
QueueTouch(0.467934, 0.439758, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(6, -1, NSEventPhaseChanged, NO);
QueueTouch(0.462807, 0.439186, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(12, -1, NSEventPhaseChanged, NO);
QueueTouch(0.454018, 0.438316, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(6, -1, NSEventPhaseChanged, NO);
QueueTouch(0.449623, 0.438026, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(9, 0, NSEventPhaseChanged, NO);
QueueTouch(0.443275, 0.437744, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTouch(0.437164, 0.437164, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(9, -1, NSEventPhaseChanged, NO);
QueueTouch(0.431305, 0.436874, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(8, -1, NSEventPhaseChanged, NO);
QueueTouch(0.425926, 0.436295, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(7, -1, NSEventPhaseChanged, NO);
QueueTouch(0.420311, 0.43573, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(7, -1, NSEventPhaseChanged, NO);
QueueTouch(0.415184, 0.43544, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(6, -1, NSEventPhaseChanged, NO);
QueueTouch(0.410057, 0.43457, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTouch(0.40493, 0.43399, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(7, -1, NSEventPhaseChanged, YES);
QueueTrackpadScroll(3, -1, NSEventPhaseChanged, NO);
QueueTouch(0.402489, 0.433701, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(5, 0, NSEventPhaseChanged, NO);
QueueTouch(0.398094, 0.433418, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(4, -1, NSEventPhaseChanged, NO);
QueueTouch(0.394669, 0.433128, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTouch(0.391006, 0.432549, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTrackpadScroll(4, -1, NSEventPhaseChanged, NO);
QueueTrackpadScroll(5, 0, NSEventPhaseChanged, YES);
QueueTouch(0.386848, 0.432259, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
QueueTouch(0.38343, 0.432259, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeGesture,
NSEventSubtypeTouch, YES);
// Skipped a bunch of events. The data on the gesture end events are fudged.
QueueTouch(0.38343, 0.432259, DEPLOYMENT_TOUCHES_MOVED, NSEventTypeEndGesture,
NSEventSubtypeMouseEvent, NO);
QueueTouch(0.38343, 0.432259, DEPLOYMENT_TOUCHES_ENDED, NSEventTypeEndGesture,
NSEventSubtypeMouseEvent, NO);
QueueGestureEnd();
QueueTrackpadScroll(0, 0, NSEventPhaseEnded, YES);
RunQueuedEvents();
ExpectUrlAndOffset(url1_, 0);
}
// Each movement event that has non-zero parameters has both horizontal and
// vertical motion. This should not trigger history navigation.
// http://crbug.com/396328
IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
DISABLED_TestAllDiagonalSwipes) {
QueueBeginningEvents(1, -1);
for (int i = 0; i < 150; ++i)
QueueScrollAndTouchMoved(1, -1);
QueueEndEvents();
RunQueuedEvents();
ExpectUrlAndOffset(url2_, 150);
}
// Disabled for flakiness. crbug.com/378158
//
// The movements are equal part diagonal, horizontal, and vertical. This should
// not trigger history navigation.
IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
DISABLED_TestStaggeredDiagonalSwipe) {
QueueBeginningEvents(1, 0);
for (int i = 0; i < 150; ++i) {
switch (i % 3) {
case 0:
QueueScrollAndTouchMoved(1, -1);
break;
case 1:
QueueScrollAndTouchMoved(0, -1);
break;
case 2:
QueueScrollAndTouchMoved(1, 0);
break;
default:
NOTREACHED_IN_MIGRATION();
}
}
QueueEndEvents();
RunQueuedEvents();
EXPECT_TRUE(content::WaitForLoadStop(GetWebContents()));
EXPECT_EQ(url2_, GetWebContents()->GetURL());
// Depending on the timing of the IPCs, some of the initial events might be
// recognized as part of the history swipe, and not forwarded to the renderer,
// resulting in a non-deterministic scroll offset. This is bad, as some
// vertical motion is lost. Once the history swiper logic is fixed, this
// should become a direct comparison between 'scroll_offset' and 100.
// crbug.com/375514
const int scroll_offset = GetScrollTop();
// TODO(erikchen): Depending on the timing of the IPCs between Chrome and the
// renderer, more than 15% of the vertical motion can be lost. This assertion
// should eventually become an equality comparison against 100.
// crbug.com/378158
EXPECT_GT(scroll_offset, 1);
}
// The movement events are mostly in the horizontal direction, which should
// trigger a history swipe. This should trigger history navigation.
IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
DISABLED_TestMostlyHorizontal) {
QueueBeginningEvents(1, 1);
for (int i = 0; i < 150; ++i) {
if (i % 10 == 0) {
QueueScrollAndTouchMoved(0, -1);
} else if (i % 5 == 0) {
QueueScrollAndTouchMoved(1, -1);
} else {
QueueScrollAndTouchMoved(1, 0);
}
}
QueueEndEvents();
RunQueuedEvents();
ExpectUrlAndOffset(url1_, 0);
}
// Each movement event is horizontal, except the first two. This should trigger
// history navigation. This test is DISABLED because it has never worked. Once
// the flaw in the history swiper logic has been corrected, this test should be
// enabled.
// crbug.com/375512
IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
DISABLED_TestAllHorizontalButFirst) {
QueueBeginningEvents(0, -1);
QueueScrollAndTouchMoved(0, -1);
for (int i = 0; i < 149; ++i)
QueueScrollAndTouchMoved(1, 0);
QueueEndEvents();
RunQueuedEvents();
ExpectUrlAndOffset(url1_, 0);
}
// Initial movements are vertical, and scroll the iframe. Subsequent movements
// are horizontal, and should not trigger history swiping.
IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
DISABLED_TestIframeHistorySwiping) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_iframe_));
ASSERT_EQ(url_iframe_, GetWebContents()->GetURL());
content::InputEventAckWaiter wheel_end_ack_waiter(
GetWebContents()->GetRenderViewHost()->GetWidget(),
base::BindRepeating([](blink::mojom::InputEventResultSource,
blink::mojom::InputEventResultState,
const blink::WebInputEvent& event) {
return event.GetType() == blink::WebInputEvent::Type::kMouseWheel &&
static_cast<const blink::WebMouseWheelEvent&>(event).phase ==
blink::WebMouseWheelEvent::kPhaseEnded;
}));
QueueBeginningEvents(0, -1);
for (int i = 0; i < 10; ++i)
QueueScrollAndTouchMoved(0, -1);
for (int i = 0; i < 149; ++i)
QueueScrollAndTouchMoved(1, 0);
QueueEndEvents();
RunQueuedEvents();
// Wait for the scroll to end.
wheel_end_ack_waiter.Wait();
EXPECT_TRUE(content::WaitForLoadStop(GetWebContents()));
EXPECT_EQ(url_iframe_, GetWebContents()->GetURL());
}
// The gesture ends before the touchesEndedWithEvent: method gets called.
IN_PROC_BROWSER_TEST_F(ChromeRenderWidgetHostViewMacHistorySwiperTest,
DISABLED_TestGestureEndTiming) {
QueueBeginningEvents(1, 0);
for (int i = 0; i < 150; ++i)
QueueScrollAndTouchMoved(1, 0);
QueueTouch(DEPLOYMENT_TOUCHES_MOVED, NSEventTypeEndGesture,
NSEventSubtypeMouseEvent, NO);
QueueGestureEnd();
QueueTouch(DEPLOYMENT_TOUCHES_ENDED, NSEventTypeEndGesture,
NSEventSubtypeMouseEvent, NO);
QueueTrackpadScroll(0, 0, NSEventPhaseEnded, YES);
RunQueuedEvents();
ExpectUrlAndOffset(url1_, 0);
}
// TODO(crbug.com/40126320): flaky.
IN_PROC_BROWSER_TEST_F(
ChromeRenderWidgetHostViewMacHistorySwiperTest,
DISABLED_InnerScrollersOverscrollBehaviorPreventsNavigation) {
const base::FilePath base_path(FILE_PATH_LITERAL("scroll"));
GURL url_overscroll_behavior = ui_test_utils::GetTestUrl(
base_path, base::FilePath(FILE_PATH_LITERAL("overscroll_behavior.html")));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_overscroll_behavior));
ASSERT_EQ(url_overscroll_behavior, GetWebContents()->GetURL());
QueueBeginningEvents(1, 0);
for (int i = 0; i < 10; ++i) {
QueueScrollAndTouchMoved(10, 0);
}
QueueEndEvents();
RunQueuedEvents();
// If navigation was to occur, the URL would be url2_.
ExpectUrlAndOffset(url_overscroll_behavior, 0);
}