Commit ef97146f authored by bokan's avatar bokan Committed by Commit bot

Made top controls work with virtual viewport pinch-to-zoom. (Chromium)

This change adjusts the scroll bubbling logic in LayerTreeHostImpl so
that outer viewport layer scrolls affect the top controls. Additionally,
the outer viewport's size is adjusted to match the inner viewport's
aspect ratio until a resize occurs.

Blink side: https://codereview.chromium.org/643473002
BUG=364109

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

Cr-Commit-Position: refs/heads/master@{#299384}
parent eaa91cae
......@@ -771,10 +771,15 @@ bool LayerImpl::IsActive() const {
return layer_tree_impl_->IsActiveTree();
}
// TODO(aelias): Convert so that bounds returns SizeF.
gfx::Size LayerImpl::bounds() const {
return gfx::ToCeiledSize(gfx::SizeF(bounds_.width() + bounds_delta_.x(),
bounds_.height() + bounds_delta_.y()));
gfx::Vector2d delta = gfx::ToCeiledVector2d(bounds_delta_);
return gfx::Size(bounds_.width() + delta.x(),
bounds_.height() + delta.y());
}
gfx::SizeF LayerImpl::BoundsForScrolling() const {
return gfx::SizeF(bounds_.width() + bounds_delta_.x(),
bounds_.height() + bounds_delta_.y());
}
void LayerImpl::SetBounds(const gfx::Size& bounds) {
......@@ -1174,7 +1179,7 @@ gfx::ScrollOffset LayerImpl::MaxScrollOffset() const {
DCHECK(this != layer_tree_impl()->InnerViewportScrollLayer() ||
IsContainerForFixedPositionLayers());
gfx::SizeF scaled_scroll_bounds(bounds());
gfx::SizeF scaled_scroll_bounds(BoundsForScrolling());
float scale_factor = 1.f;
for (LayerImpl const* current_layer = this;
......@@ -1242,11 +1247,12 @@ void LayerImpl::SetScrollbarPosition(ScrollbarLayerImplBase* scrollbar_layer,
DCHECK(scrollbar_clip_layer);
DCHECK(this != layer_tree_impl()->InnerViewportScrollLayer() ||
IsContainerForFixedPositionLayers());
gfx::RectF clip_rect(gfx::PointF(), scrollbar_clip_layer->bounds());
gfx::RectF clip_rect(gfx::PointF(),
scrollbar_clip_layer->BoundsForScrolling());
// See comment in MaxScrollOffset() regarding the use of the content layer
// bounds here.
gfx::RectF scroll_rect(gfx::PointF(), bounds());
gfx::RectF scroll_rect(gfx::PointF(), BoundsForScrolling());
if (scroll_rect.size().IsEmpty())
return;
......
......@@ -365,6 +365,9 @@ class CC_EXPORT LayerImpl : public LayerAnimationValueObserver,
void SetBounds(const gfx::Size& bounds);
gfx::Size bounds() const;
// Like bounds() but doesn't snap to int. Lossy on giant pages (e.g. millions
// of pixels) due to use of single precision float.
gfx::SizeF BoundsForScrolling() const;
void SetBoundsDelta(const gfx::Vector2dF& bounds_delta);
gfx::Vector2dF bounds_delta() const { return bounds_delta_; }
......
......@@ -78,6 +78,42 @@
namespace cc {
namespace {
// Small helper class that saves the current viewport location as the user sees
// it and resets to the same location.
class ViewportAnchor {
public:
ViewportAnchor(LayerImpl* inner_scroll, LayerImpl* outer_scroll)
: inner_(inner_scroll),
outer_(outer_scroll) {
viewport_in_content_coordinates_ = inner_->TotalScrollOffset();
if (outer_)
viewport_in_content_coordinates_ += outer_->TotalScrollOffset();
}
void ResetViewportToAnchoredPosition() {
DCHECK(outer_);
inner_->ClampScrollToMaxScrollOffset();
outer_->ClampScrollToMaxScrollOffset();
gfx::ScrollOffset viewport_location = inner_->TotalScrollOffset() +
outer_->TotalScrollOffset();
gfx::Vector2dF delta =
viewport_in_content_coordinates_.DeltaFrom(viewport_location);
delta = outer_->ScrollBy(delta);
inner_->ScrollBy(delta);
}
private:
LayerImpl* inner_;
LayerImpl* outer_;
gfx::ScrollOffset viewport_in_content_coordinates_;
};
void DidVisibilityChange(LayerTreeHostImpl* id, bool visible) {
if (visible) {
TRACE_EVENT_ASYNC_BEGIN1("webkit",
......@@ -1662,16 +1698,41 @@ void LayerTreeHostImpl::WillBeginImplFrame(const BeginFrameArgs& args) {
begin_impl_frame_interval_ = args.interval;
}
void LayerTreeHostImpl::UpdateInnerViewportContainerSize() {
LayerImpl* container_layer = active_tree_->InnerViewportContainerLayer();
if (!container_layer)
void LayerTreeHostImpl::UpdateViewportContainerSizes() {
LayerImpl* inner_container = active_tree_->InnerViewportContainerLayer();
LayerImpl* outer_container = active_tree_->OuterViewportContainerLayer();
if (!inner_container || !top_controls_manager_)
return;
if (top_controls_manager_) {
container_layer->SetBoundsDelta(
gfx::Vector2dF(0, active_tree_->top_controls_layout_height() -
active_tree_->total_top_controls_content_offset()));
}
ViewportAnchor anchor(InnerViewportScrollLayer(),
OuterViewportScrollLayer());
// Adjust the inner viewport by shrinking/expanding the container to account
// for the change in top controls height since the last Resize from Blink.
inner_container->SetBoundsDelta(
gfx::Vector2dF(0, active_tree_->top_controls_layout_height() -
active_tree_->total_top_controls_content_offset()));
if (!outer_container || outer_container->BoundsForScrolling().IsEmpty())
return;
// Adjust the outer viewport container as well, since adjusting only the
// inner may cause its bounds to exceed those of the outer, causing scroll
// clamping. We adjust it so it maintains the same aspect ratio as the
// inner viewport.
float aspect_ratio = inner_container->BoundsForScrolling().width() /
inner_container->BoundsForScrolling().height();
float target_height = outer_container->BoundsForScrolling().width() /
aspect_ratio;
float current_outer_height = outer_container->BoundsForScrolling().height() -
outer_container->bounds_delta().y();
gfx::Vector2dF delta(0, target_height - current_outer_height);
outer_container->SetBoundsDelta(delta);
active_tree_->InnerViewportScrollLayer()->SetBoundsDelta(delta);
anchor.ResetViewportToAnchoredPosition();
}
void LayerTreeHostImpl::SetTopControlsLayoutHeight(float height) {
......@@ -1679,7 +1740,7 @@ void LayerTreeHostImpl::SetTopControlsLayoutHeight(float height) {
return;
active_tree_->set_top_controls_layout_height(height);
UpdateInnerViewportContainerSize();
UpdateViewportContainerSizes();
SetFullRootLayerDamage();
}
......@@ -1800,7 +1861,7 @@ void LayerTreeHostImpl::ActivateSyncTree() {
top_controls_manager_->top_controls_height());
}
UpdateInnerViewportContainerSize();
UpdateViewportContainerSizes();
} else {
active_tree_->ProcessUIResourceRequestQueue();
}
......@@ -2164,7 +2225,7 @@ void LayerTreeHostImpl::SetViewportSize(const gfx::Size& device_viewport_size) {
device_viewport_size_ = device_viewport_size;
UpdateInnerViewportContainerSize();
UpdateViewportContainerSizes();
client_->OnCanDrawStateChanged(CanDraw());
SetFullRootLayerDamage();
active_tree_->set_needs_update_draw_properties();
......@@ -2215,7 +2276,7 @@ const gfx::Transform& LayerTreeHostImpl::DrawTransform() const {
}
void LayerTreeHostImpl::DidChangeTopControlsPosition() {
UpdateInnerViewportContainerSize();
UpdateViewportContainerSizes();
SetNeedsRedraw();
SetNeedsAnimate();
active_tree_->set_needs_update_draw_properties();
......@@ -2511,6 +2572,31 @@ static gfx::Vector2dF ScrollLayerWithLocalDelta(LayerImpl* layer_impl,
return layer_impl->ScrollDelta() - previous_delta;
}
bool LayerTreeHostImpl::ShouldTopControlsConsumeScroll(
const gfx::Vector2dF& scroll_delta) const {
DCHECK(CurrentlyScrollingLayer());
if (!top_controls_manager_)
return false;
// Always consume if it's in the direction to show the top controls.
if (scroll_delta.y() < 0)
return true;
if (CurrentlyScrollingLayer() != InnerViewportScrollLayer() &&
CurrentlyScrollingLayer() != OuterViewportScrollLayer())
return false;
if (InnerViewportScrollLayer()->MaxScrollOffset().y() > 0)
return true;
if (OuterViewportScrollLayer() &&
OuterViewportScrollLayer()->MaxScrollOffset().y() > 0)
return true;
return false;
}
bool LayerTreeHostImpl::ScrollBy(const gfx::Point& viewport_point,
const gfx::Vector2dF& scroll_delta) {
TRACE_EVENT0("cc", "LayerTreeHostImpl::ScrollBy");
......@@ -2522,14 +2608,8 @@ bool LayerTreeHostImpl::ScrollBy(const gfx::Point& viewport_point,
bool did_scroll_x = false;
bool did_scroll_y = false;
bool did_scroll_top_controls = false;
// TODO(wjmaclean) Should we guard against CurrentlyScrollingLayer() == 0
// here?
bool consume_by_top_controls =
top_controls_manager_ &&
(((CurrentlyScrollingLayer() == InnerViewportScrollLayer() ||
CurrentlyScrollingLayer() == OuterViewportScrollLayer()) &&
InnerViewportScrollLayer()->MaxScrollOffset().y() > 0) ||
scroll_delta.y() < 0);
bool consume_by_top_controls = ShouldTopControlsConsumeScroll(scroll_delta);
for (LayerImpl* layer_impl = CurrentlyScrollingLayer();
layer_impl;
......@@ -2537,23 +2617,20 @@ bool LayerTreeHostImpl::ScrollBy(const gfx::Point& viewport_point,
if (!layer_impl->scrollable())
continue;
if (layer_impl == InnerViewportScrollLayer()) {
// Only allow bubble scrolling when the scroll is in the direction to make
// the top controls visible.
gfx::Vector2dF applied_delta;
gfx::Vector2dF excess_delta;
if (layer_impl == InnerViewportScrollLayer() ||
layer_impl == OuterViewportScrollLayer()) {
if (consume_by_top_controls) {
excess_delta = top_controls_manager_->ScrollBy(pending_delta);
applied_delta = pending_delta - excess_delta;
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) {
if (applied_delta.y() != 0)
did_scroll_top_controls = true;
layer_impl->ScrollbarParametersDidChange(false);
}
}
// Track root layer deltas for reporting overscroll.
unused_root_delta = pending_delta;
if (layer_impl == InnerViewportScrollLayer())
unused_root_delta = pending_delta;
}
gfx::Vector2dF applied_delta;
......
......@@ -497,7 +497,7 @@ class CC_EXPORT LayerTreeHostImpl
GpuMemoryBufferManager* gpu_memory_buffer_manager,
int id);
void UpdateInnerViewportContainerSize();
void UpdateViewportContainerSizes();
// Virtual for testing.
virtual void AnimateLayers(base::TimeTicks monotonic_time);
......@@ -536,6 +536,8 @@ class CC_EXPORT LayerTreeHostImpl
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,
......
......@@ -2239,6 +2239,7 @@ TEST_F(LayerTreeHostImplTest, ScrollRootIgnored) {
EXPECT_FALSE(did_request_commit_);
}
// TODO(bokan): Convert these tests to create inner and outer viewports.
class LayerTreeHostImplTopControlsTest : public LayerTreeHostImplTest {
public:
LayerTreeHostImplTopControlsTest()
......@@ -2309,6 +2310,62 @@ class LayerTreeHostImplTopControlsTest : public LayerTreeHostImplTest {
EXPECT_EQ(clip_size_, root_clip_ptr->bounds());
}
void SetupTopControlsAndScrollLayerWithVirtualViewport(
const gfx::Size& inner_viewport_size,
const gfx::Size& outer_viewport_size,
const gfx::Size& scroll_layer_size) {
CreateHostImpl(settings_, CreateOutputSurface());
scoped_ptr<LayerImpl> root =
LayerImpl::Create(host_impl_->active_tree(), 1);
scoped_ptr<LayerImpl> root_clip =
LayerImpl::Create(host_impl_->active_tree(), 2);
scoped_ptr<LayerImpl> page_scale =
LayerImpl::Create(host_impl_->active_tree(), 3);
scoped_ptr<LayerImpl> outer_scroll =
LayerImpl::Create(host_impl_->active_tree(), 4);
scoped_ptr<LayerImpl> outer_clip =
LayerImpl::Create(host_impl_->active_tree(), 5);
root_clip->SetBounds(inner_viewport_size);
root->SetScrollClipLayer(root_clip->id());
root->SetBounds(outer_viewport_size);
root->SetContentBounds(outer_viewport_size);
root->SetPosition(gfx::PointF());
root->SetDrawsContent(false);
root->SetIsContainerForFixedPositionLayers(true);
outer_clip->SetBounds(outer_viewport_size);
outer_scroll->SetScrollClipLayer(outer_clip->id());
outer_scroll->SetBounds(scroll_layer_size);
outer_scroll->SetContentBounds(scroll_layer_size);
outer_scroll->SetPosition(gfx::PointF());
outer_scroll->SetDrawsContent(false);
outer_scroll->SetIsContainerForFixedPositionLayers(true);
int inner_viewport_scroll_layer_id = root->id();
int outer_viewport_scroll_layer_id = outer_scroll->id();
int page_scale_layer_id = page_scale->id();
outer_clip->AddChild(outer_scroll.Pass());
root->AddChild(outer_clip.Pass());
page_scale->AddChild(root.Pass());
root_clip->AddChild(page_scale.Pass());
host_impl_->active_tree()->SetRootLayer(root_clip.Pass());
host_impl_->active_tree()->SetViewportLayersFromIds(
page_scale_layer_id,
inner_viewport_scroll_layer_id,
outer_viewport_scroll_layer_id);
host_impl_->SetViewportSize(inner_viewport_size);
host_impl_->SetTopControlsLayoutHeight(
settings_.top_controls_height);
LayerImpl* root_clip_ptr = host_impl_->active_tree()->root_layer();
EXPECT_EQ(inner_viewport_size, root_clip_ptr->bounds());
}
protected:
gfx::Size layer_size_;
gfx::Size clip_size_;
......@@ -2470,6 +2527,156 @@ TEST_F(LayerTreeHostImplTopControlsTest, TopControlsLayoutHeightChanged) {
root_clip_ptr->bounds());
}
// Test that showing/hiding the top controls when the viewport is fully scrolled
// doesn't incorrectly change the viewport offset due to clamping from changing
// viewport bounds.
TEST_F(LayerTreeHostImplTopControlsTest, TopControlsViewportOffsetClamping) {
SetupTopControlsAndScrollLayerWithVirtualViewport(
gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400));
DrawFrame();
EXPECT_EQ(settings_.top_controls_height,
host_impl_->active_tree()->total_top_controls_content_offset());
LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
// Scroll the viewports to max scroll offset.
outer_scroll->SetScrollDelta(gfx::Vector2dF(0, 200.f));
inner_scroll->SetScrollDelta(gfx::Vector2dF(100, 100.f));
gfx::ScrollOffset viewport_offset =
host_impl_->active_tree()->TotalScrollOffset();
EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(), viewport_offset);
// Hide the top controls by 25px.
gfx::Vector2dF scroll_delta(0.f, 25.f);
EXPECT_EQ(InputHandler::ScrollStarted,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
host_impl_->ScrollBy(gfx::Point(), scroll_delta);
host_impl_->ScrollEnd();
EXPECT_EQ(scroll_delta.y(),
settings_.top_controls_height -
host_impl_->active_tree()->total_top_controls_content_offset());
inner_scroll->ClampScrollToMaxScrollOffset();
outer_scroll->ClampScrollToMaxScrollOffset();
// We should still be fully scrolled.
EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(),
host_impl_->active_tree()->TotalScrollOffset());
viewport_offset = host_impl_->active_tree()->TotalScrollOffset();
// Bring the top controls down by 25px.
scroll_delta = gfx::Vector2dF(0.f, -25.f);
EXPECT_EQ(InputHandler::ScrollStarted,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
host_impl_->ScrollBy(gfx::Point(), scroll_delta);
host_impl_->ScrollEnd();
// The viewport offset shouldn't have changed.
EXPECT_EQ(viewport_offset,
host_impl_->active_tree()->TotalScrollOffset());
// Scroll the viewports to max scroll offset.
outer_scroll->SetScrollDelta(gfx::Vector2dF(0, 200.f));
inner_scroll->SetScrollDelta(gfx::Vector2dF(100, 100.f));
EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(),
host_impl_->active_tree()->TotalScrollOffset());
}
// Test that the top controls coming in and out maintains the same aspect ratio
// between the inner and outer viewports.
TEST_F(LayerTreeHostImplTopControlsTest, TopControlsAspectRatio) {
SetupTopControlsAndScrollLayerWithVirtualViewport(
gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400));
DrawFrame();
EXPECT_EQ(settings_.top_controls_height,
host_impl_->active_tree()->total_top_controls_content_offset());
gfx::Vector2dF scroll_delta(0.f, 25.f);
EXPECT_EQ(InputHandler::ScrollStarted,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
host_impl_->ScrollBy(gfx::Point(), scroll_delta);
host_impl_->ScrollEnd();
EXPECT_EQ(scroll_delta.y(),
settings_.top_controls_height -
host_impl_->active_tree()->total_top_controls_content_offset());
// Top controls were hidden by 25px so the inner viewport should have expanded
// by that much.
LayerImpl* outer_container =
host_impl_->active_tree()->OuterViewportContainerLayer();
LayerImpl* inner_container =
host_impl_->active_tree()->InnerViewportContainerLayer();
EXPECT_EQ(gfx::Size(100, 100+25), inner_container->BoundsForScrolling());
// Outer viewport should match inner's aspect ratio. The bounds are ceiled.
float aspect_ratio = inner_container->BoundsForScrolling().width() /
inner_container->BoundsForScrolling().height();
gfx::Size expected = gfx::ToCeiledSize(gfx::SizeF(200, 200 / aspect_ratio));
EXPECT_EQ(expected, outer_container->BoundsForScrolling());
EXPECT_EQ(expected,
host_impl_->InnerViewportScrollLayer()->BoundsForScrolling());
}
// Test that scrolling the outer viewport affects the top controls.
TEST_F(LayerTreeHostImplTopControlsTest, TopControlsScrollOuterViewport) {
SetupTopControlsAndScrollLayerWithVirtualViewport(
gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400));
DrawFrame();
EXPECT_EQ(settings_.top_controls_height,
host_impl_->active_tree()->total_top_controls_content_offset());
// Send a gesture scroll that will scroll the outer viewport, make sure the
// top controls get scrolled.
gfx::Vector2dF scroll_delta(0.f, 15.f);
EXPECT_EQ(InputHandler::ScrollStarted,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
host_impl_->ScrollBy(gfx::Point(), scroll_delta);
EXPECT_EQ(host_impl_->OuterViewportScrollLayer(),
host_impl_->CurrentlyScrollingLayer());
host_impl_->ScrollEnd();
EXPECT_EQ(scroll_delta.y(),
settings_.top_controls_height -
host_impl_->active_tree()->total_top_controls_content_offset());
scroll_delta = gfx::Vector2dF(0.f, 50.f);
EXPECT_EQ(InputHandler::ScrollStarted,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
host_impl_->ScrollBy(gfx::Point(), scroll_delta);
EXPECT_EQ(0, host_impl_->active_tree()->total_top_controls_content_offset());
EXPECT_EQ(host_impl_->OuterViewportScrollLayer(),
host_impl_->CurrentlyScrollingLayer());
host_impl_->ScrollEnd();
// Position the viewports such that the inner viewport will be scrolled.
gfx::Vector2dF inner_viewport_offset(0.f, 25.f);
host_impl_->OuterViewportScrollLayer()->SetScrollDelta(gfx::Vector2dF());
host_impl_->InnerViewportScrollLayer()->SetScrollDelta(inner_viewport_offset);
scroll_delta = gfx::Vector2dF(0.f, -65.f);
EXPECT_EQ(InputHandler::ScrollStarted,
host_impl_->ScrollBegin(gfx::Point(), InputHandler::Gesture));
host_impl_->ScrollBy(gfx::Point(), scroll_delta);
EXPECT_EQ(settings_.top_controls_height,
host_impl_->active_tree()->total_top_controls_content_offset());
EXPECT_EQ(inner_viewport_offset.y() +
(scroll_delta.y() + settings_.top_controls_height),
host_impl_->InnerViewportScrollLayer()->ScrollDelta().y());
host_impl_->ScrollEnd();
}
TEST_F(LayerTreeHostImplTopControlsTest,
ScrollNonScrollableRootWithTopControls) {
SetupTopControlsAndScrollLayer();
......
......@@ -265,6 +265,12 @@ LayerImpl* LayerTreeImpl::InnerViewportContainerLayer() const {
: NULL;
}
LayerImpl* LayerTreeImpl::OuterViewportContainerLayer() const {
return outer_viewport_scroll_layer_
? outer_viewport_scroll_layer_->scroll_clip_layer()
: NULL;
}
LayerImpl* LayerTreeImpl::CurrentlyScrollingLayer() const {
DCHECK(IsActiveTree());
return currently_scrolling_layer_;
......@@ -366,7 +372,7 @@ gfx::SizeF LayerTreeImpl::ScrollableViewportSize() const {
if (!InnerViewportContainerLayer())
return gfx::SizeF();
return gfx::ScaleSize(InnerViewportContainerLayer()->bounds(),
return gfx::ScaleSize(InnerViewportContainerLayer()->BoundsForScrolling(),
1.0f / total_page_scale_factor());
}
......
......@@ -133,6 +133,7 @@ class CC_EXPORT LayerTreeImpl {
gfx::Vector2dF TotalScrollDelta() const;
LayerImpl* InnerViewportContainerLayer() const;
LayerImpl* OuterViewportContainerLayer() const;
LayerImpl* CurrentlyScrollingLayer() const;
void SetCurrentlyScrollingLayer(LayerImpl* layer);
void ClearCurrentlyScrollingLayer();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment