[go: nahoru, domu]

Move viewport scrolling logic into separate class

The viewport is made up of two scrolling layers now, the inner
and outer viewport scroll layers. These layers do not follow the
usual scroll bubbling logic since they're supposed to appear as
one viewport to the user (i.e. we should rail). Forcing it to
be scrolled via LayerTreeHostImpl::ScrollBy caused that method to
become increasingly complex.

This patch creates a Viewport class that allows LTHI::ScrollBy to
call Viewport::ScrollBy and scroll the viewport as a unit. I've
tried to simplify the logic in both these methods as much as I
could without changing behavior.

BUG=443724

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

Cr-Commit-Position: refs/heads/master@{#322268}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index df9f262..5354b7c 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -196,6 +196,8 @@
     "layers/video_layer.h",
     "layers/video_layer_impl.cc",
     "layers/video_layer_impl.h",
+    "layers/viewport.cc",
+    "layers/viewport.h",
     "output/begin_frame_args.cc",
     "output/begin_frame_args.h",
     "output/bsp_tree.cc",
diff --git a/cc/cc.gyp b/cc/cc.gyp
index c5f2033..b2bc0224 100644
--- a/cc/cc.gyp
+++ b/cc/cc.gyp
@@ -248,6 +248,8 @@
         'layers/video_layer.h',
         'layers/video_layer_impl.cc',
         'layers/video_layer_impl.h',
+        'layers/viewport.cc',
+        'layers/viewport.h',
         'output/begin_frame_args.cc',
         'output/begin_frame_args.h',
         'output/bsp_tree.cc',
diff --git a/cc/layers/viewport.cc b/cc/layers/viewport.cc
new file mode 100644
index 0000000..944f1bbd
--- /dev/null
+++ b/cc/layers/viewport.cc
@@ -0,0 +1,133 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/layers/viewport.h"
+
+#include "base/logging.h"
+#include "cc/input/top_controls_manager.h"
+#include "cc/trees/layer_tree_host_impl.h"
+#include "cc/trees/layer_tree_impl.h"
+#include "ui/gfx/geometry/vector2d_conversions.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace cc {
+
+// static
+scoped_ptr<Viewport> Viewport::Create(
+    LayerTreeHostImpl* host_impl) {
+  return make_scoped_ptr(new Viewport(host_impl));
+}
+
+Viewport::Viewport(LayerTreeHostImpl* host_impl)
+    : host_impl_(host_impl) {
+  DCHECK(host_impl_);
+}
+
+Viewport::ScrollResult Viewport::ScrollBy(const gfx::Vector2dF& delta,
+                                          const gfx::Point& viewport_point,
+                                          bool is_wheel_scroll) {
+  gfx::Vector2dF content_delta = delta;
+  ScrollResult result;
+
+  if (ShouldTopControlsConsumeScroll(delta)) {
+    result.top_controls_applied_delta = ScrollTopControls(delta);
+    content_delta -= result.top_controls_applied_delta;
+  }
+
+  gfx::Vector2dF pending_content_delta = content_delta;
+
+  if (OuterScrollLayer()) {
+    pending_content_delta -= host_impl_->ScrollLayer(OuterScrollLayer(),
+                                                     pending_content_delta,
+                                                     viewport_point,
+                                                     is_wheel_scroll);
+  }
+
+  // TODO(bokan): This shouldn't be needed but removing it causes subtle
+  // viewport movement during top controls manipulation.
+  if (!gfx::ToRoundedVector2d(pending_content_delta).IsZero()) {
+    pending_content_delta -= host_impl_->ScrollLayer(InnerScrollLayer(),
+                                                     pending_content_delta,
+                                                     viewport_point,
+                                                     is_wheel_scroll);
+    result.unused_scroll_delta = AdjustOverscroll(pending_content_delta);
+  }
+
+
+  result.applied_delta = content_delta - pending_content_delta;
+  return result;
+}
+
+gfx::Vector2dF Viewport::ScrollTopControls(const gfx::Vector2dF& delta) {
+  gfx::Vector2dF excess_delta =
+      host_impl_->top_controls_manager()->ScrollBy(delta);
+
+  return delta - excess_delta;
+}
+
+bool Viewport::ShouldTopControlsConsumeScroll(
+    const gfx::Vector2dF& scroll_delta) const {
+  // Always consume if it's in the direction to show the top controls.
+  if (scroll_delta.y() < 0)
+    return true;
+
+  if (TotalScrollOffset().y() < MaxTotalScrollOffset().y())
+    return true;
+
+  return false;
+}
+
+gfx::Vector2dF Viewport::AdjustOverscroll(const gfx::Vector2dF& delta) const {
+  const float kEpsilon = 0.1f;
+  gfx::Vector2dF adjusted = delta;
+
+  if (std::abs(adjusted.x()) < kEpsilon)
+    adjusted.set_x(0.0f);
+  if (std::abs(adjusted.y()) < kEpsilon)
+    adjusted.set_y(0.0f);
+
+  // Disable overscroll on axes which are impossible to scroll.
+  if (host_impl_->settings().report_overscroll_only_for_scrollable_axes) {
+    if (std::abs(MaxTotalScrollOffset().x()) <= kEpsilon ||
+        !InnerScrollLayer()->user_scrollable_horizontal())
+      adjusted.set_x(0.0f);
+    if (std::abs(MaxTotalScrollOffset().y()) <= kEpsilon ||
+        !InnerScrollLayer()->user_scrollable_vertical())
+      adjusted.set_y(0.0f);
+  }
+
+  return adjusted;
+}
+
+gfx::ScrollOffset Viewport::MaxTotalScrollOffset() const {
+  gfx::ScrollOffset offset;
+
+  offset += InnerScrollLayer()->MaxScrollOffset();
+
+  if (OuterScrollLayer())
+    offset += OuterScrollLayer()->MaxScrollOffset();
+
+  return offset;
+}
+
+gfx::ScrollOffset Viewport::TotalScrollOffset() const {
+  gfx::ScrollOffset offset;
+
+  offset += InnerScrollLayer()->CurrentScrollOffset();
+
+  if (OuterScrollLayer())
+    offset += OuterScrollLayer()->CurrentScrollOffset();
+
+  return offset;
+}
+
+LayerImpl* Viewport::InnerScrollLayer() const {
+  return host_impl_->InnerViewportScrollLayer();
+}
+
+LayerImpl* Viewport::OuterScrollLayer() const {
+  return host_impl_->OuterViewportScrollLayer();
+}
+
+}  // namespace cc
diff --git a/cc/layers/viewport.h b/cc/layers/viewport.h
new file mode 100644
index 0000000..25528af0
--- /dev/null
+++ b/cc/layers/viewport.h
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_LAYERS_VIEWPORT_H_
+#define CC_LAYERS_VIEWPORT_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "cc/layers/layer_impl.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace cc {
+
+class LayerTreeHostImpl;
+
+// Encapsulates gesture handling logic on the viewport layers. The "viewport"
+// is made up of two scrolling layers, the inner viewport (visual) and the
+// outer viewport (layout) scroll layers. These layers have different scroll
+// bubbling behavior from the rest of the layer tree which is encoded in this
+// class.
+class CC_EXPORT Viewport {
+ public:
+  struct ScrollResult {
+    gfx::Vector2dF applied_delta;
+    gfx::Vector2dF unused_scroll_delta;
+    gfx::Vector2dF top_controls_applied_delta;
+  };
+
+  static scoped_ptr<Viewport> Create(LayerTreeHostImpl* host_impl);
+
+  // Scrolls the viewport, applying the unique bubbling between the inner and
+  // outer viewport. Scrolls can be consumed by top controls.
+  ScrollResult ScrollBy(const gfx::Vector2dF& delta,
+                        const gfx::Point& viewport_point,
+                        bool is_wheel_scroll);
+
+ private:
+  explicit Viewport(LayerTreeHostImpl* host_impl);
+
+  bool ShouldTopControlsConsumeScroll(const gfx::Vector2dF& scroll_delta) const;
+  gfx::Vector2dF AdjustOverscroll(const gfx::Vector2dF& delta) const;
+
+  // Sends the delta to the top controls, returns the amount applied.
+  gfx::Vector2dF ScrollTopControls(const gfx::Vector2dF& delta);
+
+  gfx::ScrollOffset MaxTotalScrollOffset() const;
+  gfx::ScrollOffset TotalScrollOffset() const;
+
+  LayerImpl* InnerScrollLayer() const;
+  LayerImpl* OuterScrollLayer() const;
+
+  LayerTreeHostImpl* host_impl_;
+
+  DISALLOW_COPY_AND_ASSIGN(Viewport);
+};
+
+}  // namespace cc
+
+#endif  // CC_LAYERS_VIEWPORT_H_
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 44c9eed..55eec2fe 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -39,6 +39,7 @@
 #include "cc/layers/painted_scrollbar_layer_impl.h"
 #include "cc/layers/render_surface_impl.h"
 #include "cc/layers/scrollbar_layer_impl_base.h"
+#include "cc/layers/viewport.h"
 #include "cc/output/compositor_frame_metadata.h"
 #include "cc/output/copy_output_request.h"
 #include "cc/output/delegating_renderer.h"
@@ -240,6 +241,8 @@
       LayerTreeImpl::create(this, new SyncedProperty<ScaleGroup>(),
                             new SyncedTopControls, new SyncedElasticOverscroll);
 
+  viewport_ = Viewport::Create(this);
+
   TRACE_EVENT_OBJECT_CREATED_WITH_ID(
       TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::LayerTreeHostImpl", id_);
 
@@ -2477,7 +2480,6 @@
 
 gfx::Vector2dF LayerTreeHostImpl::ScrollLayerWithViewportSpaceDelta(
     LayerImpl* layer_impl,
-    float scale_from_viewport_to_screen_space,
     const gfx::PointF& viewport_point,
     const gfx::Vector2dF& viewport_delta) {
   // Layers with non-invertible screen space transforms should not have passed
@@ -2491,6 +2493,7 @@
   // layers, we may need to explicitly handle uninvertible transforms here.
   DCHECK(did_invert);
 
+  float scale_from_viewport_to_screen_space = device_scale_factor_;
   gfx::PointF screen_space_point =
       gfx::ScalePoint(viewport_point, scale_from_viewport_to_screen_space);
 
@@ -2565,19 +2568,25 @@
   return gfx::Vector2dF(scrolled.x(), scrolled.y());
 }
 
-bool LayerTreeHostImpl::ShouldTopControlsConsumeScroll(
-    const gfx::Vector2dF& scroll_delta) const {
-  DCHECK(CurrentlyScrollingLayer());
+gfx::Vector2dF LayerTreeHostImpl::ScrollLayer(LayerImpl* layer_impl,
+                                              const gfx::Vector2dF& delta,
+                                              const gfx::Point& viewport_point,
+                                              bool is_wheel_scroll) {
+  // Gesture events need to be transformed from viewport coordinates to
+  // local layer coordinates so that the scrolling contents exactly follow
+  // the user's finger. In contrast, wheel events represent a fixed amount
+  // of scrolling so we can just apply them directly, but the page scale
+  // factor is applied to the scroll delta.
+  if (is_wheel_scroll) {
+    float scale_factor = active_tree()->current_page_scale_factor();
+    return ScrollLayerWithLocalDelta(layer_impl,
+                                     delta,
+                                     scale_factor);
+  }
 
-  // Always consume if it's in the direction to show the top controls.
-  if (scroll_delta.y() < 0)
-    return true;
-
-  if (active_tree()->TotalScrollOffset().y() <
-      active_tree()->TotalMaxScrollOffset().y())
-    return true;
-
-  return false;
+  return ScrollLayerWithViewportSpaceDelta(layer_impl,
+                                           viewport_point,
+                                           delta);
 }
 
 InputHandlerScrollResult LayerTreeHostImpl::ScrollBy(
@@ -2593,127 +2602,68 @@
   bool did_scroll_y = false;
   bool did_scroll_top_controls = false;
 
-  bool consume_by_top_controls = ShouldTopControlsConsumeScroll(scroll_delta);
-
-  // There's an edge case where the outer viewport isn't scrollable when the
-  // scroll starts, however, as the top controls show the outer viewport becomes
-  // scrollable. Therefore, always try scrolling the outer viewport before the
-  // inner.
-  // TODO(bokan): Move the top controls logic out of the loop since the scroll
-  // that causes the outer viewport to become scrollable will still be applied
-  // to the inner viewport.
-  LayerImpl* start_layer = CurrentlyScrollingLayer();
-  if (start_layer == InnerViewportScrollLayer() && OuterViewportScrollLayer())
-      start_layer = OuterViewportScrollLayer();
-
-  for (LayerImpl* layer_impl = start_layer;
+  for (LayerImpl* layer_impl = CurrentlyScrollingLayer();
        layer_impl;
        layer_impl = layer_impl->parent()) {
-    if (!layer_impl->scrollable())
+    // Skip the outer viewport scroll layer so that we try to scroll the
+    // viewport only once. i.e. The inner viewport layer represents the
+    // viewport.
+    if (!layer_impl->scrollable() || layer_impl == OuterViewportScrollLayer())
       continue;
 
-    if (layer_impl == InnerViewportScrollLayer() ||
-        layer_impl == OuterViewportScrollLayer()) {
-      if (consume_by_top_controls) {
-        gfx::Vector2dF excess_delta =
-            top_controls_manager_->ScrollBy(pending_delta);
-        gfx::Vector2dF applied_delta = pending_delta - excess_delta;
-        pending_delta = excess_delta;
-        // Force updating of vertical adjust values if needed.
-        if (applied_delta.y() != 0)
-          did_scroll_top_controls = true;
-      }
-      // Track root layer deltas for reporting overscroll.
-      if (layer_impl == InnerViewportScrollLayer())
-        unused_root_delta = pending_delta;
-    }
-
     gfx::Vector2dF applied_delta;
-    // Gesture events need to be transformed from viewport coordinates to local
-    // layer coordinates so that the scrolling contents exactly follow the
-    // user's finger. In contrast, wheel events represent a fixed amount of
-    // scrolling so we can just apply them directly, but the page scale factor
-    // is applied to the scroll delta.
-    if (!wheel_scrolling_) {
-      float scale_from_viewport_to_screen_space = device_scale_factor_;
-      applied_delta =
-          ScrollLayerWithViewportSpaceDelta(layer_impl,
-                                            scale_from_viewport_to_screen_space,
-                                            viewport_point, pending_delta);
-    } else {
-      applied_delta = ScrollLayerWithLocalDelta(
-          layer_impl, pending_delta, active_tree_->current_page_scale_factor());
-    }
-
-    const float kEpsilon = 0.1f;
     if (layer_impl == InnerViewportScrollLayer()) {
-      unused_root_delta.Subtract(applied_delta);
-      if (std::abs(unused_root_delta.x()) < kEpsilon)
-        unused_root_delta.set_x(0.0f);
-      if (std::abs(unused_root_delta.y()) < kEpsilon)
-        unused_root_delta.set_y(0.0f);
-      // Disable overscroll on axes which is impossible to scroll.
-      if (settings_.report_overscroll_only_for_scrollable_axes) {
-        if (std::abs(active_tree_->TotalMaxScrollOffset().x()) <= kEpsilon ||
-            !layer_impl->user_scrollable_horizontal())
-          unused_root_delta.set_x(0.0f);
-        if (std::abs(active_tree_->TotalMaxScrollOffset().y()) <= kEpsilon ||
-            !layer_impl->user_scrollable_vertical())
-          unused_root_delta.set_y(0.0f);
-      }
+      Viewport::ScrollResult result = viewport()->ScrollBy(pending_delta,
+                                                           viewport_point,
+                                                           wheel_scrolling_);
+      applied_delta = result.applied_delta;
+      unused_root_delta = result.unused_scroll_delta;
+      did_scroll_top_controls = result.top_controls_applied_delta.y() != 0;
+    } else {
+      applied_delta = ScrollLayer(layer_impl,
+                                  pending_delta,
+                                  viewport_point,
+                                  wheel_scrolling_);
     }
 
-    // Scrolls should bubble perfectly between the outer and inner viewports.
-    bool allow_unrestricted_bubbling_for_current_layer =
-        layer_impl == OuterViewportScrollLayer();
-    bool allow_bubbling_for_current_layer =
-        allow_unrestricted_bubbling_for_current_layer || should_bubble_scrolls_;
-
     // If the layer wasn't able to move, try the next one in the hierarchy.
+    const float kEpsilon = 0.1f;
     bool did_move_layer_x = std::abs(applied_delta.x()) > kEpsilon;
     bool did_move_layer_y = std::abs(applied_delta.y()) > kEpsilon;
     did_scroll_x |= did_move_layer_x;
     did_scroll_y |= did_move_layer_y;
-    if (!did_move_layer_x && !did_move_layer_y) {
-      if (allow_bubbling_for_current_layer || !did_lock_scrolling_layer_)
-        continue;
-      else
+
+    if (did_move_layer_x || did_move_layer_y) {
+      did_lock_scrolling_layer_ = true;
+
+      // When scrolls are allowed to bubble, it's important that the original
+      // scrolling layer be preserved. This ensures that, after a scroll
+      // bubbles, the user can reverse scroll directions and immediately resume
+      // scrolling the original layer that scrolled.
+      if (!should_bubble_scrolls_) {
+        active_tree_->SetCurrentlyScrollingLayer(layer_impl);
         break;
-    }
+      }
 
-    did_lock_scrolling_layer_ = true;
-
-    // When scrolls are allowed to bubble, it's important that the original
-    // scrolling layer be preserved. This ensures that, after a scroll bubbles,
-    // the user can reverse scroll directions and immediately resume scrolling
-    // the original layer that scrolled.
-    if (!should_bubble_scrolls_)
-      active_tree_->SetCurrentlyScrollingLayer(layer_impl);
-
-    if (!allow_bubbling_for_current_layer)
-      break;
-
-    if (allow_unrestricted_bubbling_for_current_layer) {
-      pending_delta -= applied_delta;
-    } else {
       // If the applied delta is within 45 degrees of the input delta, bail out
       // to make it easier to scroll just one layer in one direction without
       // affecting any of its parents.
       float angle_threshold = 45;
       if (MathUtil::SmallestAngleBetweenVectors(applied_delta, pending_delta) <
-          angle_threshold) {
-        pending_delta = gfx::Vector2dF();
+          angle_threshold)
         break;
-      }
 
       // Allow further movement only on an axis perpendicular to the direction
       // in which the layer moved.
       gfx::Vector2dF perpendicular_axis(-applied_delta.y(), applied_delta.x());
       pending_delta =
           MathUtil::ProjectVector(pending_delta, perpendicular_axis);
+
+      if (gfx::ToRoundedVector2d(pending_delta).IsZero())
+        break;
     }
 
-    if (gfx::ToRoundedVector2d(pending_delta).IsZero())
+    if (!should_bubble_scrolls_ && did_lock_scrolling_layer_)
       break;
   }
 
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index 2efe2dab..388de5e 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -73,6 +73,7 @@
 class UIResourceBitmap;
 class UIResourceRequest;
 struct ScrollAndScaleSet;
+class Viewport;
 
 enum class GpuRasterizationStatus {
   ON,
@@ -524,6 +525,11 @@
     return frame_timing_tracker_.get();
   }
 
+  gfx::Vector2dF ScrollLayer(LayerImpl* layer_impl,
+                             const gfx::Vector2dF& delta,
+                             const gfx::Point& viewport_point,
+                             bool is_wheel_scroll);
+
  protected:
   LayerTreeHostImpl(
       const LayerTreeSettings& settings,
@@ -546,6 +552,11 @@
   Proxy* proxy_;
 
  private:
+  gfx::Vector2dF ScrollLayerWithViewportSpaceDelta(
+      LayerImpl* layer_impl,
+      const gfx::PointF& viewport_point,
+      const gfx::Vector2dF& viewport_delta);
+
   void CreateAndSetRenderer();
   void CreateAndSetTileManager();
   void DestroyTileManager();
@@ -555,6 +566,8 @@
 
   bool IsSynchronousSingleThreaded() const;
 
+  Viewport* viewport() { return viewport_.get(); }
+
   // Scroll by preferring to move the outer viewport first, only moving the
   // inner if the outer is at its scroll extents.
   void ScrollViewportBy(gfx::Vector2dF scroll_delta);
@@ -565,14 +578,6 @@
   void AnimateScrollbars(base::TimeTicks monotonic_time);
   void AnimateTopControls(base::TimeTicks monotonic_time);
 
-  bool ShouldTopControlsConsumeScroll(const gfx::Vector2dF& scroll_delta) const;
-
-  gfx::Vector2dF ScrollLayerWithViewportSpaceDelta(
-      LayerImpl* layer_impl,
-      float scale_from_viewport_to_screen_space,
-      const gfx::PointF& viewport_point,
-      const gfx::Vector2dF& viewport_delta);
-
   void TrackDamageForAllSurfaces(
       LayerImpl* root_draw_layer,
       const LayerImplList& render_surface_layer_list);
@@ -747,6 +752,8 @@
 
   scoped_ptr<FrameTimingTracker> frame_timing_tracker_;
 
+  scoped_ptr<Viewport> viewport_;
+
   DISALLOW_COPY_AND_ASSIGN(LayerTreeHostImpl);
 };