[go: up one dir, main page]

[Android] Disable pull-to-refresh with overflow-y:hidden

Currently, the only way to disable the pull-to-refresh effect is to
explictly preventDefault the causal touches or suppress their default
actions with touch-action: none. However, the latter prevents
any kind of accelerated scrolling, and the former can be to get right
with composed or nested scrollable content.

Use the overflow-y:hidden property on the root element as a signal to
disable the effect. This dovetails with how this property both prevents
hiding of the top controls and suppresses the overscroll glow effect.

BUG=456515,456300

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

Cr-Commit-Position: refs/heads/master@{#315938}
(cherry picked from commit 2afdbf72bcfa0a2e30d31a37ca6499dcb99a62aa)

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

Cr-Commit-Position: refs/branch-heads/2272@{#320}
Cr-Branched-From: 827a380cfdb31aa54c8d56e63ce2c3fd8c3ba4d4-refs/heads/master@{#310958}
diff --git a/cc/output/compositor_frame_metadata.cc b/cc/output/compositor_frame_metadata.cc
index 039f69d3..dad78a0c 100644
--- a/cc/output/compositor_frame_metadata.cc
+++ b/cc/output/compositor_frame_metadata.cc
@@ -10,7 +10,9 @@
     : device_scale_factor(0.f),
       page_scale_factor(0.f),
       min_page_scale_factor(0.f),
-      max_page_scale_factor(0.f) {
+      max_page_scale_factor(0.f),
+      root_overflow_x_hidden(false),
+      root_overflow_y_hidden(false) {
 }
 
 CompositorFrameMetadata::~CompositorFrameMetadata() {
diff --git a/cc/output/compositor_frame_metadata.h b/cc/output/compositor_frame_metadata.h
index a6fe2d4..e92a84d 100644
--- a/cc/output/compositor_frame_metadata.h
+++ b/cc/output/compositor_frame_metadata.h
@@ -34,6 +34,8 @@
   gfx::SizeF root_layer_size;
   float min_page_scale_factor;
   float max_page_scale_factor;
+  bool root_overflow_x_hidden;
+  bool root_overflow_y_hidden;
 
   // Used to position the Android location top bar and page content, whose
   // precise position is computed by the renderer compositor.
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 303cd0e..0e54c28 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -1456,6 +1456,16 @@
   active_tree_->GetViewportSelection(&metadata.selection_start,
                                      &metadata.selection_end);
 
+  LayerImpl* root_layer_for_overflow = OuterViewportScrollLayer()
+                                           ? OuterViewportScrollLayer()
+                                           : InnerViewportScrollLayer();
+  if (root_layer_for_overflow) {
+    metadata.root_overflow_x_hidden =
+        !root_layer_for_overflow->user_scrollable_horizontal();
+    metadata.root_overflow_y_hidden =
+        !root_layer_for_overflow->user_scrollable_vertical();
+  }
+
   if (!InnerViewportScrollLayer())
     return metadata;
 
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index fb2fdb6..36fb10c 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -1646,6 +1646,8 @@
     EXPECT_EQ(gfx::SizeF(100.f, 100.f), metadata.root_layer_size);
     EXPECT_EQ(0.5f, metadata.min_page_scale_factor);
     EXPECT_EQ(4.f, metadata.max_page_scale_factor);
+    EXPECT_FALSE(metadata.root_overflow_x_hidden);
+    EXPECT_FALSE(metadata.root_overflow_y_hidden);
   }
 
   // Scrolling should update metadata immediately.
@@ -1664,6 +1666,24 @@
     EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
   }
 
+  // Root "overflow: hidden" properties should be reflected.
+  {
+    host_impl_->active_tree()
+        ->InnerViewportScrollLayer()
+        ->set_user_scrollable_horizontal(false);
+    CompositorFrameMetadata metadata =
+        host_impl_->MakeCompositorFrameMetadata();
+    EXPECT_TRUE(metadata.root_overflow_x_hidden);
+    EXPECT_FALSE(metadata.root_overflow_y_hidden);
+
+    host_impl_->active_tree()
+        ->InnerViewportScrollLayer()
+        ->set_user_scrollable_vertical(false);
+    metadata = host_impl_->MakeCompositorFrameMetadata();
+    EXPECT_TRUE(metadata.root_overflow_x_hidden);
+    EXPECT_TRUE(metadata.root_overflow_y_hidden);
+  }
+
   // Page scale should update metadata correctly (shrinking only the viewport).
   host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture);
   host_impl_->PinchGestureBegin();
diff --git a/content/browser/android/overscroll_controller_android.cc b/content/browser/android/overscroll_controller_android.cc
index 68c2006..9091393 100644
--- a/content/browser/android/overscroll_controller_android.cc
+++ b/content/browser/android/overscroll_controller_android.cc
@@ -252,8 +252,10 @@
   gfx::Vector2dF content_scroll_offset =
       gfx::ScaleVector2d(frame_metadata.root_scroll_offset, scale_factor);
 
-  if (refresh_effect_)
-    refresh_effect_->UpdateDisplay(viewport_size, content_scroll_offset);
+  if (refresh_effect_) {
+    refresh_effect_->UpdateDisplay(viewport_size, content_scroll_offset,
+                                   frame_metadata.root_overflow_y_hidden);
+  }
 
   if (glow_effect_) {
     glow_effect_->UpdateDisplay(viewport_size, content_size,
diff --git a/content/browser/android/overscroll_refresh.cc b/content/browser/android/overscroll_refresh.cc
index 13b2329..58962f6 100644
--- a/content/browser/android/overscroll_refresh.cc
+++ b/content/browser/android/overscroll_refresh.cc
@@ -412,6 +412,7 @@
                                      bool mirror)
     : client_(client),
       scrolled_to_top_(true),
+      overflow_y_hidden_(false),
       scroll_consumption_state_(DISABLED),
       effect_(new Effect(resource_manager, target_drag_offset_pixels, mirror)) {
   DCHECK(client);
@@ -427,7 +428,7 @@
 
 void OverscrollRefresh::OnScrollBegin() {
   ReleaseWithoutActivation();
-  if (scrolled_to_top_)
+  if (scrolled_to_top_ && !overflow_y_hidden_)
     scroll_consumption_state_ = AWAITING_SCROLL_UPDATE_ACK;
 }
 
@@ -495,9 +496,11 @@
 
 void OverscrollRefresh::UpdateDisplay(
     const gfx::SizeF& viewport_size,
-    const gfx::Vector2dF& content_scroll_offset) {
+    const gfx::Vector2dF& content_scroll_offset,
+    bool root_overflow_y_hidden) {
   viewport_size_ = viewport_size;
   scrolled_to_top_ = content_scroll_offset.y() == 0;
+  overflow_y_hidden_ = root_overflow_y_hidden;
 }
 
 void OverscrollRefresh::Release(bool allow_activation) {
diff --git a/content/browser/android/overscroll_refresh.h b/content/browser/android/overscroll_refresh.h
index fb325f8..77549c9 100644
--- a/content/browser/android/overscroll_refresh.h
+++ b/content/browser/android/overscroll_refresh.h
@@ -37,7 +37,8 @@
 
 // Simple pull-to-refresh styled effect. Listens to scroll events, conditionally
 // activating when:
-//   1) The scroll begins when the page has no vertical scroll offset.
+//   1) The scroll begins when the page's root layer 1) has no vertical scroll
+//      offset and 2) lacks the overflow-y:hidden property.
 //   2) The page doesn't consume the initial scroll events.
 //   3) The initial scroll direction is upward.
 // The actual page reload action is triggered only when the effect is active
@@ -81,7 +82,8 @@
   // Update the effect according to the most recent display parameters,
   // Note: All dimensions are in device pixels.
   void UpdateDisplay(const gfx::SizeF& viewport_size,
-                     const gfx::Vector2dF& content_scroll_offset);
+                     const gfx::Vector2dF& content_scroll_offset,
+                     bool root_overflow_y_hidden);
 
   // Reset the effect to its inactive state, immediately detaching and
   // disabling any active effects.
@@ -100,6 +102,7 @@
 
   gfx::SizeF viewport_size_;
   bool scrolled_to_top_;
+  bool overflow_y_hidden_;
 
   enum ScrollConsumptionState {
     DISABLED,
diff --git a/content/browser/android/overscroll_refresh_unittest.cc b/content/browser/android/overscroll_refresh_unittest.cc
index 449c2ad8..45b1dea 100644
--- a/content/browser/android/overscroll_refresh_unittest.cc
+++ b/content/browser/android/overscroll_refresh_unittest.cc
@@ -9,13 +9,6 @@
 
 namespace content {
 
-const float kDragTargetPixels = 100;
-const bool kMirror = false;
-
-gfx::SizeF DefaultViewportSize() {
-  return gfx::SizeF(512, 512);
-}
-
 class OverscrollRefreshTest : public OverscrollRefreshClient,
                               public ui::ResourceManager,
                               public testing::Test {
@@ -54,6 +47,21 @@
   }
 
  protected:
+
+  scoped_ptr<OverscrollRefresh> CreateEffect() {
+    const float kDragTargetPixels = 100;
+    const bool kMirror = false;
+    scoped_ptr<OverscrollRefresh> effect(
+        new OverscrollRefresh(this, this, kDragTargetPixels, kMirror));
+
+    const gfx::SizeF kViewportSize(512, 512);
+    const gfx::Vector2dF kScrollOffset;
+    const bool kOverflowYHidden = false;
+    effect->UpdateDisplay(kViewportSize, kScrollOffset, kOverflowYHidden);
+
+    return effect.Pass();
+  }
+
   void SignalRefreshCompleted() { still_refreshing_ = false; }
 
  private:
@@ -62,46 +70,44 @@
 };
 
 TEST_F(OverscrollRefreshTest, Basic) {
-  OverscrollRefresh effect(this, this, kDragTargetPixels, kMirror);
+  scoped_ptr<OverscrollRefresh> effect = CreateEffect();
 
-  gfx::Vector2dF origin_scroll_offset;
-  effect.UpdateDisplay(DefaultViewportSize(), origin_scroll_offset);
-  EXPECT_FALSE(effect.IsActive());
-  EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
+  EXPECT_FALSE(effect->IsActive());
+  EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck());
 
-  effect.OnScrollBegin();
-  EXPECT_FALSE(effect.IsActive());
-  EXPECT_TRUE(effect.IsAwaitingScrollUpdateAck());
+  effect->OnScrollBegin();
+  EXPECT_FALSE(effect->IsActive());
+  EXPECT_TRUE(effect->IsAwaitingScrollUpdateAck());
 
   // The initial scroll should not be consumed, as it should first be offered
   // to content.
   gfx::Vector2dF scroll_up(0, 10);
-  EXPECT_FALSE(effect.WillHandleScrollUpdate(scroll_up));
-  EXPECT_FALSE(effect.IsActive());
-  EXPECT_TRUE(effect.IsAwaitingScrollUpdateAck());
+  EXPECT_FALSE(effect->WillHandleScrollUpdate(scroll_up));
+  EXPECT_FALSE(effect->IsActive());
+  EXPECT_TRUE(effect->IsAwaitingScrollUpdateAck());
 
-  // The unconsumed, overscrolling scroll will trigger the effect.
-  effect.OnScrollUpdateAck(false);
-  EXPECT_TRUE(effect.IsActive());
-  EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
+  // The unconsumed, overscrolling scroll will trigger the effect->
+  effect->OnScrollUpdateAck(false);
+  EXPECT_TRUE(effect->IsActive());
+  EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck());
 
   // Further scrolls will be consumed.
-  EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
-  EXPECT_TRUE(effect.IsActive());
+  EXPECT_TRUE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 50)));
+  EXPECT_TRUE(effect->IsActive());
 
   // Even scrolls in the down direction should be consumed.
-  EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, -50)));
-  EXPECT_TRUE(effect.IsActive());
+  EXPECT_TRUE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, -50)));
+  EXPECT_TRUE(effect->IsActive());
 
   // Feed enough scrolls to the effect to exceeds the threshold.
