| /* |
| * Copyright (C) 2012 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR |
| * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h" |
| |
| #include "build/build_config.h" |
| #include "cc/layers/picture_layer.h" |
| #include "cc/trees/sticky_position_constraint.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/web_cache.h" |
| #include "third_party/blink/public/platform/web_rect.h" |
| #include "third_party/blink/public/platform/web_url_loader_mock_factory.h" |
| #include "third_party/blink/public/web/web_settings.h" |
| #include "third_party/blink/public/web/web_view_client.h" |
| #include "third_party/blink/renderer/core/css/css_style_sheet.h" |
| #include "third_party/blink/renderer/core/css/style_sheet_list.h" |
| #include "third_party/blink/renderer/core/dom/events/native_event_listener.h" |
| #include "third_party/blink/renderer/core/exported/web_plugin_container_impl.h" |
| #include "third_party/blink/renderer/core/exported/web_view_impl.h" |
| #include "third_party/blink/renderer/core/frame/frame_test_helpers.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/frame/web_frame_widget_base.h" |
| #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" |
| #include "third_party/blink/renderer/core/html/html_iframe_element.h" |
| #include "third_party/blink/renderer/core/html/html_object_element.h" |
| #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator_context.h" |
| #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h" |
| #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/platform/geometry/int_point.h" |
| #include "third_party/blink/renderer/platform/geometry/int_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" |
| #include "third_party/blink/renderer/platform/graphics/test/fake_gles2_interface.h" |
| #include "third_party/blink/renderer/platform/graphics/test/fake_web_graphics_context_3d_provider.h" |
| #include "third_party/blink/renderer/platform/graphics/touch_action.h" |
| #include "third_party/blink/renderer/platform/testing/histogram_tester.h" |
| #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" |
| #include "third_party/blink/renderer/platform/testing/url_test_helpers.h" |
| |
| namespace blink { |
| |
| class ScrollingCoordinatorTest |
| : public testing::Test, |
| public testing::WithParamInterface<bool>, |
| private ScopedPaintNonFastScrollableRegionsForTest { |
| public: |
| ScrollingCoordinatorTest() |
| : ScopedPaintNonFastScrollableRegionsForTest(GetParam()), |
| base_url_("http://www.test.com/") { |
| helper_.Initialize(nullptr, nullptr, nullptr, &ConfigureSettings); |
| GetWebView()->MainFrameWidget()->Resize(IntSize(320, 240)); |
| |
| // macOS attaches main frame scrollbars to the VisualViewport so the |
| // VisualViewport layers need to be initialized. |
| GetWebView()->MainFrameWidget()->UpdateAllLifecyclePhases( |
| WebWidget::LifecycleUpdateReason::kTest); |
| WebFrameWidgetBase* main_frame_widget = |
| GetWebView()->MainFrameImpl()->FrameWidgetImpl(); |
| main_frame_widget->SetRootGraphicsLayer(GetWebView() |
| ->MainFrameImpl() |
| ->GetFrame() |
| ->View() |
| ->GetLayoutView() |
| ->Compositor() |
| ->RootGraphicsLayer()); |
| } |
| |
| ~ScrollingCoordinatorTest() override { |
| Platform::Current() |
| ->GetURLLoaderMockFactory() |
| ->UnregisterAllURLsAndClearMemoryCache(); |
| } |
| |
| void NavigateTo(const std::string& url) { |
| frame_test_helpers::LoadFrame(GetWebView()->MainFrameImpl(), url); |
| } |
| |
| void LoadHTML(const std::string& html) { |
| frame_test_helpers::LoadHTMLString(GetWebView()->MainFrameImpl(), html, |
| url_test_helpers::ToKURL("about:blank")); |
| } |
| |
| void ForceFullCompositingUpdate() { |
| GetWebView()->MainFrameWidget()->UpdateAllLifecyclePhases( |
| WebWidget::LifecycleUpdateReason::kTest); |
| } |
| |
| void RegisterMockedHttpURLLoad(const std::string& file_name) { |
| url_test_helpers::RegisterMockedURLLoadFromBase( |
| WebString::FromUTF8(base_url_), test::CoreTestDataPath(), |
| WebString::FromUTF8(file_name)); |
| } |
| |
| cc::Layer* GetRootScrollLayer() { |
| GraphicsLayer* layer = |
| GetFrame()->View()->LayoutViewport()->LayerForScrolling(); |
| return layer ? layer->CcLayer() : nullptr; |
| } |
| |
| WebViewImpl* GetWebView() const { return helper_.GetWebView(); } |
| LocalFrame* GetFrame() const { return helper_.LocalMainFrame()->GetFrame(); } |
| frame_test_helpers::TestWebWidgetClient* GetWidgetClient() const { |
| return helper_.GetWebWidgetClient(); |
| } |
| |
| void LoadAhem() { helper_.LoadAhem(); } |
| |
| protected: |
| std::string base_url_; |
| |
| private: |
| static void ConfigureSettings(WebSettings* settings) { |
| settings->SetPreferCompositingToLCDTextEnabled(true); |
| } |
| |
| frame_test_helpers::WebViewHelper helper_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, ScrollingCoordinatorTest, testing::Bool()); |
| |
| TEST_P(ScrollingCoordinatorTest, fastScrollingByDefault) { |
| GetWebView()->MainFrameWidget()->Resize(WebSize(800, 600)); |
| LoadHTML("<div id='spacer' style='height: 1000px'></div>"); |
| ForceFullCompositingUpdate(); |
| |
| // Make sure the scrolling coordinator is active. |
| LocalFrameView* frame_view = GetFrame()->View(); |
| Page* page = GetFrame()->GetPage(); |
| ASSERT_TRUE(page->GetScrollingCoordinator()); |
| ASSERT_TRUE(page->GetScrollingCoordinator()->CoordinatesScrollingForFrameView( |
| frame_view)); |
| |
| // Fast scrolling should be enabled by default. |
| cc::Layer* root_scroll_layer = GetRootScrollLayer(); |
| ASSERT_TRUE(root_scroll_layer); |
| ASSERT_TRUE(root_scroll_layer->scrollable()); |
| ASSERT_FALSE(root_scroll_layer->GetMainThreadScrollingReasons()); |
| ASSERT_EQ(cc::EventListenerProperties::kNone, |
| GetWidgetClient()->EventListenerProperties( |
| cc::EventListenerClass::kTouchStartOrMove)); |
| ASSERT_EQ(cc::EventListenerProperties::kNone, |
| GetWidgetClient()->EventListenerProperties( |
| cc::EventListenerClass::kMouseWheel)); |
| |
| cc::Layer* inner_viewport_scroll_layer = |
| page->GetVisualViewport().ScrollLayer()->CcLayer(); |
| ASSERT_TRUE(inner_viewport_scroll_layer->scrollable()); |
| ASSERT_FALSE(inner_viewport_scroll_layer->GetMainThreadScrollingReasons()); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, fastFractionalScrollingDiv) { |
| RegisterMockedHttpURLLoad("fractional-scroll-div.html"); |
| NavigateTo(base_url_ + "fractional-scroll-div.html"); |
| ForceFullCompositingUpdate(); |
| |
| Document* document = GetFrame()->GetDocument(); |
| Element* scrollable_element = document->getElementById("scroller"); |
| DCHECK(scrollable_element); |
| |
| scrollable_element->setScrollTop(1.0); |
| scrollable_element->setScrollLeft(1.0); |
| ForceFullCompositingUpdate(); |
| |
| // Make sure the fractional scroll offset change 1.0 -> 1.2 gets propagated |
| // to compositor. |
| scrollable_element->setScrollTop(1.2); |
| scrollable_element->setScrollLeft(1.2); |
| ForceFullCompositingUpdate(); |
| |
| LayoutObject* layout_object = scrollable_element->GetLayoutObject(); |
| ASSERT_TRUE(layout_object->IsBox()); |
| LayoutBox* box = ToLayoutBox(layout_object); |
| ASSERT_TRUE(box->UsesCompositedScrolling()); |
| CompositedLayerMapping* composited_layer_mapping = |
| box->Layer()->GetCompositedLayerMapping(); |
| ASSERT_TRUE(composited_layer_mapping->HasScrollingLayer()); |
| DCHECK(composited_layer_mapping->ScrollingContentsLayer()); |
| cc::Layer* cc_scroll_layer = |
| composited_layer_mapping->ScrollingContentsLayer()->CcLayer(); |
| ASSERT_TRUE(cc_scroll_layer); |
| ASSERT_NEAR(1.2f, cc_scroll_layer->CurrentScrollOffset().x(), 0.01f); |
| ASSERT_NEAR(1.2f, cc_scroll_layer->CurrentScrollOffset().y(), 0.01f); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, fastScrollingForFixedPosition) { |
| RegisterMockedHttpURLLoad("fixed-position.html"); |
| NavigateTo(base_url_ + "fixed-position.html"); |
| ForceFullCompositingUpdate(); |
| |
| // Fixed position should not fall back to main thread scrolling. |
| cc::Layer* root_scroll_layer = GetRootScrollLayer(); |
| ASSERT_TRUE(root_scroll_layer); |
| ASSERT_FALSE(root_scroll_layer->GetMainThreadScrollingReasons()); |
| } |
| |
| // Sticky constraints are stored on transform property tree nodes. |
| static cc::StickyPositionConstraint GetStickyConstraint(Element* element) { |
| const auto* properties = |
| element->GetLayoutObject()->FirstFragment().PaintProperties(); |
| DCHECK(properties); |
| return *properties->StickyTranslation()->GetStickyConstraint(); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, fastScrollingForStickyPosition) { |
| RegisterMockedHttpURLLoad("sticky-position.html"); |
| NavigateTo(base_url_ + "sticky-position.html"); |
| ForceFullCompositingUpdate(); |
| |
| // Sticky position should not fall back to main thread scrolling. |
| cc::Layer* root_scroll_layer = GetRootScrollLayer(); |
| ASSERT_TRUE(root_scroll_layer); |
| EXPECT_FALSE(root_scroll_layer->GetMainThreadScrollingReasons()); |
| |
| Document* document = GetFrame()->GetDocument(); |
| { |
| Element* element = document->getElementById("div-tl"); |
| auto constraint = GetStickyConstraint(element); |
| EXPECT_TRUE(constraint.is_anchored_top && constraint.is_anchored_left && |
| !constraint.is_anchored_right && |
| !constraint.is_anchored_bottom); |
| EXPECT_EQ(1.f, constraint.top_offset); |
| EXPECT_EQ(1.f, constraint.left_offset); |
| EXPECT_EQ(gfx::Rect(100, 100, 10, 10), |
| constraint.scroll_container_relative_sticky_box_rect); |
| EXPECT_EQ(gfx::Rect(100, 100, 200, 200), |
| constraint.scroll_container_relative_containing_block_rect); |
| } |
| { |
| Element* element = document->getElementById("div-tr"); |
| auto constraint = GetStickyConstraint(element); |
| EXPECT_TRUE(constraint.is_anchored_top && !constraint.is_anchored_left && |
| constraint.is_anchored_right && !constraint.is_anchored_bottom); |
| } |
| { |
| Element* element = document->getElementById("div-bl"); |
| auto constraint = GetStickyConstraint(element); |
| EXPECT_TRUE(!constraint.is_anchored_top && constraint.is_anchored_left && |
| !constraint.is_anchored_right && constraint.is_anchored_bottom); |
| } |
| { |
| Element* element = document->getElementById("div-br"); |
| auto constraint = GetStickyConstraint(element); |
| EXPECT_TRUE(!constraint.is_anchored_top && !constraint.is_anchored_left && |
| constraint.is_anchored_right && constraint.is_anchored_bottom); |
| } |
| { |
| Element* element = document->getElementById("span-tl"); |
| auto constraint = GetStickyConstraint(element); |
| EXPECT_TRUE(constraint.is_anchored_top && constraint.is_anchored_left && |
| !constraint.is_anchored_right && |
| !constraint.is_anchored_bottom); |
| } |
| { |
| Element* element = document->getElementById("span-tlbr"); |
| auto constraint = GetStickyConstraint(element); |
| EXPECT_TRUE(constraint.is_anchored_top && constraint.is_anchored_left && |
| constraint.is_anchored_right && constraint.is_anchored_bottom); |
| EXPECT_EQ(1.f, constraint.top_offset); |
| EXPECT_EQ(1.f, constraint.left_offset); |
| EXPECT_EQ(1.f, constraint.right_offset); |
| EXPECT_EQ(1.f, constraint.bottom_offset); |
| } |
| { |
| Element* element = document->getElementById("composited-top"); |
| auto constraint = GetStickyConstraint(element); |
| EXPECT_TRUE(constraint.is_anchored_top); |
| EXPECT_EQ(gfx::Rect(100, 110, 10, 10), |
| constraint.scroll_container_relative_sticky_box_rect); |
| EXPECT_EQ(gfx::Rect(100, 100, 200, 200), |
| constraint.scroll_container_relative_containing_block_rect); |
| } |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, elementPointerEventHandler) { |
| LoadHTML(R"HTML( |
| <div id="pointer" style="width: 100px; height: 100px;"></div> |
| <script> |
| pointer.addEventListener('pointerdown', function(event) { |
| }, {blocking: false} ); |
| </script> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* layout_view = GetFrame()->View()->GetLayoutView(); |
| auto* mapping = layout_view->Layer()->GetCompositedLayerMapping(); |
| GraphicsLayer* graphics_layer = mapping->ScrollingContentsLayer(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| |
| // Pointer event handlers should not generate blocking touch action regions. |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_TRUE(region.IsEmpty()); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, touchEventHandler) { |
| RegisterMockedHttpURLLoad("touch-event-handler.html"); |
| NavigateTo(base_url_ + "touch-event-handler.html"); |
| ForceFullCompositingUpdate(); |
| |
| ASSERT_EQ(cc::EventListenerProperties::kBlocking, |
| GetWidgetClient()->EventListenerProperties( |
| cc::EventListenerClass::kTouchStartOrMove)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, elementBlockingTouchEventHandler) { |
| LoadHTML(R"HTML( |
| <div id="blocking" style="width: 100px; height: 100px;"></div> |
| <script> |
| blocking.addEventListener('touchstart', function(event) { |
| }, {passive: false} ); |
| </script> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* layout_view = GetFrame()->View()->GetLayoutView(); |
| auto* mapping = layout_view->Layer()->GetCompositedLayerMapping(); |
| cc::Layer* cc_layer = mapping->ScrollingContentsLayer()->CcLayer(); |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_EQ(region.bounds(), gfx::Rect(8, 8, 100, 100)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, touchEventHandlerPassive) { |
| RegisterMockedHttpURLLoad("touch-event-handler-passive.html"); |
| NavigateTo(base_url_ + "touch-event-handler-passive.html"); |
| ForceFullCompositingUpdate(); |
| |
| ASSERT_EQ(cc::EventListenerProperties::kPassive, |
| GetWidgetClient()->EventListenerProperties( |
| cc::EventListenerClass::kTouchStartOrMove)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, elementTouchEventHandlerPassive) { |
| LoadHTML(R"HTML( |
| <div id="passive" style="width: 100px; height: 100px;"></div> |
| <script> |
| passive.addEventListener('touchstart', function(event) { |
| }, {passive: true} ); |
| </script> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* layout_view = GetFrame()->View()->GetLayoutView(); |
| auto* mapping = layout_view->Layer()->GetCompositedLayerMapping(); |
| GraphicsLayer* graphics_layer = mapping->ScrollingContentsLayer(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| |
| // Passive event handlers should not generate blocking touch action regions. |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_TRUE(region.IsEmpty()); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, TouchActionRectsOnImage) { |
| LoadHTML(R"HTML( |
| <img id="image" style="width: 100px; height: 100px; touch-action: none;"> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* layout_view = GetFrame()->View()->GetLayoutView(); |
| auto* mapping = layout_view->Layer()->GetCompositedLayerMapping(); |
| cc::Layer* cc_layer = mapping->ScrollingContentsLayer()->CcLayer(); |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_EQ(region.bounds(), gfx::Rect(8, 8, 100, 100)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, touchEventHandlerBoth) { |
| RegisterMockedHttpURLLoad("touch-event-handler-both.html"); |
| NavigateTo(base_url_ + "touch-event-handler-both.html"); |
| ForceFullCompositingUpdate(); |
| |
| ASSERT_EQ(cc::EventListenerProperties::kBlockingAndPassive, |
| GetWidgetClient()->EventListenerProperties( |
| cc::EventListenerClass::kTouchStartOrMove)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, wheelEventHandler) { |
| RegisterMockedHttpURLLoad("wheel-event-handler.html"); |
| NavigateTo(base_url_ + "wheel-event-handler.html"); |
| ForceFullCompositingUpdate(); |
| |
| ASSERT_EQ(cc::EventListenerProperties::kBlocking, |
| GetWidgetClient()->EventListenerProperties( |
| cc::EventListenerClass::kMouseWheel)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, wheelEventHandlerPassive) { |
| RegisterMockedHttpURLLoad("wheel-event-handler-passive.html"); |
| NavigateTo(base_url_ + "wheel-event-handler-passive.html"); |
| ForceFullCompositingUpdate(); |
| |
| ASSERT_EQ(cc::EventListenerProperties::kPassive, |
| GetWidgetClient()->EventListenerProperties( |
| cc::EventListenerClass::kMouseWheel)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, wheelEventHandlerBoth) { |
| RegisterMockedHttpURLLoad("wheel-event-handler-both.html"); |
| NavigateTo(base_url_ + "wheel-event-handler-both.html"); |
| ForceFullCompositingUpdate(); |
| |
| ASSERT_EQ(cc::EventListenerProperties::kBlockingAndPassive, |
| GetWidgetClient()->EventListenerProperties( |
| cc::EventListenerClass::kMouseWheel)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, scrollEventHandler) { |
| RegisterMockedHttpURLLoad("scroll-event-handler.html"); |
| NavigateTo(base_url_ + "scroll-event-handler.html"); |
| ForceFullCompositingUpdate(); |
| |
| ASSERT_TRUE(GetWidgetClient()->HaveScrollEventHandlers()); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, updateEventHandlersDuringTeardown) { |
| RegisterMockedHttpURLLoad("scroll-event-handler-window.html"); |
| NavigateTo(base_url_ + "scroll-event-handler-window.html"); |
| ForceFullCompositingUpdate(); |
| |
| // Simulate detaching the document from its DOM window. This should not |
| // cause a crash when the WebViewImpl is closed by the test runner. |
| GetFrame()->GetDocument()->Shutdown(); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, clippedBodyTest) { |
| RegisterMockedHttpURLLoad("clipped-body.html"); |
| NavigateTo(base_url_ + "clipped-body.html"); |
| ForceFullCompositingUpdate(); |
| |
| cc::Layer* root_scroll_layer = GetRootScrollLayer(); |
| ASSERT_TRUE(root_scroll_layer); |
| EXPECT_TRUE(root_scroll_layer->non_fast_scrollable_region().IsEmpty()); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, touchAction) { |
| RegisterMockedHttpURLLoad("touch-action.html"); |
| NavigateTo(base_url_ + "touch-action.html"); |
| ForceFullCompositingUpdate(); |
| |
| Element* scrollable_element = |
| GetFrame()->GetDocument()->getElementById("scrollable"); |
| LayoutBox* box = ToLayoutBox(scrollable_element->GetLayoutObject()); |
| ASSERT_TRUE(box->UsesCompositedScrolling()); |
| ASSERT_EQ(kPaintsIntoOwnBacking, box->Layer()->GetCompositingState()); |
| |
| auto* composited_layer_mapping = box->Layer()->GetCompositedLayerMapping(); |
| auto* graphics_layer = composited_layer_mapping->ScrollingContentsLayer(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanX | TouchAction::kTouchActionPanDown); |
| EXPECT_EQ(region.GetRegionComplexity(), 1); |
| EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 1000, 1000)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, touchActionRegions) { |
| RegisterMockedHttpURLLoad("touch-action-regions.html"); |
| NavigateTo(base_url_ + "touch-action-regions.html"); |
| ForceFullCompositingUpdate(); |
| |
| Element* scrollable_element = |
| GetFrame()->GetDocument()->getElementById("scrollable"); |
| LayoutBox* box = ToLayoutBox(scrollable_element->GetLayoutObject()); |
| ASSERT_TRUE(box->UsesCompositedScrolling()); |
| ASSERT_EQ(kPaintsIntoOwnBacking, box->Layer()->GetCompositingState()); |
| |
| auto* composited_layer_mapping = box->Layer()->GetCompositedLayerMapping(); |
| auto* graphics_layer = composited_layer_mapping->ScrollingContentsLayer(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanDown | TouchAction::kTouchActionPanX); |
| EXPECT_EQ(region.GetRegionComplexity(), 1); |
| EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 100, 100)); |
| |
| region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanDown | TouchAction::kTouchActionPanRight); |
| EXPECT_EQ(region.GetRegionComplexity(), 1); |
| EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 50, 50)); |
| |
| region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanDown); |
| EXPECT_EQ(region.GetRegionComplexity(), 1); |
| EXPECT_EQ(region.bounds(), gfx::Rect(0, 100, 100, 100)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, touchActionNesting) { |
| LoadHTML(R"HTML( |
| <style> |
| #scrollable { |
| width: 200px; |
| height: 200px; |
| overflow: scroll; |
| } |
| #touchaction { |
| touch-action: pan-x; |
| width: 100px; |
| height: 100px; |
| margin: 5px; |
| } |
| #child { |
| width: 150px; |
| height: 50px; |
| } |
| </style> |
| <div id="scrollable"> |
| <div id="touchaction"> |
| <div id="child"></div> |
| </div> |
| <div id="forcescroll" style="width: 1000px; height: 1000px;"></div> |
| </div> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* scrollable = GetFrame()->GetDocument()->getElementById("scrollable"); |
| auto* box = ToLayoutBox(scrollable->GetLayoutObject()); |
| auto* composited_layer_mapping = box->Layer()->GetCompositedLayerMapping(); |
| auto* graphics_layer = composited_layer_mapping->ScrollingContentsLayer(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanX); |
| EXPECT_EQ(region.GetRegionComplexity(), 2); |
| EXPECT_EQ(region.bounds(), gfx::Rect(5, 5, 150, 100)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, nestedTouchActionInvalidation) { |
| LoadHTML(R"HTML( |
| <style> |
| #scrollable { |
| width: 200px; |
| height: 200px; |
| overflow: scroll; |
| } |
| #touchaction { |
| touch-action: pan-x; |
| width: 100px; |
| height: 100px; |
| margin: 5px; |
| } |
| #child { |
| width: 150px; |
| height: 50px; |
| } |
| </style> |
| <div id="scrollable"> |
| <div id="touchaction"> |
| <div id="child"></div> |
| </div> |
| <div id="forcescroll" style="width: 1000px; height: 1000px;"></div> |
| </div> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* scrollable = GetFrame()->GetDocument()->getElementById("scrollable"); |
| auto* box = ToLayoutBox(scrollable->GetLayoutObject()); |
| auto* composited_layer_mapping = box->Layer()->GetCompositedLayerMapping(); |
| auto* graphics_layer = composited_layer_mapping->ScrollingContentsLayer(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanX); |
| EXPECT_EQ(region.GetRegionComplexity(), 2); |
| EXPECT_EQ(region.bounds(), gfx::Rect(5, 5, 150, 100)); |
| |
| scrollable->setAttribute("style", "touch-action: none", ASSERT_NO_EXCEPTION); |
| ForceFullCompositingUpdate(); |
| region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanX); |
| EXPECT_TRUE(region.IsEmpty()); |
| } |
| |
| // Similar to nestedTouchActionInvalidation but tests that an ancestor with |
| // touch-action: pan-x and a descendant with touch-action: pan-y results in a |
| // touch-action rect of none for the descendant. |
| TEST_P(ScrollingCoordinatorTest, nestedTouchActionChangesUnion) { |
| LoadHTML(R"HTML( |
| <style> |
| #ancestor { |
| width: 100px; |
| height: 100px; |
| } |
| #child { |
| touch-action: pan-x; |
| width: 150px; |
| height: 50px; |
| } |
| </style> |
| <div id="ancestor"> |
| <div id="child"></div> |
| </div> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* layout_view = GetFrame()->View()->GetLayoutView(); |
| auto* mapping = layout_view->Layer()->GetCompositedLayerMapping(); |
| GraphicsLayer* graphics_layer = mapping->ScrollingContentsLayer(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanX); |
| EXPECT_EQ(region.bounds(), gfx::Rect(8, 8, 150, 50)); |
| region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_TRUE(region.IsEmpty()); |
| |
| Element* ancestor = GetFrame()->GetDocument()->getElementById("ancestor"); |
| ancestor->setAttribute("style", "touch-action: pan-y", ASSERT_NO_EXCEPTION); |
| ForceFullCompositingUpdate(); |
| |
| region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanY); |
| EXPECT_EQ(region.bounds(), gfx::Rect(8, 8, 100, 100)); |
| region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanX); |
| EXPECT_TRUE(region.IsEmpty()); |
| region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_EQ(region.bounds(), gfx::Rect(8, 8, 150, 50)); |
| } |
| |
| // Box shadow is not hit testable and should not be included in touch action. |
| TEST_P(ScrollingCoordinatorTest, touchActionExcludesBoxShadow) { |
| LoadHTML(R"HTML( |
| <style> |
| #shadow { |
| width: 100px; |
| height: 100px; |
| touch-action: none; |
| box-shadow: 10px 5px 5px red; |
| } |
| </style> |
| <div id="shadow"></div> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* layout_view = GetFrame()->View()->GetLayoutView(); |
| auto* mapping = layout_view->Layer()->GetCompositedLayerMapping(); |
| GraphicsLayer* graphics_layer = mapping->ScrollingContentsLayer(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_EQ(region.bounds(), gfx::Rect(8, 8, 100, 100)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, touchActionOnInline) { |
| RegisterMockedHttpURLLoad("touch-action-on-inline.html"); |
| NavigateTo(base_url_ + "touch-action-on-inline.html"); |
| LoadAhem(); |
| ForceFullCompositingUpdate(); |
| |
| auto* layout_view = GetFrame()->View()->GetLayoutView(); |
| auto* mapping = layout_view->Layer()->GetCompositedLayerMapping(); |
| GraphicsLayer* graphics_layer = mapping->ScrollingContentsLayer(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_EQ(region.bounds(), gfx::Rect(8, 8, 80, 50)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, touchActionWithVerticalRLWritingMode) { |
| RegisterMockedHttpURLLoad("touch-action-with-vertical-rl-writing-mode.html"); |
| NavigateTo(base_url_ + "touch-action-with-vertical-rl-writing-mode.html"); |
| LoadAhem(); |
| ForceFullCompositingUpdate(); |
| |
| auto* layout_view = GetFrame()->View()->GetLayoutView(); |
| auto* mapping = layout_view->Layer()->GetCompositedLayerMapping(); |
| GraphicsLayer* graphics_layer = mapping->ScrollingContentsLayer(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_EQ(region.bounds(), gfx::Rect(292, 8, 20, 80)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, touchActionBlockingHandler) { |
| RegisterMockedHttpURLLoad("touch-action-blocking-handler.html"); |
| NavigateTo(base_url_ + "touch-action-blocking-handler.html"); |
| ForceFullCompositingUpdate(); |
| |
| Element* scrollable_element = |
| GetFrame()->GetDocument()->getElementById("scrollable"); |
| LayoutBox* box = ToLayoutBox(scrollable_element->GetLayoutObject()); |
| ASSERT_TRUE(box->UsesCompositedScrolling()); |
| ASSERT_EQ(kPaintsIntoOwnBacking, box->Layer()->GetCompositingState()); |
| |
| auto* composited_layer_mapping = box->Layer()->GetCompositedLayerMapping(); |
| auto* graphics_layer = composited_layer_mapping->ScrollingContentsLayer(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_EQ(region.GetRegionComplexity(), 1); |
| EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 100, 100)); |
| |
| region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanY); |
| EXPECT_EQ(region.GetRegionComplexity(), 2); |
| EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 1000, 1000)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, touchActionOnScrollingElement) { |
| LoadHTML(R"HTML( |
| <style> |
| #scrollable { |
| width: 100px; |
| height: 100px; |
| overflow: scroll; |
| touch-action: pan-y; |
| } |
| #child { |
| width: 50px; |
| height: 150px; |
| } |
| </style> |
| <div id="scrollable"> |
| <div id="child"></div> |
| </div> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| Element* scrollable_element = |
| GetFrame()->GetDocument()->getElementById("scrollable"); |
| LayoutBox* box = ToLayoutBox(scrollable_element->GetLayoutObject()); |
| auto* composited_layer_mapping = box->Layer()->GetCompositedLayerMapping(); |
| |
| // The outer layer (not scrollable) will be fully marked as pan-y (100x100) |
| // and the scrollable layer will only have the contents marked as pan-y |
| // (50x150). |
| auto* scrolling_contents_layer = |
| composited_layer_mapping->ScrollingContentsLayer()->CcLayer(); |
| cc::Region region = |
| scrolling_contents_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanY); |
| EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 50, 150)); |
| |
| auto* non_scrolling_layer = |
| composited_layer_mapping->MainGraphicsLayer()->CcLayer(); |
| region = non_scrolling_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionPanY); |
| EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 100, 100)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, IframeWindowTouchHandler) { |
| LoadHTML( |
| R"(<iframe style="width: 275px; height: 250px;"></iframe>)"); |
| auto* child_frame = |
| To<WebLocalFrameImpl>(GetWebView()->MainFrameImpl()->FirstChild()); |
| frame_test_helpers::LoadHTMLString(child_frame, R"HTML( |
| <p style="margin: 1000px"> Hello </p> |
| <script> |
| window.addEventListener('touchstart', (e) => { |
| e.preventDefault(); |
| }, {passive: false}); |
| </script> |
| )HTML", |
| url_test_helpers::ToKURL("about:blank")); |
| ForceFullCompositingUpdate(); |
| |
| PaintLayer* paint_layer_child_frame = |
| child_frame->GetFrame()->GetDocument()->GetLayoutView()->Layer(); |
| auto* child_mapping = paint_layer_child_frame->GetCompositedLayerMapping(); |
| // Touch action regions are stored on the layer that draws the background. |
| auto* child_graphics_layer = child_mapping->ScrollingContentsLayer(); |
| |
| cc::Region region_child_frame = |
| child_graphics_layer->CcLayer() |
| ->touch_action_region() |
| .GetRegionForTouchAction(TouchAction::kTouchActionNone); |
| PaintLayer* paint_layer_main_frame = GetWebView() |
| ->MainFrameImpl() |
| ->GetFrame() |
| ->GetDocument() |
| ->GetLayoutView() |
| ->Layer(); |
| cc::Region region_main_frame = |
| paint_layer_main_frame |
| ->EnclosingLayerForPaintInvalidationCrossingFrameBoundaries() |
| ->GraphicsLayerBacking(&paint_layer_main_frame->GetLayoutObject()) |
| ->CcLayer() |
| ->touch_action_region() |
| .GetRegionForTouchAction(TouchAction::kTouchActionNone); |
| EXPECT_TRUE(region_main_frame.bounds().IsEmpty()); |
| EXPECT_FALSE(region_child_frame.bounds().IsEmpty()); |
| // We only check for the content size for verification as the offset is 0x0 |
| // due to child frame having its own composited layer. |
| |
| // Because touch action rects are painted on the scrolling contents layer, |
| // the size of the rect should be equal to the entire scrolling contents area. |
| EXPECT_EQ(child_graphics_layer->Size(), |
| gfx::Size(region_child_frame.bounds().size())); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, WindowTouchEventHandler) { |
| LoadHTML(R"HTML( |
| <style> |
| html { width: 200px; height: 200px; } |
| body { width: 100px; height: 100px; } |
| </style> |
| <script> |
| window.addEventListener('touchstart', function(event) { |
| event.preventDefault(); |
| }, {passive: false} ); |
| </script> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* layout_view = GetFrame()->View()->GetLayoutView(); |
| auto* mapping = layout_view->Layer()->GetCompositedLayerMapping(); |
| // Touch action regions are stored on the layer that draws the background. |
| auto* graphics_layer = mapping->ScrollingContentsLayer(); |
| |
| // The touch action region should include the entire frame, even though the |
| // document is smaller than the frame. |
| cc::Region region = |
| graphics_layer->CcLayer()->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 320, 240)); |
| } |
| |
| namespace { |
| class ScrollingCoordinatorMockEventListener final : public NativeEventListener { |
| public: |
| void Invoke(ExecutionContext*, Event*) override {} |
| }; |
| } // namespace |
| |
| TEST_P(ScrollingCoordinatorTest, WindowTouchEventHandlerInvalidation) { |
| LoadHTML(""); |
| ForceFullCompositingUpdate(); |
| |
| auto* layout_view = GetFrame()->View()->GetLayoutView(); |
| auto* mapping = layout_view->Layer()->GetCompositedLayerMapping(); |
| // Touch action regions are stored on the layer that draws the background. |
| auto* graphics_layer = mapping->ScrollingContentsLayer(); |
| auto* cc_layer = graphics_layer->CcLayer(); |
| |
| // Initially there are no touch action regions. |
| auto region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_TRUE(region.IsEmpty()); |
| |
| // Adding a blocking window event handler should create a touch action region. |
| auto* listener = |
| MakeGarbageCollected<ScrollingCoordinatorMockEventListener>(); |
| auto* resolved_options = |
| MakeGarbageCollected<AddEventListenerOptionsResolved>(); |
| resolved_options->setPassive(false); |
| GetFrame()->DomWindow()->addEventListener(event_type_names::kTouchstart, |
| listener, resolved_options); |
| ForceFullCompositingUpdate(); |
| region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_FALSE(region.IsEmpty()); |
| |
| // Removing the window event handler also removes the blocking touch action |
| // region. |
| GetFrame()->DomWindow()->RemoveAllEventListeners(); |
| ForceFullCompositingUpdate(); |
| region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_TRUE(region.IsEmpty()); |
| } |
| |
| // Ensure we don't crash when a plugin becomes a LayoutInline |
| TEST_P(ScrollingCoordinatorTest, PluginBecomesLayoutInline) { |
| LoadHTML(R"HTML( |
| <style> |
| body { |
| margin: 0; |
| height: 3000px; |
| } |
| </style> |
| <object id="plugin" type="appilcation/x-webkit-test-plugin"></object> |
| <script> |
| document.getElementById("plugin") |
| .appendChild(document.createElement("label")) |
| </script> |
| )HTML"); |
| |
| // This test passes if it doesn't crash. We're trying to make sure |
| // ScrollingCoordinator can deal with LayoutInline plugins when generating |
| // NonFastScrollableRegions. |
| HTMLObjectElement* plugin = |
| ToHTMLObjectElement(GetFrame()->GetDocument()->getElementById("plugin")); |
| ASSERT_TRUE(plugin->GetLayoutObject()->IsLayoutInline()); |
| ForceFullCompositingUpdate(); |
| } |
| |
| // Ensure NonFastScrollableRegions are correctly generated for both fixed and |
| // in-flow plugins that need them. |
| TEST_P(ScrollingCoordinatorTest, NonFastScrollableRegionsForPlugins) { |
| LoadHTML(R"HTML( |
| <style> |
| body { |
| margin: 0; |
| height: 3000px; |
| } |
| #plugin { |
| width: 300px; |
| height: 300px; |
| } |
| #pluginfixed { |
| width: 200px; |
| height: 200px; |
| } |
| #fixed { |
| position: fixed; |
| top: 500px; |
| } |
| </style> |
| <div id="fixed"> |
| <object id="pluginfixed" type="application/x-webkit-test-plugin"></object> |
| </div> |
| <object id="plugin" type="application/x-webkit-test-plugin"></object> |
| )HTML"); |
| |
| HTMLObjectElement* plugin = |
| ToHTMLObjectElement(GetFrame()->GetDocument()->getElementById("plugin")); |
| HTMLObjectElement* plugin_fixed = ToHTMLObjectElement( |
| GetFrame()->GetDocument()->getElementById("pluginfixed")); |
| // NonFastScrollableRegions are generated for plugins that require wheel |
| // events. |
| plugin->OwnedPlugin()->SetWantsWheelEvents(true); |
| plugin_fixed->OwnedPlugin()->SetWantsWheelEvents(true); |
| |
| ForceFullCompositingUpdate(); |
| |
| if (!RuntimeEnabledFeatures::PaintNonFastScrollableRegionsEnabled()) { |
| Region scrolling; |
| Region fixed; |
| Page* page = GetFrame()->GetPage(); |
| page->GetScrollingCoordinator() |
| ->ComputeShouldHandleScrollGestureOnMainThreadRegion( |
| To<LocalFrame>(page->MainFrame()), &scrolling, &fixed); |
| |
| EXPECT_TRUE(scrolling.IsRect()); |
| EXPECT_TRUE(fixed.IsRect()); |
| EXPECT_EQ(scrolling.Rects().at(0), IntRect(0, 0, 300, 300)); |
| EXPECT_EQ(fixed.Rects().at(0), IntRect(0, 500, 200, 200)); |
| } |
| |
| // The non-fixed plugin should create a non-fast scrollable region in the |
| // scrolling contents layer of the LayoutView. |
| auto* layout_viewport = GetFrame()->View()->LayoutViewport(); |
| auto* mapping = layout_viewport->Layer()->GetCompositedLayerMapping(); |
| auto* viewport_non_fast_layer = mapping->ScrollingContentsLayer()->CcLayer(); |
| EXPECT_EQ(viewport_non_fast_layer->non_fast_scrollable_region().bounds(), |
| gfx::Rect(0, 0, 300, 300)); |
| |
| // The fixed plugin should create a non-fast scrollable region in a fixed |
| // cc::Layer. |
| if (!RuntimeEnabledFeatures::PaintNonFastScrollableRegionsEnabled()) { |
| // The fixed non-fast region should be on the visual viewport's scrolling |
| // layer. This is not correct in all cases and is a restriction of the |
| // pre-PaintNonFsatScrollableRegions code. |
| auto* non_fast_layer = |
| GetFrame()->GetPage()->GetVisualViewport().ScrollLayer()->CcLayer(); |
| EXPECT_EQ(non_fast_layer->non_fast_scrollable_region().bounds(), |
| gfx::Rect(0, 500, 200, 200)); |
| } else { |
| auto* fixed = GetFrame()->GetDocument()->getElementById("fixed"); |
| auto* fixed_object = ToLayoutBox(fixed->GetLayoutObject()); |
| auto* fixed_graphics_layer = |
| fixed_object->EnclosingLayer()->GraphicsLayerBacking(fixed_object); |
| EXPECT_EQ( |
| fixed_graphics_layer->CcLayer()->non_fast_scrollable_region().bounds(), |
| gfx::Rect(0, 0, 200, 200)); |
| } |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, NonFastScrollableRegionWithBorder) { |
| GetWebView()->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled( |
| false); |
| LoadHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| body { margin: 0; } |
| #scroller { |
| height: 100px; |
| width: 100px; |
| overflow-y: scroll; |
| border: 10px solid black; |
| } |
| </style> |
| <div id="scroller"> |
| <div id="forcescroll" style="height: 1000px;"></div> |
| </div> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| // The non-fast scrollable regions are stored on different layers with and |
| // without PaintNonFastScrollableRegions. This test is only interested in |
| // the dimensions of the non-fast region generated. |
| cc::Layer* non_fast_layer = nullptr; |
| if (!RuntimeEnabledFeatures::PaintNonFastScrollableRegionsEnabled()) { |
| Page* page = GetFrame()->GetPage(); |
| non_fast_layer = page->GetVisualViewport().ScrollLayer()->CcLayer(); |
| } else { |
| non_fast_layer = |
| GetFrame()->View()->LayoutViewport()->LayerForScrolling()->CcLayer(); |
| } |
| |
| EXPECT_EQ(non_fast_layer->non_fast_scrollable_region().bounds(), |
| gfx::Rect(0, 0, 120, 120)); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, overflowScrolling) { |
| RegisterMockedHttpURLLoad("overflow-scrolling.html"); |
| NavigateTo(base_url_ + "overflow-scrolling.html"); |
| ForceFullCompositingUpdate(); |
| |
| // Verify the properties of the accelerated scrolling element starting from |
| // the LayoutObject all the way to the cc::Layer. |
| Element* scrollable_element = |
| GetFrame()->GetDocument()->getElementById("scrollable"); |
| DCHECK(scrollable_element); |
| |
| LayoutObject* layout_object = scrollable_element->GetLayoutObject(); |
| ASSERT_TRUE(layout_object->IsBox()); |
| ASSERT_TRUE(layout_object->HasLayer()); |
| |
| LayoutBox* box = ToLayoutBox(layout_object); |
| ASSERT_TRUE(box->UsesCompositedScrolling()); |
| ASSERT_EQ(kPaintsIntoOwnBacking, box->Layer()->GetCompositingState()); |
| |
| CompositedLayerMapping* composited_layer_mapping = |
| box->Layer()->GetCompositedLayerMapping(); |
| ASSERT_TRUE(composited_layer_mapping->HasScrollingLayer()); |
| DCHECK(composited_layer_mapping->ScrollingContentsLayer()); |
| |
| cc::Layer* cc_scroll_layer = |
| composited_layer_mapping->ScrollingContentsLayer()->CcLayer(); |
| ASSERT_TRUE(cc_scroll_layer->scrollable()); |
| ASSERT_TRUE(cc_scroll_layer->GetUserScrollableHorizontal()); |
| ASSERT_TRUE(cc_scroll_layer->GetUserScrollableVertical()); |
| |
| #if defined(OS_ANDROID) |
| // Now verify we've attached impl-side scrollbars onto the scrollbar layers |
| ASSERT_TRUE(composited_layer_mapping->LayerForHorizontalScrollbar()); |
| ASSERT_TRUE(composited_layer_mapping->LayerForHorizontalScrollbar() |
| ->HasContentsLayer()); |
| ASSERT_TRUE(composited_layer_mapping->LayerForVerticalScrollbar()); |
| ASSERT_TRUE(composited_layer_mapping->LayerForVerticalScrollbar() |
| ->HasContentsLayer()); |
| #endif |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, overflowHidden) { |
| RegisterMockedHttpURLLoad("overflow-hidden.html"); |
| NavigateTo(base_url_ + "overflow-hidden.html"); |
| ForceFullCompositingUpdate(); |
| |
| // Verify the properties of the accelerated scrolling element starting from |
| // the LayoutObject all the way to the cc::Layer. |
| Element* overflow_element = |
| GetFrame()->GetDocument()->getElementById("unscrollable-y"); |
| DCHECK(overflow_element); |
| |
| LayoutObject* layout_object = overflow_element->GetLayoutObject(); |
| ASSERT_TRUE(layout_object->IsBox()); |
| ASSERT_TRUE(layout_object->HasLayer()); |
| |
| LayoutBox* box = ToLayoutBox(layout_object); |
| ASSERT_TRUE(box->UsesCompositedScrolling()); |
| ASSERT_EQ(kPaintsIntoOwnBacking, box->Layer()->GetCompositingState()); |
| |
| CompositedLayerMapping* composited_layer_mapping = |
| box->Layer()->GetCompositedLayerMapping(); |
| ASSERT_TRUE(composited_layer_mapping->HasScrollingLayer()); |
| DCHECK(composited_layer_mapping->ScrollingContentsLayer()); |
| |
| cc::Layer* cc_scroll_layer = |
| composited_layer_mapping->ScrollingContentsLayer()->CcLayer(); |
| ASSERT_TRUE(cc_scroll_layer->scrollable()); |
| ASSERT_TRUE(cc_scroll_layer->GetUserScrollableHorizontal()); |
| ASSERT_FALSE(cc_scroll_layer->GetUserScrollableVertical()); |
| |
| overflow_element = |
| GetFrame()->GetDocument()->getElementById("unscrollable-x"); |
| DCHECK(overflow_element); |
| |
| layout_object = overflow_element->GetLayoutObject(); |
| ASSERT_TRUE(layout_object->IsBox()); |
| ASSERT_TRUE(layout_object->HasLayer()); |
| |
| box = ToLayoutBox(layout_object); |
| ASSERT_TRUE(box->GetScrollableArea()->UsesCompositedScrolling()); |
| ASSERT_EQ(kPaintsIntoOwnBacking, box->Layer()->GetCompositingState()); |
| |
| composited_layer_mapping = box->Layer()->GetCompositedLayerMapping(); |
| ASSERT_TRUE(composited_layer_mapping->HasScrollingLayer()); |
| DCHECK(composited_layer_mapping->ScrollingContentsLayer()); |
| |
| cc_scroll_layer = |
| composited_layer_mapping->ScrollingContentsLayer()->CcLayer(); |
| ASSERT_TRUE(cc_scroll_layer->scrollable()); |
| ASSERT_FALSE(cc_scroll_layer->GetUserScrollableHorizontal()); |
| ASSERT_TRUE(cc_scroll_layer->GetUserScrollableVertical()); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, iframeScrolling) { |
| RegisterMockedHttpURLLoad("iframe-scrolling.html"); |
| RegisterMockedHttpURLLoad("iframe-scrolling-inner.html"); |
| NavigateTo(base_url_ + "iframe-scrolling.html"); |
| ForceFullCompositingUpdate(); |
| |
| // Verify the properties of the accelerated scrolling element starting from |
| // the LayoutObject all the way to the cc::Layer. |
| Element* scrollable_frame = |
| GetFrame()->GetDocument()->getElementById("scrollable"); |
| ASSERT_TRUE(scrollable_frame); |
| |
| LayoutObject* layout_object = scrollable_frame->GetLayoutObject(); |
| ASSERT_TRUE(layout_object); |
| ASSERT_TRUE(layout_object->IsLayoutEmbeddedContent()); |
| |
| LayoutEmbeddedContent* layout_embedded_content = |
| ToLayoutEmbeddedContent(layout_object); |
| ASSERT_TRUE(layout_embedded_content); |
| |
| LocalFrameView* inner_frame_view = |
| To<LocalFrameView>(layout_embedded_content->ChildFrameView()); |
| ASSERT_TRUE(inner_frame_view); |
| |
| auto* inner_layout_view = inner_frame_view->GetLayoutView(); |
| ASSERT_TRUE(inner_layout_view); |
| |
| PaintLayerCompositor* inner_compositor = inner_layout_view->Compositor(); |
| ASSERT_TRUE(inner_compositor->InCompositingMode()); |
| |
| GraphicsLayer* scroll_layer = |
| inner_frame_view->LayoutViewport()->LayerForScrolling(); |
| ASSERT_TRUE(scroll_layer); |
| |
| cc::Layer* cc_scroll_layer = scroll_layer->CcLayer(); |
| ASSERT_TRUE(cc_scroll_layer->scrollable()); |
| |
| #if defined(OS_ANDROID) |
| // Now verify we've attached impl-side scrollbars onto the scrollbar layers |
| GraphicsLayer* horizontal_scrollbar_layer = |
| inner_frame_view->LayoutViewport()->LayerForHorizontalScrollbar(); |
| ASSERT_TRUE(horizontal_scrollbar_layer); |
| ASSERT_TRUE(horizontal_scrollbar_layer->HasContentsLayer()); |
| GraphicsLayer* vertical_scrollbar_layer = |
| inner_frame_view->LayoutViewport()->LayerForVerticalScrollbar(); |
| ASSERT_TRUE(vertical_scrollbar_layer); |
| ASSERT_TRUE(vertical_scrollbar_layer->HasContentsLayer()); |
| #endif |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, rtlIframe) { |
| RegisterMockedHttpURLLoad("rtl-iframe.html"); |
| RegisterMockedHttpURLLoad("rtl-iframe-inner.html"); |
| NavigateTo(base_url_ + "rtl-iframe.html"); |
| ForceFullCompositingUpdate(); |
| |
| // Verify the properties of the accelerated scrolling element starting from |
| // the LayoutObject all the way to the cc::Layer. |
| Element* scrollable_frame = |
| GetFrame()->GetDocument()->getElementById("scrollable"); |
| ASSERT_TRUE(scrollable_frame); |
| |
| LayoutObject* layout_object = scrollable_frame->GetLayoutObject(); |
| ASSERT_TRUE(layout_object); |
| ASSERT_TRUE(layout_object->IsLayoutEmbeddedContent()); |
| |
| LayoutEmbeddedContent* layout_embedded_content = |
| ToLayoutEmbeddedContent(layout_object); |
| ASSERT_TRUE(layout_embedded_content); |
| |
| LocalFrameView* inner_frame_view = |
| To<LocalFrameView>(layout_embedded_content->ChildFrameView()); |
| ASSERT_TRUE(inner_frame_view); |
| |
| auto* inner_layout_view = inner_frame_view->GetLayoutView(); |
| ASSERT_TRUE(inner_layout_view); |
| |
| PaintLayerCompositor* inner_compositor = inner_layout_view->Compositor(); |
| ASSERT_TRUE(inner_compositor->InCompositingMode()); |
| |
| GraphicsLayer* scroll_layer = |
| inner_frame_view->LayoutViewport()->LayerForScrolling(); |
| ASSERT_TRUE(scroll_layer); |
| |
| cc::Layer* cc_scroll_layer = scroll_layer->CcLayer(); |
| ASSERT_TRUE(cc_scroll_layer->scrollable()); |
| |
| int expected_scroll_position = 958 + (inner_frame_view->LayoutViewport() |
| ->VerticalScrollbar() |
| ->IsOverlayScrollbar() |
| ? 0 |
| : 15); |
| ASSERT_EQ(expected_scroll_position, |
| cc_scroll_layer->CurrentScrollOffset().x()); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, setupScrollbarLayerShouldNotCrash) { |
| RegisterMockedHttpURLLoad("setup_scrollbar_layer_crash.html"); |
| NavigateTo(base_url_ + "setup_scrollbar_layer_crash.html"); |
| ForceFullCompositingUpdate(); |
| // This test document setup an iframe with scrollbars, then switch to |
| // an empty document by javascript. |
| } |
| |
| #if defined(OS_MACOSX) || defined(OS_ANDROID) |
| TEST_P(ScrollingCoordinatorTest, |
| DISABLED_setupScrollbarLayerShouldSetScrollLayerOpaque) |
| #else |
| TEST_P(ScrollingCoordinatorTest, setupScrollbarLayerShouldSetScrollLayerOpaque) |
| #endif |
| { |
| RegisterMockedHttpURLLoad("wide_document.html"); |
| NavigateTo(base_url_ + "wide_document.html"); |
| ForceFullCompositingUpdate(); |
| |
| LocalFrameView* frame_view = GetFrame()->View(); |
| ASSERT_TRUE(frame_view); |
| |
| GraphicsLayer* scrollbar_graphics_layer = |
| frame_view->LayoutViewport()->LayerForHorizontalScrollbar(); |
| ASSERT_TRUE(scrollbar_graphics_layer); |
| |
| cc::Layer* platform_layer = scrollbar_graphics_layer->CcLayer(); |
| ASSERT_TRUE(platform_layer); |
| |
| cc::Layer* contents_layer = scrollbar_graphics_layer->ContentsLayer(); |
| ASSERT_TRUE(contents_layer); |
| |
| // After ScrollableAreaScrollbarLayerDidChange(), |
| // if the main frame's scrollbar_layer is opaque, |
| // contents_layer should be opaque too. |
| ASSERT_EQ(platform_layer->contents_opaque(), |
| contents_layer->contents_opaque()); |
| } |
| |
| // LocalFrameView::FrameIsScrollableDidChange is used as a dirty bit and is |
| // set to clean in ScrollingCoordinator::UpdateAfterPaint. This test ensures |
| // that the dirty bit is set and unset properly. |
| TEST_P(ScrollingCoordinatorTest, FrameIsScrollableDidChange) { |
| LoadHTML(R"HTML( |
| <div id='bg' style='background: red; width: 10px; height: 10px;'></div> |
| <div id='forcescroll' style='height: 5000px;'></div> |
| )HTML"); |
| |
| // Initially there is a change but that goes away after a compositing update. |
| EXPECT_TRUE(GetFrame()->View()->FrameIsScrollableDidChange()); |
| ForceFullCompositingUpdate(); |
| EXPECT_FALSE(GetFrame()->View()->FrameIsScrollableDidChange()); |
| |
| // A change to background color should not change the frame's scrollability. |
| auto* background = GetFrame()->GetDocument()->getElementById("bg"); |
| background->removeAttribute(html_names::kStyleAttr); |
| EXPECT_FALSE(GetFrame()->View()->FrameIsScrollableDidChange()); |
| |
| ForceFullCompositingUpdate(); |
| |
| // Making the frame not scroll should change the frame's scrollability. |
| auto* forcescroll = GetFrame()->GetDocument()->getElementById("forcescroll"); |
| forcescroll->removeAttribute(html_names::kStyleAttr); |
| GetFrame()->View()->UpdateLifecycleToLayoutClean(); |
| EXPECT_TRUE(GetFrame()->View()->FrameIsScrollableDidChange()); |
| |
| ForceFullCompositingUpdate(); |
| EXPECT_FALSE(GetFrame()->View()->FrameIsScrollableDidChange()); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, NestedIFramesMainThreadScrollingRegion) { |
| // This page has an absolute IFRAME. It contains a scrollable child DIV |
| // that's nested within an intermediate IFRAME. |
| GetWebView()->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled( |
| false); |
| LoadHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #spacer { |
| height: 10000px; |
| } |
| iframe { |
| position: absolute; |
| top: 1200px; |
| left: 0px; |
| width: 200px; |
| height: 200px; |
| border: 0; |
| } |
| |
| </style> |
| <div id="spacer"></div> |
| <iframe srcdoc=" |
| <!DOCTYPE html> |
| <style> |
| body { margin: 0; } |
| iframe { width: 100px; height: 100px; border: 0; } |
| </style> |
| <iframe srcdoc='<!DOCTYPE html> |
| <style> |
| body { margin: 0; } |
| div { |
| width: 65px; |
| height: 65px; |
| overflow: auto; |
| } |
| p { |
| width: 300px; |
| height: 300px; |
| } |
| </style> |
| <div> |
| <p></p> |
| </div>'> |
| </iframe>"> |
| </iframe> |
| )HTML"); |
| |
| ForceFullCompositingUpdate(); |
| |
| // Scroll the frame to ensure the rect is in the correct coordinate space. |
| GetFrame()->GetDocument()->View()->GetScrollableArea()->SetScrollOffset( |
| ScrollOffset(0, 1000), kProgrammaticScroll); |
| |
| ForceFullCompositingUpdate(); |
| |
| if (!RuntimeEnabledFeatures::PaintNonFastScrollableRegionsEnabled()) { |
| Region scrolling; |
| Region fixed; |
| Page* page = GetFrame()->GetPage(); |
| page->GetScrollingCoordinator() |
| ->ComputeShouldHandleScrollGestureOnMainThreadRegion( |
| To<LocalFrame>(page->MainFrame()), &scrolling, &fixed); |
| |
| EXPECT_TRUE(fixed.IsEmpty()) |
| << "Since the DIV will move when the main frame is scrolled, it should" |
| " not be placed in the fixed region."; |
| |
| EXPECT_EQ(scrolling.Bounds(), IntRect(0, 1200, 65, 65)) |
| << "Since the DIV will move when the main frame is scrolled, it should " |
| "be placed in the scrolling region."; |
| } |
| |
| auto* layout_viewport = GetFrame()->View()->LayoutViewport(); |
| auto* mapping = layout_viewport->Layer()->GetCompositedLayerMapping(); |
| auto* non_fast_layer = mapping->ScrollingContentsLayer()->CcLayer(); |
| EXPECT_EQ(non_fast_layer->non_fast_scrollable_region().bounds(), |
| gfx::Rect(0, 1200, 65, 65)); |
| } |
| |
| // Same as above but test that the rect is correctly calculated into the fixed |
| // region when the containing iframe is position: fixed. |
| TEST_P(ScrollingCoordinatorTest, NestedFixedIFramesMainThreadScrollingRegion) { |
| // This page has a fixed IFRAME. It contains a scrollable child DIV that's |
| // nested within an intermediate IFRAME. |
| GetWebView()->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled( |
| false); |
| LoadHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #spacer { |
| height: 10000px; |
| } |
| #iframe { |
| position: fixed; |
| top: 20px; |
| left: 0px; |
| width: 200px; |
| height: 200px; |
| border: 0; |
| } |
| |
| </style> |
| <div id="spacer"></div> |
| <iframe id="iframe" srcdoc=" |
| <!DOCTYPE html> |
| <style> |
| body { margin: 0; } |
| iframe { width: 100px; height: 100px; border: 0; } |
| </style> |
| <iframe srcdoc='<!DOCTYPE html> |
| <style> |
| body { margin: 0; } |
| div { |
| width: 75px; |
| height: 75px; |
| overflow: auto; |
| } |
| p { |
| width: 300px; |
| height: 300px; |
| } |
| </style> |
| <div> |
| <p></p> |
| </div>'> |
| </iframe>"> |
| </iframe> |
| )HTML"); |
| |
| ForceFullCompositingUpdate(); |
| |
| // Scroll the frame to ensure the rect is in the correct coordinate space. |
| GetFrame()->GetDocument()->View()->GetScrollableArea()->SetScrollOffset( |
| ScrollOffset(0, 1000), kProgrammaticScroll); |
| |
| ForceFullCompositingUpdate(); |
| |
| if (!RuntimeEnabledFeatures::PaintNonFastScrollableRegionsEnabled()) { |
| Region scrolling; |
| Region fixed; |
| Page* page = GetFrame()->GetPage(); |
| page->GetScrollingCoordinator() |
| ->ComputeShouldHandleScrollGestureOnMainThreadRegion( |
| To<LocalFrame>(page->MainFrame()), &scrolling, &fixed); |
| |
| EXPECT_TRUE(scrolling.IsEmpty()) |
| << "Since the DIV will not move when the " |
| "main frame is scrolled, it should " |
| "not be placed in the scrolling region."; |
| |
| EXPECT_EQ(fixed.Bounds(), IntRect(0, 20, 75, 75)) |
| << "Since the DIV not move when the main frame is scrolled, it should " |
| "be placed in the scrolling region."; |
| } |
| |
| if (!RuntimeEnabledFeatures::PaintNonFastScrollableRegionsEnabled()) { |
| // Since the main frame isn't scrollable, the NonFastScrollableRegions |
| // should be stored on the visual viewport's scrolling layer, rather than |
| // the main frame's scrolling contents layer. This is a restriction of the |
| // pre-PaintNonFastScrollableRegions code which only stored non-fast regions |
| // on one scrolling layer, and required using the visual viewport's |
| // scrolling layer to correctly handle some fixed-position cases. |
| auto* non_fast_layer = |
| GetFrame()->GetPage()->GetVisualViewport().ScrollLayer()->CcLayer(); |
| EXPECT_EQ(non_fast_layer->non_fast_scrollable_region().bounds(), |
| gfx::Rect(0, 20, 75, 75)); |
| } else { |
| // PaintNonFastScrollableRegions can put the non-fast scrollable region on |
| // the fixed-position layer. |
| auto* outer_iframe = GetFrame()->GetDocument()->getElementById("iframe"); |
| auto* outer_iframe_box = ToLayoutBox(outer_iframe->GetLayoutObject()); |
| auto* mapping = outer_iframe_box->Layer()->GetCompositedLayerMapping(); |
| auto* non_fast_layer = mapping->MainGraphicsLayer()->CcLayer(); |
| EXPECT_EQ(non_fast_layer->non_fast_scrollable_region().bounds(), |
| gfx::Rect(0, 0, 75, 75)); |
| } |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, IframeCompositedScrollingHideAndShow) { |
| GetWebView()->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled( |
| false); |
| LoadHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| body { |
| margin: 0; |
| } |
| iframe { |
| height: 100px; |
| width: 100px; |
| } |
| </style> |
| <iframe id="iframe" srcdoc=" |
| <!DOCTYPE html> |
| <style> |
| body {height: 1000px;} |
| </style>"></iframe> |
| )HTML"); |
| |
| ForceFullCompositingUpdate(); |
| |
| cc::Layer* non_fast_layer = nullptr; |
| if (!RuntimeEnabledFeatures::PaintNonFastScrollableRegionsEnabled()) { |
| // Since the main frame isn't scrollable, the NonFastScrollableRegions |
| // should be stored on the visual viewport's scrolling layer, rather than |
| // the main frame's scrolling contents layer. This is a restriction of the |
| // pre-PaintNonFastScrollableRegions code which only stored non-fast regions |
| // on one scrolling layer, and required using the visual viewport's |
| // scrolling layer to correctly handle some fixed-position cases. |
| Page* page = GetFrame()->GetPage(); |
| non_fast_layer = page->GetVisualViewport().ScrollLayer()->CcLayer(); |
| } else { |
| non_fast_layer = |
| GetFrame()->View()->LayoutViewport()->LayerForScrolling()->CcLayer(); |
| } |
| |
| // Should have a NFSR initially. |
| EXPECT_EQ(non_fast_layer->non_fast_scrollable_region().bounds(), |
| gfx::Rect(2, 2, 100, 100)); |
| |
| // Hiding the iframe should clear the NFSR. |
| Element* iframe = GetFrame()->GetDocument()->getElementById("iframe"); |
| iframe->setAttribute(html_names::kStyleAttr, "display: none"); |
| ForceFullCompositingUpdate(); |
| EXPECT_TRUE(non_fast_layer->non_fast_scrollable_region().bounds().IsEmpty()); |
| |
| // Showing it again should compute the NFSR. |
| iframe->setAttribute(html_names::kStyleAttr, ""); |
| ForceFullCompositingUpdate(); |
| EXPECT_EQ(non_fast_layer->non_fast_scrollable_region().bounds(), |
| gfx::Rect(2, 2, 100, 100)); |
| } |
| |
| // Same as above but the main frame is scrollable. This should cause the non |
| // fast scrollable regions to go on the outer viewport's scroll layer. |
| TEST_P(ScrollingCoordinatorTest, |
| IframeCompositedScrollingHideAndShowScrollable) { |
| GetWebView()->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled( |
| false); |
| LoadHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| body { |
| height: 1000px; |
| margin: 0; |
| } |
| iframe { |
| height: 100px; |
| width: 100px; |
| } |
| </style> |
| <iframe id="iframe" srcdoc=" |
| <!DOCTYPE html> |
| <style> |
| body {height: 1000px;} |
| </style>"></iframe> |
| )HTML"); |
| |
| ForceFullCompositingUpdate(); |
| |
| Page* page = GetFrame()->GetPage(); |
| cc::Layer* inner_viewport_scroll_layer = |
| page->GetVisualViewport().ScrollLayer()->CcLayer(); |
| Element* iframe = GetFrame()->GetDocument()->getElementById("iframe"); |
| |
| cc::Layer* outer_viewport_scroll_layer = |
| GetFrame()->View()->LayoutViewport()->LayerForScrolling()->CcLayer(); |
| |
| // Should have a NFSR initially. |
| ForceFullCompositingUpdate(); |
| EXPECT_FALSE(outer_viewport_scroll_layer->non_fast_scrollable_region() |
| .bounds() |
| .IsEmpty()); |
| |
| // Ensure the visual viewport's scrolling layer didn't get an NFSR. |
| EXPECT_TRUE(inner_viewport_scroll_layer->non_fast_scrollable_region() |
| .bounds() |
| .IsEmpty()); |
| |
| // Hiding the iframe should clear the NFSR. |
| iframe->setAttribute(html_names::kStyleAttr, "display: none"); |
| ForceFullCompositingUpdate(); |
| EXPECT_TRUE(outer_viewport_scroll_layer->non_fast_scrollable_region() |
| .bounds() |
| .IsEmpty()); |
| |
| // Showing it again should compute the NFSR. |
| iframe->setAttribute(html_names::kStyleAttr, ""); |
| ForceFullCompositingUpdate(); |
| EXPECT_FALSE(outer_viewport_scroll_layer->non_fast_scrollable_region() |
| .bounds() |
| .IsEmpty()); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, ScrollOffsetClobberedBeforeCompositingUpdate) { |
| LoadHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #container { |
| width: 300px; |
| height: 300px; |
| overflow: auto; |
| will-change: transform; |
| } |
| #spacer { |
| height: 1000px; |
| } |
| </style> |
| <div id="container"> |
| <div id="spacer"></div> |
| </div> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| Element* container = GetFrame()->GetDocument()->getElementById("container"); |
| ScrollableArea* scroller = |
| ToLayoutBox(container->GetLayoutObject())->GetScrollableArea(); |
| cc::Layer* cc_layer = scroller->LayerForScrolling()->CcLayer(); |
| |
| ASSERT_EQ(0, scroller->GetScrollOffset().Height()); |
| |
| // Simulate 100px of scroll coming from the compositor thread during a commit. |
| gfx::ScrollOffset compositor_delta(0, 100.f); |
| cc_layer->SetNeedsCommit(); |
| cc_layer->SetScrollOffsetFromImplSide(compositor_delta); |
| EXPECT_EQ(compositor_delta.y(), scroller->GetScrollOffset().Height()); |
| EXPECT_EQ(compositor_delta, cc_layer->CurrentScrollOffset()); |
| |
| // Before updating compositing or the lifecycle, set the scroll offset back |
| // to what it was before the commit from the main thread. |
| scroller->SetScrollOffset(ScrollOffset(0, 0), kProgrammaticScroll); |
| |
| // Ensure the offset is up-to-date on the cc::Layer even though, as far as |
| // the main thread is concerned, it was unchanged since the last time we |
| // pushed the scroll offset. |
| ForceFullCompositingUpdate(); |
| EXPECT_EQ(gfx::ScrollOffset(), cc_layer->CurrentScrollOffset()); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, UpdateVisualViewportScrollLayer) { |
| LoadHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| #box { |
| width: 300px; |
| height: 1000px; |
| background-color: red; |
| } |
| </style> |
| <div id="box"> |
| </div> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| Page* page = GetFrame()->GetPage(); |
| cc::Layer* inner_viewport_scroll_layer = |
| page->GetVisualViewport().ScrollLayer()->CcLayer(); |
| |
| page->GetVisualViewport().SetScale(2); |
| |
| EXPECT_EQ(gfx::ScrollOffset(0, 0), |
| inner_viewport_scroll_layer->CurrentScrollOffset()); |
| |
| page->GetVisualViewport().SetLocation(FloatPoint(10, 20)); |
| |
| EXPECT_EQ(gfx::ScrollOffset(10, 20), |
| inner_viewport_scroll_layer->CurrentScrollOffset()); |
| } |
| |
| TEST_P(ScrollingCoordinatorTest, UpdateUMAMetricUpdated) { |
| HistogramTester histogram_tester; |
| LoadHTML(R"HTML( |
| <div id='bg' style='background: blue;'></div> |
| <div id='scroller' style='overflow: scroll; width: 10px; height: 10px;'> |
| <div id='forcescroll' style='height: 1000px;'></div> |
| </div> |
| )HTML"); |
| |
| // The initial count should be zero. |
| histogram_tester.ExpectTotalCount("Blink.ScrollingCoordinator.UpdateTime", 0); |
| |
| // After an initial compositing update, we should have one scrolling update. |
| ForceFullCompositingUpdate(); |
| histogram_tester.ExpectTotalCount("Blink.ScrollingCoordinator.UpdateTime", 1); |
| |
| // An update with no scrolling changes should not cause a scrolling update. |
| ForceFullCompositingUpdate(); |
| histogram_tester.ExpectTotalCount("Blink.ScrollingCoordinator.UpdateTime", 1); |
| |
| // A change to background color should not cause a scrolling update. |
| auto* background = GetFrame()->GetDocument()->getElementById("bg"); |
| background->removeAttribute(html_names::kStyleAttr); |
| ForceFullCompositingUpdate(); |
| histogram_tester.ExpectTotalCount("Blink.ScrollingCoordinator.UpdateTime", 1); |
| |
| // Removing a scrollable area should cause a scrolling update. |
| auto* scroller = GetFrame()->GetDocument()->getElementById("scroller"); |
| scroller->removeAttribute(html_names::kStyleAttr); |
| ForceFullCompositingUpdate(); |
| histogram_tester.ExpectTotalCount("Blink.ScrollingCoordinator.UpdateTime", 2); |
| } |
| |
| // TODO(pdr): Replace this with ScrollingCoordinatorTest when |
| // PaintNonFastScrollableRegions is launched. |
| using PaintNonFastScrollableRegionsScrollingCoordinatorTest = |
| ScrollingCoordinatorTest; |
| INSTANTIATE_TEST_SUITE_P(All, |
| PaintNonFastScrollableRegionsScrollingCoordinatorTest, |
| ::testing::Values(true)); |
| |
| TEST_P(PaintNonFastScrollableRegionsScrollingCoordinatorTest, |
| NonCompositedNonFastScrollableRegion) { |
| GetWebView()->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled( |
| false); |
| LoadHTML(R"HTML( |
| <!DOCTYPE html> |
| <style> |
| body { margin: 0; } |
| #composited_container { |
| width: 220px; |
| height: 220px; |
| will-change: transform; |
| } |
| #scroller { |
| height: 200px; |
| width: 200px; |
| overflow-y: scroll; |
| } |
| </style> |
| <div id="composited_container"> |
| <div id="scroller"> |
| <div id="forcescroll" style="height: 1000px;"></div> |
| </div> |
| </div> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* container = |
| GetFrame()->GetDocument()->getElementById("composited_container"); |
| auto* layer = ToLayoutBox(container->GetLayoutObject())->Layer(); |
| auto* mapping = layer->GetCompositedLayerMapping(); |
| // The non-scrolling graphics layer should have a non-scrolling region for the |
| // non-composited scroller. |
| cc::Layer* cc_layer = mapping->MainGraphicsLayer()->CcLayer(); |
| auto region = cc_layer->non_fast_scrollable_region(); |
| EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 200, 200)); |
| } |
| |
| TEST_P(PaintNonFastScrollableRegionsScrollingCoordinatorTest, |
| NonCompositedResizerNonFastScrollableRegion) { |
| GetWebView()->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled( |
| false); |
| LoadHTML(R"HTML( |
| <style> |
| #container { will-change: transform; } |
| #scroller { |
| width: 80px; |
| height: 80px; |
| resize: both; |
| overflow-y: scroll; |
| } |
| </style> |
| <div id="container"> |
| <div id="offset" style="height: 35px;"></div> |
| <div id="scroller"></div> |
| </div> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* container_element = |
| GetFrame()->GetDocument()->getElementById("container"); |
| auto* container = ToLayoutBox(container_element->GetLayoutObject()); |
| auto* container_graphics_layer = |
| container->EnclosingLayer()->GraphicsLayerBacking(container); |
| // The non-fast scrollable region should be on the container's graphics layer |
| // and not one of the viewport scroll layers because the region should move |
| // when the container moves and not when the viewport scrolls. |
| auto region = |
| container_graphics_layer->CcLayer()->non_fast_scrollable_region(); |
| EXPECT_EQ(region.bounds(), gfx::Rect(66, 101, 14, 14)); |
| } |
| |
| TEST_P(PaintNonFastScrollableRegionsScrollingCoordinatorTest, |
| CompositedResizerNonFastScrollableRegion) { |
| LoadHTML(R"HTML( |
| <style> |
| #container { will-change: transform; } |
| #scroller { |
| will-change: transform; |
| width: 80px; |
| height: 80px; |
| resize: both; |
| overflow-y: scroll; |
| } |
| </style> |
| <div id="container"> |
| <div id="offset" style="height: 35px;"></div> |
| <div id="scroller"></div> |
| </div> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| auto* scroller_element = |
| GetFrame()->GetDocument()->getElementById("scroller"); |
| auto* scroller = ToLayoutBox(scroller_element->GetLayoutObject()); |
| auto* scroll_corner_graphics_layer = |
| scroller->GetScrollableArea()->LayerForScrollCorner(); |
| auto region = |
| scroll_corner_graphics_layer->CcLayer()->non_fast_scrollable_region(); |
| EXPECT_EQ(region.bounds(), gfx::Rect(-7, -7, 14, 14)); |
| } |
| |
| class ScrollingCoordinatorTestWithAcceleratedContext |
| : public ScrollingCoordinatorTest { |
| public: |
| ScrollingCoordinatorTestWithAcceleratedContext() |
| : ScrollingCoordinatorTest() {} |
| |
| protected: |
| void SetUp() override { |
| auto factory = [](FakeGLES2Interface* gl, bool* gpu_compositing_disabled) |
| -> std::unique_ptr<WebGraphicsContext3DProvider> { |
| *gpu_compositing_disabled = false; |
| gl->SetIsContextLost(false); |
| return std::make_unique<FakeWebGraphicsContext3DProvider>(gl); |
| }; |
| SharedGpuContext::SetContextProviderFactoryForTesting( |
| WTF::BindRepeating(factory, WTF::Unretained(&gl_))); |
| ScrollingCoordinatorTest::SetUp(); |
| } |
| |
| void TearDown() override { |
| SharedGpuContext::ResetForTesting(); |
| ScrollingCoordinatorTest::TearDown(); |
| } |
| |
| private: |
| FakeGLES2Interface gl_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| ScrollingCoordinatorTestWithAcceleratedContext, |
| testing::Bool()); |
| |
| TEST_P(ScrollingCoordinatorTestWithAcceleratedContext, CanvasTouchActionRects) { |
| LoadHTML(R"HTML( |
| <canvas id="canvas" style="touch-action: none; will-change: transform;"> |
| <script> |
| var canvas = document.getElementById("canvas"); |
| var ctx = canvas.getContext("2d"); |
| canvas.width = 400; |
| canvas.height = 400; |
| ctx.fillStyle = 'lightgrey'; |
| ctx.fillRect(0, 0, 400, 400); |
| </script> |
| )HTML"); |
| ForceFullCompositingUpdate(); |
| |
| Element* canvas = GetFrame()->GetDocument()->getElementById("canvas"); |
| auto* canvas_box = ToLayoutBox(canvas->GetLayoutObject()); |
| auto* mapping = canvas_box->Layer()->GetCompositedLayerMapping(); |
| cc::Layer* cc_layer = mapping->MainGraphicsLayer()->CcLayer(); |
| cc::Region region = cc_layer->touch_action_region().GetRegionForTouchAction( |
| TouchAction::kTouchActionNone); |
| EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 400, 400)); |
| } |
| |
| } // namespace blink |