| /* |
| * 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/paint/link_highlight_impl.h" |
| |
| #include <memory> |
| |
| #include "cc/layers/picture_layer.h" |
| #include "cc/trees/layer_tree_host.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/web_float_point.h" |
| #include "third_party/blink/public/platform/web_input_event.h" |
| #include "third_party/blink/public/platform/web_size.h" |
| #include "third_party/blink/public/platform/web_url_loader_mock_factory.h" |
| #include "third_party/blink/public/web/web_frame.h" |
| #include "third_party/blink/public/web/web_local_frame_client.h" |
| #include "third_party/blink/public/web/web_view_client.h" |
| #include "third_party/blink/renderer/core/dom/node.h" |
| #include "third_party/blink/renderer/core/events/web_input_event_conversion.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/link_highlights.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" |
| #include "third_party/blink/renderer/core/input/event_handler.h" |
| #include "third_party/blink/renderer/core/layout/layout_object.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/platform/animation/compositor_animation_timeline.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/geometry/int_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" |
| #include "third_party/blink/renderer/platform/testing/paint_test_configurations.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 LinkHighlightImplTest : public testing::Test, |
| public PaintTestConfigurations { |
| protected: |
| GestureEventWithHitTestResults GetTargetedEvent( |
| WebGestureEvent& touch_event) { |
| WebGestureEvent scaled_event = TransformWebGestureEvent( |
| web_view_helper_.GetWebView()->MainFrameImpl()->GetFrameView(), |
| touch_event); |
| return web_view_helper_.GetWebView() |
| ->GetPage() |
| ->DeprecatedLocalMainFrame() |
| ->GetEventHandler() |
| .TargetGestureEvent(scaled_event, true); |
| } |
| |
| void SetUp() override { |
| WebURL url = url_test_helpers::RegisterMockedURLLoadFromBase( |
| WebString::FromUTF8("http://www.test.com/"), test::CoreTestDataPath(), |
| WebString::FromUTF8("test_touch_link_highlight.html")); |
| web_view_helper_.InitializeAndLoad(url.GetString().Utf8()); |
| } |
| |
| void TearDown() override { |
| Platform::Current() |
| ->GetURLLoaderMockFactory() |
| ->UnregisterAllURLsAndClearMemoryCache(); |
| |
| // Ensure we fully clean up while scoped settings are enabled. Without this, |
| // garbage collection would occur after ScopedBlinkGenPropertyTreesForTest |
| // is out of scope, so the settings would not apply in some destructors. |
| web_view_helper_.Reset(); |
| ThreadState::Current()->CollectAllGarbage(); |
| } |
| |
| size_t ContentLayerCount() { |
| // paint_artifact_compositor()->EnableExtraDataForTesting() should be called |
| // before using this function. |
| DCHECK(paint_artifact_compositor()->GetExtraDataForTesting()); |
| return paint_artifact_compositor() |
| ->GetExtraDataForTesting() |
| ->content_layers.size(); |
| } |
| |
| PaintArtifactCompositor* paint_artifact_compositor() { |
| auto* local_frame_view = web_view_helper_.LocalMainFrame()->GetFrameView(); |
| return local_frame_view->GetPaintArtifactCompositorForTesting(); |
| } |
| |
| void UpdateAllLifecyclePhases() { |
| web_view_helper_.GetWebView()->MainFrameWidget()->UpdateAllLifecyclePhases( |
| WebWidget::LifecycleUpdateReason::kTest); |
| } |
| |
| frame_test_helpers::WebViewHelper web_view_helper_; |
| }; |
| |
| INSTANTIATE_PAINT_TEST_CASE_P(LinkHighlightImplTest); |
| |
| TEST_P(LinkHighlightImplTest, verifyWebViewImplIntegration) { |
| WebViewImpl* web_view_impl = web_view_helper_.GetWebView(); |
| int page_width = 640; |
| int page_height = 480; |
| web_view_impl->MainFrameWidget()->Resize(WebSize(page_width, page_height)); |
| UpdateAllLifecyclePhases(); |
| |
| WebGestureEvent touch_event(WebInputEvent::kGestureShowPress, |
| WebInputEvent::kNoModifiers, |
| WebInputEvent::GetStaticTimeStampForTests(), |
| kWebGestureDeviceTouchscreen); |
| |
| // The coordinates below are linked to absolute positions in the referenced |
| // .html file. |
| touch_event.SetPositionInWidget(WebFloatPoint(20, 20)); |
| |
| ASSERT_TRUE(web_view_impl->BestTapNode(GetTargetedEvent(touch_event))); |
| |
| touch_event.SetPositionInWidget(WebFloatPoint(20, 40)); |
| EXPECT_FALSE(web_view_impl->BestTapNode(GetTargetedEvent(touch_event))); |
| |
| touch_event.SetPositionInWidget(WebFloatPoint(20, 20)); |
| // Shouldn't crash. |
| web_view_impl->EnableTapHighlightAtPoint(GetTargetedEvent(touch_event)); |
| |
| const auto& highlights = |
| web_view_impl->GetPage()->GetLinkHighlights().link_highlights_; |
| EXPECT_TRUE(highlights.at(0)); |
| EXPECT_EQ(1u, highlights.at(0)->FragmentCountForTesting()); |
| EXPECT_TRUE(highlights.at(0)->LayerForTesting(0)); |
| |
| // Find a target inside a scrollable div |
| touch_event.SetPositionInWidget(WebFloatPoint(20, 100)); |
| web_view_impl->EnableTapHighlightAtPoint(GetTargetedEvent(touch_event)); |
| ASSERT_TRUE(highlights.at(0)); |
| |
| // Enesure the timeline was added to a host. |
| EXPECT_TRUE(!!web_view_impl->GetPage() |
| ->GetLinkHighlights() |
| .timeline_->GetAnimationTimeline() |
| ->animation_host()); |
| |
| // Don't highlight if no "hand cursor" |
| touch_event.SetPositionInWidget( |
| WebFloatPoint(20, 220)); // An A-link with cross-hair cursor. |
| web_view_impl->EnableTapHighlightAtPoint(GetTargetedEvent(touch_event)); |
| ASSERT_EQ(0U, highlights.size()); |
| |
| touch_event.SetPositionInWidget(WebFloatPoint(20, 260)); // A text input box. |
| web_view_impl->EnableTapHighlightAtPoint(GetTargetedEvent(touch_event)); |
| ASSERT_EQ(0U, highlights.size()); |
| } |
| |
| TEST_P(LinkHighlightImplTest, resetDuringNodeRemoval) { |
| WebViewImpl* web_view_impl = web_view_helper_.GetWebView(); |
| |
| int page_width = 640; |
| int page_height = 480; |
| web_view_impl->MainFrameWidget()->Resize(WebSize(page_width, page_height)); |
| UpdateAllLifecyclePhases(); |
| |
| WebGestureEvent touch_event(WebInputEvent::kGestureShowPress, |
| WebInputEvent::kNoModifiers, |
| WebInputEvent::GetStaticTimeStampForTests(), |
| kWebGestureDeviceTouchscreen); |
| touch_event.SetPositionInWidget(WebFloatPoint(20, 20)); |
| |
| GestureEventWithHitTestResults targeted_event = GetTargetedEvent(touch_event); |
| Node* touch_node = web_view_impl->BestTapNode(targeted_event); |
| ASSERT_TRUE(touch_node); |
| |
| web_view_impl->EnableTapHighlightAtPoint(targeted_event); |
| const auto& highlights = web_view_impl->GetPage()->GetLinkHighlights(); |
| ASSERT_EQ(1u, highlights.link_highlights_.size()); |
| ASSERT_TRUE(highlights.link_highlights_.at(0)); |
| EXPECT_EQ(touch_node, highlights.link_highlights_.at(0)->GetNode()); |
| |
| GraphicsLayer* highlight_layer; |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| highlight_layer = |
| highlights.link_highlights_.at(0)->CurrentGraphicsLayerForTesting(); |
| ASSERT_TRUE(highlight_layer); |
| EXPECT_TRUE(highlight_layer->GetLinkHighlights().at(0)); |
| } |
| |
| touch_node->remove(IGNORE_EXCEPTION_FOR_TESTING); |
| UpdateAllLifecyclePhases(); |
| |
| ASSERT_EQ(1u, highlights.link_highlights_.size()); |
| ASSERT_TRUE(highlights.link_highlights_.at(0)); |
| EXPECT_FALSE(highlights.link_highlights_.at(0)->GetNode()); |
| |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| EXPECT_EQ(0U, highlight_layer->GetLinkHighlights().size()); |
| EXPECT_FALSE( |
| highlights.link_highlights_.at(0)->CurrentGraphicsLayerForTesting()); |
| } |
| } |
| |
| // A lifetime test: delete LayerTreeView while running LinkHighlights. |
| TEST_P(LinkHighlightImplTest, resetLayerTreeView) { |
| WebViewImpl* web_view_impl = web_view_helper_.GetWebView(); |
| |
| int page_width = 640; |
| int page_height = 480; |
| web_view_impl->MainFrameWidget()->Resize(WebSize(page_width, page_height)); |
| UpdateAllLifecyclePhases(); |
| |
| WebGestureEvent touch_event(WebInputEvent::kGestureShowPress, |
| WebInputEvent::kNoModifiers, |
| WebInputEvent::GetStaticTimeStampForTests(), |
| kWebGestureDeviceTouchscreen); |
| touch_event.SetPositionInWidget(WebFloatPoint(20, 20)); |
| |
| GestureEventWithHitTestResults targeted_event = GetTargetedEvent(touch_event); |
| Node* touch_node = web_view_impl->BestTapNode(targeted_event); |
| ASSERT_TRUE(touch_node); |
| |
| web_view_impl->EnableTapHighlightAtPoint(targeted_event); |
| const auto& highlights = |
| web_view_impl->GetPage()->GetLinkHighlights().link_highlights_; |
| ASSERT_TRUE(highlights.at(0)); |
| |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| GraphicsLayer* highlight_layer = |
| highlights.at(0)->CurrentGraphicsLayerForTesting(); |
| ASSERT_TRUE(highlight_layer); |
| EXPECT_TRUE(highlight_layer->GetLinkHighlights().at(0)); |
| } |
| } |
| |
| TEST_P(LinkHighlightImplTest, HighlightLayerEffectNode) { |
| // This is testing the blink->cc layer integration. |
| if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() && |
| !RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| int page_width = 640; |
| int page_height = 480; |
| WebViewImpl* web_view_impl = web_view_helper_.GetWebView(); |
| web_view_impl->MainFrameWidget()->Resize(WebSize(page_width, page_height)); |
| |
| paint_artifact_compositor()->EnableExtraDataForTesting(); |
| UpdateAllLifecyclePhases(); |
| size_t layer_count_before_highlight = ContentLayerCount(); |
| |
| WebGestureEvent touch_event(WebInputEvent::kGestureShowPress, |
| WebInputEvent::kNoModifiers, |
| WebInputEvent::GetStaticTimeStampForTests(), |
| kWebGestureDeviceTouchscreen); |
| touch_event.SetPositionInWidget(WebFloatPoint(20, 20)); |
| |
| GestureEventWithHitTestResults targeted_event = GetTargetedEvent(touch_event); |
| Node* touch_node = web_view_impl->BestTapNode(targeted_event); |
| ASSERT_TRUE(touch_node); |
| |
| web_view_impl->EnableTapHighlightAtPoint(targeted_event); |
| // The highlight should create one additional layer. |
| EXPECT_EQ(layer_count_before_highlight + 1, ContentLayerCount()); |
| |
| const auto& highlights = web_view_impl->GetPage()->GetLinkHighlights(); |
| auto* highlight = highlights.link_highlights_.at(0).get(); |
| ASSERT_TRUE(highlight); |
| |
| // Check that the link highlight cc layer has a cc effect property tree node. |
| EXPECT_EQ(1u, highlight->FragmentCountForTesting()); |
| auto* layer = highlight->LayerForTesting(0); |
| // We don't set layer's element id. |
| EXPECT_EQ(cc::ElementId(), layer->element_id()); |
| auto effect_tree_index = layer->effect_tree_index(); |
| auto* property_trees = layer->layer_tree_host()->property_trees(); |
| EXPECT_EQ( |
| effect_tree_index, |
| property_trees->element_id_to_effect_node_index[highlight->element_id()]); |
| // The link highlight cc effect node should correspond to the blink effect |
| // node. |
| EXPECT_EQ(highlight->effect()->GetCompositorElementId(), |
| highlight->element_id()); |
| EXPECT_TRUE(highlight->effect()->RequiresCompositingForAnimation()); |
| |
| touch_node->remove(IGNORE_EXCEPTION_FOR_TESTING); |
| UpdateAllLifecyclePhases(); |
| // Removing the highlight layer should drop the cc layer count by one. |
| EXPECT_EQ(layer_count_before_highlight, ContentLayerCount()); |
| } |
| |
| TEST_P(LinkHighlightImplTest, MultiColumn) { |
| // This is testing the blink->cc layer integration. |
| if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() && |
| !RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| return; |
| |
| int page_width = 640; |
| int page_height = 480; |
| WebViewImpl* web_view_impl = web_view_helper_.GetWebView(); |
| web_view_impl->MainFrameWidget()->Resize(WebSize(page_width, page_height)); |
| UpdateAllLifecyclePhases(); |
| |
| paint_artifact_compositor()->EnableExtraDataForTesting(); |
| UpdateAllLifecyclePhases(); |
| size_t layer_count_before_highlight = ContentLayerCount(); |
| |
| WebGestureEvent touch_event(WebInputEvent::kGestureShowPress, |
| WebInputEvent::kNoModifiers, |
| WebInputEvent::GetStaticTimeStampForTests(), |
| kWebGestureDeviceTouchscreen); |
| // This will touch the link under multicol. |
| touch_event.SetPositionInWidget(WebFloatPoint(20, 300)); |
| |
| GestureEventWithHitTestResults targeted_event = GetTargetedEvent(touch_event); |
| Node* touch_node = web_view_impl->BestTapNode(targeted_event); |
| ASSERT_TRUE(touch_node); |
| |
| web_view_impl->EnableTapHighlightAtPoint(targeted_event); |
| |
| const auto& highlights = |
| web_view_impl->GetPage()->GetLinkHighlights().link_highlights_; |
| EXPECT_EQ(1u, highlights.size()); |
| const auto* highlight = highlights.at(0).get(); |
| ASSERT_TRUE(highlight); |
| |
| // The link highlight cc effect node should correspond to the blink effect |
| // node. |
| const auto* effect = highlight->effect(); |
| ASSERT_TRUE(effect); |
| EXPECT_EQ(effect->GetCompositorElementId(), highlight->element_id()); |
| EXPECT_TRUE(effect->RequiresCompositingForAnimation()); |
| |
| const auto& first_fragment = touch_node->GetLayoutObject()->FirstFragment(); |
| EXPECT_EQ(effect, first_fragment.PaintProperties()->LinkHighlightEffect()); |
| const auto* second_fragment = first_fragment.NextFragment(); |
| ASSERT_TRUE(second_fragment); |
| EXPECT_EQ(effect, second_fragment->PaintProperties()->LinkHighlightEffect()); |
| EXPECT_FALSE(second_fragment->NextFragment()); |
| |
| auto check_layer = [&](const cc::PictureLayer* layer) { |
| ASSERT_TRUE(layer); |
| // We don't set layer's element id. |
| EXPECT_EQ(cc::ElementId(), layer->element_id()); |
| auto effect_tree_index = layer->effect_tree_index(); |
| auto* property_trees = layer->layer_tree_host()->property_trees(); |
| EXPECT_EQ(effect_tree_index, |
| property_trees |
| ->element_id_to_effect_node_index[highlight->element_id()]); |
| }; |
| |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // The highlight should create 2 additional layer, each for each fragment. |
| EXPECT_EQ(layer_count_before_highlight + 2, ContentLayerCount()); |
| EXPECT_EQ(2u, highlight->FragmentCountForTesting()); |
| check_layer(highlight->LayerForTesting(0)); |
| check_layer(highlight->LayerForTesting(1)); |
| } else { |
| // The highlight should create 1 additional layer covering both fragments. |
| EXPECT_EQ(layer_count_before_highlight + 1, ContentLayerCount()); |
| EXPECT_EQ(1u, highlight->FragmentCountForTesting()); |
| check_layer(highlight->LayerForTesting(0)); |
| } |
| |
| touch_node->remove(IGNORE_EXCEPTION_FOR_TESTING); |
| UpdateAllLifecyclePhases(); |
| // Removing the highlight layer should drop the cc layers for highlights. |
| EXPECT_EQ(layer_count_before_highlight, ContentLayerCount()); |
| } |
| |
| } // namespace blink |