-  PullBeyondActivationThreshold(&effect);
-  EXPECT_TRUE(effect.IsActive());
+  PullBeyondActivationThreshold(effect.get());
+  EXPECT_TRUE(effect->IsActive());
 
   // Ending the scroll while beyond the threshold should trigger a refresh.
   gfx::Vector2dF zero_velocity;
   EXPECT_FALSE(GetAndResetRefreshTriggered());
-  effect.OnScrollEnd(zero_velocity);
-  EXPECT_TRUE(effect.IsActive());
+  effect->OnScrollEnd(zero_velocity);
+  EXPECT_TRUE(effect->IsActive());
   EXPECT_TRUE(GetAndResetRefreshTriggered());
   SignalRefreshCompleted();
 
@@ -109,7 +115,7 @@
   base::TimeTicks initial_time = base::TimeTicks::Now();
   base::TimeTicks current_time = initial_time;
   scoped_refptr<cc::Layer> layer = cc::Layer::Create();
-  while (effect.Animate(current_time, layer.get()))
+  while (effect->Animate(current_time, layer.get()))
     current_time += base::TimeDelta::FromMilliseconds(16);
 
   // The effect should terminate in a timely fashion.
@@ -117,20 +123,19 @@
   EXPECT_LE(
       current_time.ToInternalValue(),
       (initial_time + base::TimeDelta::FromSeconds(10)).ToInternalValue());
-  EXPECT_FALSE(effect.IsActive());
+  EXPECT_FALSE(effect->IsActive());
 }
 
 TEST_F(OverscrollRefreshTest, AnimationTerminatesEvenIfRefreshNeverTerminates) {
-  OverscrollRefresh effect(this, this, kDragTargetPixels, kMirror);
-  effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
-  effect.OnScrollBegin();
-  ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
-  ASSERT_TRUE(effect.IsAwaitingScrollUpdateAck());
-  effect.OnScrollUpdateAck(false);
-  ASSERT_TRUE(effect.IsActive());
-  PullBeyondActivationThreshold(&effect);
-  ASSERT_TRUE(effect.IsActive());
-  effect.OnScrollEnd(gfx::Vector2dF(0, 0));
+  scoped_ptr<OverscrollRefresh> effect = CreateEffect();
+  effect->OnScrollBegin();
+  ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
+  ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck());
+  effect->OnScrollUpdateAck(false);
+  ASSERT_TRUE(effect->IsActive());
+  PullBeyondActivationThreshold(effect.get());
+  ASSERT_TRUE(effect->IsActive());
+  effect->OnScrollEnd(gfx::Vector2dF(0, 0));
   ASSERT_TRUE(GetAndResetRefreshTriggered());
 
   // Verify that the animation terminates even if the triggered refresh
@@ -138,155 +143,171 @@
   base::TimeTicks initial_time = base::TimeTicks::Now();
   base::TimeTicks current_time = initial_time;
   scoped_refptr<cc::Layer> layer = cc::Layer::Create();
-  while (effect.Animate(current_time, layer.get()))
+  while (effect->Animate(current_time, layer.get()))
     current_time += base::TimeDelta::FromMilliseconds(16);
 
   EXPECT_GT(current_time.ToInternalValue(), initial_time.ToInternalValue());
   EXPECT_LE(
       current_time.ToInternalValue(),
       (initial_time + base::TimeDelta::FromSeconds(10)).ToInternalValue());
-  EXPECT_FALSE(effect.IsActive());
+  EXPECT_FALSE(effect->IsActive());
 }
 
 TEST_F(OverscrollRefreshTest, NotTriggeredIfBelowThreshold) {
-  OverscrollRefresh effect(this, this, kDragTargetPixels, kMirror);
-  effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
-  effect.OnScrollBegin();
-  ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
-  ASSERT_TRUE(effect.IsAwaitingScrollUpdateAck());
-  effect.OnScrollUpdateAck(false);
-  ASSERT_TRUE(effect.IsActive());
+  scoped_ptr<OverscrollRefresh> effect = CreateEffect();
+  effect->OnScrollBegin();
+  ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
+  ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck());
+  effect->OnScrollUpdateAck(false);
+  ASSERT_TRUE(effect->IsActive());
 
   // Terminating the pull before it exceeds the threshold will prevent refresh.
-  EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
-  effect.OnScrollEnd(gfx::Vector2dF());
+  EXPECT_TRUE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
+  effect->OnScrollEnd(gfx::Vector2dF());
   EXPECT_FALSE(GetAndResetRefreshTriggered());
 }
 
 TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialYOffsetIsNotZero) {
-  OverscrollRefresh effect(this, this, kDragTargetPixels, kMirror);
+  scoped_ptr<OverscrollRefresh> effect = CreateEffect();
 
   // A positive y scroll offset at the start of scroll will prevent activation,
   // even if the subsequent scroll overscrolls upward.
+  gfx::SizeF viewport_size(512, 512);
   gfx::Vector2dF nonzero_offset(0, 10);
-  effect.UpdateDisplay(DefaultViewportSize(), nonzero_offset);
-  effect.OnScrollBegin();
+  bool overflow_y_hidden = false;
+  effect->UpdateDisplay(viewport_size, nonzero_offset, overflow_y_hidden);
+  effect->OnScrollBegin();
 
-  effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
-  ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
-  EXPECT_FALSE(effect.IsActive());
-  EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
-  effect.OnScrollUpdateAck(false);
-  EXPECT_FALSE(effect.IsActive());
-  EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
-  EXPECT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
-  effect.OnScrollEnd(gfx::Vector2dF());
+  ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
+  EXPECT_FALSE(effect->IsActive());
+  EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck());
+  effect->OnScrollUpdateAck(false);
+  EXPECT_FALSE(effect->IsActive());
+  EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck());
+  EXPECT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
+  effect->OnScrollEnd(gfx::Vector2dF());
+  EXPECT_FALSE(GetAndResetRefreshTriggered());
+}
+
+TEST_F(OverscrollRefreshTest, NotTriggeredIfOverflowYHidden) {
+  scoped_ptr<OverscrollRefresh> effect = CreateEffect();
+
+  // "overflow-y: hidden" on the root layer will prevent activation,
+  // even if the subsequent scroll overscrolls upward.
+  gfx::SizeF viewport_size(512, 512);
+  gfx::Vector2dF zero_offset;
+  bool overflow_y_hidden = true;
+  effect->UpdateDisplay(viewport_size, zero_offset, overflow_y_hidden);
+  effect->OnScrollBegin();
+  ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
+  EXPECT_FALSE(effect->IsActive());
+  EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck());
+  effect->OnScrollUpdateAck(false);
+  EXPECT_FALSE(effect->IsActive());
+  EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck());
+  EXPECT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
+  effect->OnScrollEnd(gfx::Vector2dF());
   EXPECT_FALSE(GetAndResetRefreshTriggered());
 }
 
 TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialScrollDownward) {
-  OverscrollRefresh effect(this, this, kDragTargetPixels, kMirror);
-  effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
-  effect.OnScrollBegin();
+  scoped_ptr<OverscrollRefresh> effect = CreateEffect();
+  effect->OnScrollBegin();
 
   // A downward initial scroll will prevent activation, even if the subsequent
   // scroll overscrolls upward.
-  ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, -10)));
-  EXPECT_FALSE(effect.IsActive());
-  EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
+  ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, -10)));
+  EXPECT_FALSE(effect->IsActive());
+  EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck());
 
-  effect.OnScrollUpdateAck(false);
-  EXPECT_FALSE(effect.IsActive());
-  EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
-  EXPECT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
-  effect.OnScrollEnd(gfx::Vector2dF());
+  effect->OnScrollUpdateAck(false);
+  EXPECT_FALSE(effect->IsActive());
+  EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck());
+  EXPECT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
+  effect->OnScrollEnd(gfx::Vector2dF());
   EXPECT_FALSE(GetAndResetRefreshTriggered());
 }
 
 TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialScrollOrTouchConsumed) {
-  OverscrollRefresh effect(this, this, kDragTargetPixels, kMirror);
-  effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
-  effect.OnScrollBegin();
-  ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
-  ASSERT_TRUE(effect.IsAwaitingScrollUpdateAck());
+  scoped_ptr<OverscrollRefresh> effect = CreateEffect();
+  effect->OnScrollBegin();
+  ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
+  ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck());
 
   // Consumption of the initial touchmove or scroll should prevent future
   // activation.
-  effect.OnScrollUpdateAck(true);
-  EXPECT_FALSE(effect.IsActive());
-  EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
-  EXPECT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
-  effect.OnScrollUpdateAck(false);
-  EXPECT_FALSE(effect.IsActive());
-  EXPECT_FALSE(effect.IsAwaitingScrollUpdateAck());
-  EXPECT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
-  effect.OnScrollEnd(gfx::Vector2dF());
+  effect->OnScrollUpdateAck(true);
+  EXPECT_FALSE(effect->IsActive());
+  EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck());
+  EXPECT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
+  effect->OnScrollUpdateAck(false);
+  EXPECT_FALSE(effect->IsActive());
+  EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck());
+  EXPECT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
+  effect->OnScrollEnd(gfx::Vector2dF());
   EXPECT_FALSE(GetAndResetRefreshTriggered());
 }
 
 TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialScrollsJanked) {
-  OverscrollRefresh effect(this, this, kDragTargetPixels, kMirror);
-  effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
-  effect.OnScrollBegin();
-  ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
-  ASSERT_TRUE(effect.IsAwaitingScrollUpdateAck());
-  effect.OnScrollUpdateAck(false);
-  ASSERT_TRUE(effect.IsActive());
+  scoped_ptr<OverscrollRefresh> effect = CreateEffect();
+  effect->OnScrollBegin();
+  ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
+  ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck());
+  effect->OnScrollUpdateAck(false);
+  ASSERT_TRUE(effect->IsActive());
 
   // It should take more than just one or two large scrolls to trigger,
-  // mitigating likelihood of jank triggering the effect.
-  EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
-  EXPECT_TRUE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
-  effect.OnScrollEnd(gfx::Vector2dF());
+  // mitigating likelihood of jank triggering the effect->
+  EXPECT_TRUE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
+  EXPECT_TRUE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500)));
+  effect->OnScrollEnd(gfx::Vector2dF());
   EXPECT_FALSE(GetAndResetRefreshTriggered());
 }
 
 TEST_F(OverscrollRefreshTest, NotTriggeredIfFlungDownward) {
-  OverscrollRefresh effect(this, this, kDragTargetPixels, kMirror);
-  effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
-  effect.OnScrollBegin();
-  ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
-  ASSERT_TRUE(effect.IsAwaitingScrollUpdateAck());
-  effect.OnScrollUpdateAck(false);
-  ASSERT_TRUE(effect.IsActive());
+  scoped_ptr<OverscrollRefresh> effect = CreateEffect();
+  effect->OnScrollBegin();
+  ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
+  ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck());
+  effect->OnScrollUpdateAck(false);
+  ASSERT_TRUE(effect->IsActive());
 
   // Ensure the pull exceeds the necessary threshold.
-  PullBeyondActivationThreshold(&effect);
-  ASSERT_TRUE(effect.IsActive());
+  PullBeyondActivationThreshold(effect.get());
+  ASSERT_TRUE(effect->IsActive());
 
   // Terminating the pull with a down-directed fling should prevent triggering.
-  effect.OnScrollEnd(gfx::Vector2dF(0, -1000));
+  effect->OnScrollEnd(gfx::Vector2dF(0, -1000));
   EXPECT_FALSE(GetAndResetRefreshTriggered());
 }
 
 TEST_F(OverscrollRefreshTest, NotTriggeredIfReleasedWithoutActivation) {
-  OverscrollRefresh effect(this, this, kDragTargetPixels, kMirror);
-  effect.UpdateDisplay(DefaultViewportSize(), gfx::Vector2dF());
-  effect.OnScrollBegin();
-  ASSERT_FALSE(effect.WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
-  ASSERT_TRUE(effect.IsAwaitingScrollUpdateAck());
-  effect.OnScrollUpdateAck(false);
-  ASSERT_TRUE(effect.IsActive());
+  scoped_ptr<OverscrollRefresh> effect = CreateEffect();
+  effect->OnScrollBegin();
+  ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10)));
+  ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck());
+  effect->OnScrollUpdateAck(false);
+  ASSERT_TRUE(effect->IsActive());
 
   // Ensure the pull exceeds the necessary threshold.
-  PullBeyondActivationThreshold(&effect);
-  ASSERT_TRUE(effect.IsActive());
+  PullBeyondActivationThreshold(effect.get());
+  ASSERT_TRUE(effect->IsActive());
 
   // An early release should prevent the refresh action from firing.
-  effect.ReleaseWithoutActivation();
-  effect.OnScrollEnd(gfx::Vector2dF());
+  effect->ReleaseWithoutActivation();
+  effect->OnScrollEnd(gfx::Vector2dF());
   EXPECT_FALSE(GetAndResetRefreshTriggered());
 
   // The early release should trigger a dismissal animation.
-  EXPECT_TRUE(effect.IsActive());
+  EXPECT_TRUE(effect->IsActive());
   base::TimeTicks initial_time = base::TimeTicks::Now();
   base::TimeTicks current_time = initial_time;
   scoped_refptr<cc::Layer> layer = cc::Layer::Create();
-  while (effect.Animate(current_time, layer.get()))
+  while (effect->Animate(current_time, layer.get()))
     current_time += base::TimeDelta::FromMilliseconds(16);
 
   EXPECT_GT(current_time.ToInternalValue(), initial_time.ToInternalValue());
-  EXPECT_FALSE(effect.IsActive());
+  EXPECT_FALSE(effect->IsActive());
   EXPECT_FALSE(GetAndResetRefreshTriggered());
 }
 
diff --git a/content/common/cc_messages.h b/content/common/cc_messages.h
index 5a76af22..4b8be1b 100644
--- a/content/common/cc_messages.h
+++ b/content/common/cc_messages.h
@@ -290,6 +290,8 @@
   IPC_STRUCT_TRAITS_MEMBER(root_layer_size)
   IPC_STRUCT_TRAITS_MEMBER(min_page_scale_factor)
   IPC_STRUCT_TRAITS_MEMBER(max_page_scale_factor)
+  IPC_STRUCT_TRAITS_MEMBER(root_overflow_x_hidden)
+  IPC_STRUCT_TRAITS_MEMBER(root_overflow_y_hidden)
   IPC_STRUCT_TRAITS_MEMBER(location_bar_offset)
   IPC_STRUCT_TRAITS_MEMBER(location_bar_content_translation)
   IPC_STRUCT_TRAITS_MEMBER(selection_start)