1 /*
2  * Copyright (C) 2012 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1.  Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  * 2.  Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
23  * DAMAGE.
24  */
25 
26 #include "third_party/blink/renderer/core/paint/link_highlight_impl.h"
27 
28 #include <memory>
29 
30 #include "cc/layers/picture_layer.h"
31 #include "cc/trees/layer_tree_host.h"
32 #include "testing/gtest/include/gtest/gtest.h"
33 #include "third_party/blink/public/common/input/web_input_event.h"
34 #include "third_party/blink/public/platform/web_size.h"
35 #include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
36 #include "third_party/blink/public/web/web_frame.h"
37 #include "third_party/blink/public/web/web_local_frame_client.h"
38 #include "third_party/blink/public/web/web_view_client.h"
39 #include "third_party/blink/renderer/core/dom/node.h"
40 #include "third_party/blink/renderer/core/events/web_input_event_conversion.h"
41 #include "third_party/blink/renderer/core/exported/web_view_impl.h"
42 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
43 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
44 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
45 #include "third_party/blink/renderer/core/input/event_handler.h"
46 #include "third_party/blink/renderer/core/layout/layout_object.h"
47 #include "third_party/blink/renderer/core/page/link_highlight.h"
48 #include "third_party/blink/renderer/core/page/page.h"
49 #include "third_party/blink/renderer/platform/animation/compositor_animation_timeline.h"
50 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
51 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
52 #include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h"
53 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
54 #include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
55 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
56 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
57 #include "third_party/blink/renderer/platform/web_test_support.h"
58 
59 namespace blink {
60 
61 class LinkHighlightImplTest : public testing::Test,
62                               public PaintTestConfigurations {
63  protected:
GetTargetedEvent(WebGestureEvent & touch_event)64   GestureEventWithHitTestResults GetTargetedEvent(
65       WebGestureEvent& touch_event) {
66     WebGestureEvent scaled_event = TransformWebGestureEvent(
67         web_view_helper_.GetWebView()->MainFrameImpl()->GetFrameView(),
68         touch_event);
69     return web_view_helper_.GetWebView()
70         ->GetPage()
71         ->DeprecatedLocalMainFrame()
72         ->GetEventHandler()
73         .TargetGestureEvent(scaled_event, true);
74   }
75 
SetUp()76   void SetUp() override {
77     // TODO(crbug.com/751425): We should use the mock functionality
78     // via |web_view_helper_|.
79     WebURL url = url_test_helpers::RegisterMockedURLLoadFromBase(
80         WebString::FromUTF8("http://www.test.com/"), test::CoreTestDataPath(),
81         WebString::FromUTF8("test_touch_link_highlight.html"));
82     web_view_helper_.InitializeAndLoad(url.GetString().Utf8());
83   }
84 
TearDown()85   void TearDown() override {
86     url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
87 
88     // Ensure we fully clean up while scoped settings are enabled. Without this,
89     // garbage collection would occur after Scoped[setting]ForTest is out of
90     // scope, so the settings would not apply in some destructors.
91     web_view_helper_.Reset();
92     ThreadState::Current()->CollectAllGarbageForTesting();
93   }
94 
LayerCount()95   size_t LayerCount() {
96     return paint_artifact_compositor()->RootLayer()->children().size();
97   }
98 
paint_artifact_compositor()99   PaintArtifactCompositor* paint_artifact_compositor() {
100     auto* local_frame_view = web_view_helper_.LocalMainFrame()->GetFrameView();
101     return local_frame_view->GetPaintArtifactCompositor();
102   }
103 
UpdateAllLifecyclePhases()104   void UpdateAllLifecyclePhases() {
105     web_view_helper_.GetWebView()->MainFrameWidget()->UpdateAllLifecyclePhases(
106         DocumentUpdateReason::kTest);
107   }
108 
GetLinkHighlight()109   LinkHighlight& GetLinkHighlight() {
110     return web_view_helper_.GetWebView()->GetPage()->GetLinkHighlight();
111   }
112 
GetLinkHighlightImpl()113   LinkHighlightImpl* GetLinkHighlightImpl() {
114     return GetLinkHighlight().impl_.get();
115   }
116 
GetAnimationHost()117   cc::AnimationHost* GetAnimationHost() {
118     EXPECT_EQ(
119         GetLinkHighlight().timeline_->GetAnimationTimeline()->animation_host(),
120         GetLinkHighlight().animation_host_);
121     return GetLinkHighlight().animation_host_;
122   }
123 
124   frame_test_helpers::WebViewHelper web_view_helper_;
125 };
126 
127 INSTANTIATE_PAINT_TEST_SUITE_P(LinkHighlightImplTest);
128 
TEST_P(LinkHighlightImplTest,verifyWebViewImplIntegration)129 TEST_P(LinkHighlightImplTest, verifyWebViewImplIntegration) {
130   WebViewImpl* web_view_impl = web_view_helper_.GetWebView();
131   int page_width = 640;
132   int page_height = 480;
133   web_view_impl->MainFrameWidget()->Resize(WebSize(page_width, page_height));
134   UpdateAllLifecyclePhases();
135 
136   WebGestureEvent touch_event(WebInputEvent::kGestureShowPress,
137                               WebInputEvent::kNoModifiers,
138                               WebInputEvent::GetStaticTimeStampForTests(),
139                               WebGestureDevice::kTouchscreen);
140 
141   // The coordinates below are linked to absolute positions in the referenced
142   // .html file.
143   touch_event.SetPositionInWidget(gfx::PointF(20, 20));
144 
145   ASSERT_TRUE(web_view_impl->BestTapNode(GetTargetedEvent(touch_event)));
146 
147   touch_event.SetPositionInWidget(gfx::PointF(20, 40));
148   EXPECT_FALSE(web_view_impl->BestTapNode(GetTargetedEvent(touch_event)));
149 
150   touch_event.SetPositionInWidget(gfx::PointF(20, 20));
151   // Shouldn't crash.
152   web_view_impl->EnableTapHighlightAtPoint(GetTargetedEvent(touch_event));
153 
154   const auto* highlight = GetLinkHighlightImpl();
155   EXPECT_TRUE(highlight);
156   EXPECT_EQ(1u, highlight->FragmentCountForTesting());
157   EXPECT_TRUE(highlight->LayerForTesting(0));
158 
159   // Find a target inside a scrollable div
160   touch_event.SetPositionInWidget(gfx::PointF(20, 100));
161   web_view_impl->EnableTapHighlightAtPoint(GetTargetedEvent(touch_event));
162   ASSERT_TRUE(highlight);
163 
164   // Enesure the timeline was added to a host.
165   EXPECT_TRUE(GetAnimationHost());
166 
167   // Don't highlight if no "hand cursor"
168   touch_event.SetPositionInWidget(
169       gfx::PointF(20, 220));  // An A-link with cross-hair cursor.
170   web_view_impl->EnableTapHighlightAtPoint(GetTargetedEvent(touch_event));
171   EXPECT_FALSE(GetLinkHighlightImpl());
172 
173   touch_event.SetPositionInWidget(gfx::PointF(20, 260));  // A text input box.
174   web_view_impl->EnableTapHighlightAtPoint(GetTargetedEvent(touch_event));
175   EXPECT_FALSE(GetLinkHighlightImpl());
176 }
177 
TEST_P(LinkHighlightImplTest,resetDuringNodeRemoval)178 TEST_P(LinkHighlightImplTest, resetDuringNodeRemoval) {
179   WebViewImpl* web_view_impl = web_view_helper_.GetWebView();
180 
181   int page_width = 640;
182   int page_height = 480;
183   web_view_impl->MainFrameWidget()->Resize(WebSize(page_width, page_height));
184   UpdateAllLifecyclePhases();
185 
186   WebGestureEvent touch_event(WebInputEvent::kGestureShowPress,
187                               WebInputEvent::kNoModifiers,
188                               WebInputEvent::GetStaticTimeStampForTests(),
189                               WebGestureDevice::kTouchscreen);
190   touch_event.SetPositionInWidget(gfx::PointF(20, 20));
191 
192   GestureEventWithHitTestResults targeted_event = GetTargetedEvent(touch_event);
193   Node* touch_node = web_view_impl->BestTapNode(targeted_event);
194   ASSERT_TRUE(touch_node);
195 
196   web_view_impl->EnableTapHighlightAtPoint(targeted_event);
197   const auto* highlight = GetLinkHighlightImpl();
198   ASSERT_TRUE(highlight);
199   EXPECT_EQ(touch_node->GetLayoutObject(), highlight->GetLayoutObject());
200 
201   touch_node->remove(IGNORE_EXCEPTION_FOR_TESTING);
202   UpdateAllLifecyclePhases();
203 
204   ASSERT_EQ(highlight, GetLinkHighlightImpl());
205   ASSERT_TRUE(highlight);
206   EXPECT_FALSE(highlight->GetLayoutObject());
207 }
208 
209 // A lifetime test: delete LayerTreeView while running LinkHighlights.
TEST_P(LinkHighlightImplTest,resetLayerTreeView)210 TEST_P(LinkHighlightImplTest, resetLayerTreeView) {
211   WebViewImpl* web_view_impl = web_view_helper_.GetWebView();
212 
213   int page_width = 640;
214   int page_height = 480;
215   web_view_impl->MainFrameWidget()->Resize(WebSize(page_width, page_height));
216   UpdateAllLifecyclePhases();
217 
218   WebGestureEvent touch_event(WebInputEvent::kGestureShowPress,
219                               WebInputEvent::kNoModifiers,
220                               WebInputEvent::GetStaticTimeStampForTests(),
221                               WebGestureDevice::kTouchscreen);
222   touch_event.SetPositionInWidget(gfx::PointF(20, 20));
223 
224   GestureEventWithHitTestResults targeted_event = GetTargetedEvent(touch_event);
225   Node* touch_node = web_view_impl->BestTapNode(targeted_event);
226   ASSERT_TRUE(touch_node);
227 
228   web_view_impl->EnableTapHighlightAtPoint(targeted_event);
229   ASSERT_TRUE(GetLinkHighlightImpl());
230 }
231 
TEST_P(LinkHighlightImplTest,HighlightLayerEffectNode)232 TEST_P(LinkHighlightImplTest, HighlightLayerEffectNode) {
233   bool was_running_web_test = WebTestSupport::IsRunningWebTest();
234   WebTestSupport::SetIsRunningWebTest(false);
235   int page_width = 640;
236   int page_height = 480;
237   WebViewImpl* web_view_impl = web_view_helper_.GetWebView();
238   web_view_impl->MainFrameWidget()->Resize(WebSize(page_width, page_height));
239 
240   UpdateAllLifecyclePhases();
241   size_t layer_count_before_highlight = LayerCount();
242 
243   WebGestureEvent touch_event(WebInputEvent::kGestureShowPress,
244                               WebInputEvent::kNoModifiers,
245                               WebInputEvent::GetStaticTimeStampForTests(),
246                               WebGestureDevice::kTouchscreen);
247   touch_event.SetPositionInWidget(gfx::PointF(20, 20));
248 
249   GestureEventWithHitTestResults targeted_event = GetTargetedEvent(touch_event);
250   Node* touch_node = web_view_impl->BestTapNode(targeted_event);
251   ASSERT_TRUE(touch_node);
252 
253   web_view_impl->EnableTapHighlightAtPoint(targeted_event);
254   // The highlight should create one additional layer.
255   EXPECT_EQ(layer_count_before_highlight + 1, LayerCount());
256 
257   const auto* highlight = GetLinkHighlightImpl();
258   ASSERT_TRUE(highlight);
259 
260   // Check that the link highlight cc layer has a cc effect property tree node.
261   EXPECT_EQ(1u, highlight->FragmentCountForTesting());
262   auto* layer = highlight->LayerForTesting(0);
263   // We don't set layer's element id.
264   EXPECT_EQ(cc::ElementId(), layer->element_id());
265   auto effect_tree_index = layer->effect_tree_index();
266   auto* property_trees = layer->layer_tree_host()->property_trees();
267   EXPECT_EQ(
268       effect_tree_index,
269       property_trees
270           ->element_id_to_effect_node_index[highlight->ElementIdForTesting()]);
271   // The link highlight cc effect node should correspond to the blink effect
272   // node.
273   EXPECT_EQ(highlight->Effect().GetCompositorElementId(),
274             highlight->ElementIdForTesting());
275 
276   // Initially the highlight node has full opacity as it is expected to remain
277   // visible until the user completes a tap. See https://crbug.com/974631
278   EXPECT_EQ(1.f, highlight->Effect().Opacity());
279   EXPECT_TRUE(highlight->Effect().HasActiveOpacityAnimation());
280 
281   // After starting the highlight animation the effect node's opacity should
282   // be 0.f as it will be overridden bt the animation but may become visible
283   // before the animation is destructed. See https://crbug.com/974160
284   GetLinkHighlight().StartHighlightAnimationIfNeeded();
285   EXPECT_EQ(0.f, highlight->Effect().Opacity());
286   EXPECT_TRUE(highlight->Effect().HasActiveOpacityAnimation());
287 
288   touch_node->remove(IGNORE_EXCEPTION_FOR_TESTING);
289   UpdateAllLifecyclePhases();
290   // Removing the highlight layer should drop the cc layer count by one.
291   EXPECT_EQ(layer_count_before_highlight, LayerCount());
292 
293   WebTestSupport::SetIsRunningWebTest(was_running_web_test);
294 }
295 
TEST_P(LinkHighlightImplTest,MultiColumn)296 TEST_P(LinkHighlightImplTest, MultiColumn) {
297   int page_width = 640;
298   int page_height = 480;
299   WebViewImpl* web_view_impl = web_view_helper_.GetWebView();
300   web_view_impl->MainFrameWidget()->Resize(WebSize(page_width, page_height));
301   UpdateAllLifecyclePhases();
302 
303   UpdateAllLifecyclePhases();
304   size_t layer_count_before_highlight = LayerCount();
305 
306   WebGestureEvent touch_event(WebInputEvent::kGestureShowPress,
307                               WebInputEvent::kNoModifiers,
308                               WebInputEvent::GetStaticTimeStampForTests(),
309                               WebGestureDevice::kTouchscreen);
310   // This will touch the link under multicol.
311   touch_event.SetPositionInWidget(gfx::PointF(20, 300));
312 
313   GestureEventWithHitTestResults targeted_event = GetTargetedEvent(touch_event);
314   Node* touch_node = web_view_impl->BestTapNode(targeted_event);
315   ASSERT_TRUE(touch_node);
316 
317   web_view_impl->EnableTapHighlightAtPoint(targeted_event);
318 
319   const auto* highlight = GetLinkHighlightImpl();
320   ASSERT_TRUE(highlight);
321 
322   // The link highlight cc effect node should correspond to the blink effect
323   // node.
324   const auto& effect = highlight->Effect();
325   EXPECT_EQ(effect.GetCompositorElementId(), highlight->ElementIdForTesting());
326   EXPECT_TRUE(effect.HasActiveOpacityAnimation());
327 
328   const auto& first_fragment = touch_node->GetLayoutObject()->FirstFragment();
329   const auto* second_fragment = first_fragment.NextFragment();
330   ASSERT_TRUE(second_fragment);
331   EXPECT_FALSE(second_fragment->NextFragment());
332 
333   auto check_layer = [&](const cc::PictureLayer* layer) {
334     ASSERT_TRUE(layer);
335     // We don't set layer's element id.
336     EXPECT_EQ(cc::ElementId(), layer->element_id());
337     auto effect_tree_index = layer->effect_tree_index();
338     auto* property_trees = layer->layer_tree_host()->property_trees();
339     EXPECT_EQ(effect_tree_index, property_trees->element_id_to_effect_node_index
340                                      [highlight->ElementIdForTesting()]);
341   };
342 
343   // The highlight should create 2 additional layer, each for each fragment.
344   EXPECT_EQ(layer_count_before_highlight + 2, LayerCount());
345   EXPECT_EQ(2u, highlight->FragmentCountForTesting());
346   check_layer(highlight->LayerForTesting(0));
347   check_layer(highlight->LayerForTesting(1));
348 
349   Element* multicol = touch_node->parentElement();
350   EXPECT_EQ(50, multicol->OffsetHeight());
351   // Make multicol shorter to create 3 total columns for touch_node.
352   multicol->setAttribute(html_names::kStyleAttr, "height: 25px");
353   UpdateAllLifecyclePhases();
354   ASSERT_EQ(&first_fragment, &touch_node->GetLayoutObject()->FirstFragment());
355   ASSERT_EQ(second_fragment, first_fragment.NextFragment());
356   const auto* third_fragment = second_fragment->NextFragment();
357   ASSERT_TRUE(third_fragment);
358   EXPECT_FALSE(third_fragment->NextFragment());
359 
360   EXPECT_EQ(layer_count_before_highlight + 3, LayerCount());
361   EXPECT_EQ(3u, highlight->FragmentCountForTesting());
362   check_layer(highlight->LayerForTesting(0));
363   check_layer(highlight->LayerForTesting(1));
364   check_layer(highlight->LayerForTesting(2));
365 
366   // Make multicol taller to create only 1 column for touch_node.
367   multicol->setAttribute(html_names::kStyleAttr, "height: 100px");
368   UpdateAllLifecyclePhases();
369   ASSERT_EQ(&first_fragment, &touch_node->GetLayoutObject()->FirstFragment());
370   EXPECT_FALSE(first_fragment.NextFragment());
371 
372   EXPECT_EQ(layer_count_before_highlight + 1, LayerCount());
373   EXPECT_EQ(1u, highlight->FragmentCountForTesting());
374   check_layer(highlight->LayerForTesting(0));
375 
376   touch_node->remove(IGNORE_EXCEPTION_FOR_TESTING);
377   UpdateAllLifecyclePhases();
378   // Removing the highlight layer should drop the cc layers for highlights.
379   EXPECT_EQ(layer_count_before_highlight, LayerCount());
380 }
381 
TEST_P(LinkHighlightImplTest,DisplayContents)382 TEST_P(LinkHighlightImplTest, DisplayContents) {
383   WebViewImpl* web_view_impl = web_view_helper_.GetWebView();
384 
385   int page_width = 640;
386   int page_height = 480;
387   web_view_impl->MainFrameWidget()->Resize(WebSize(page_width, page_height));
388   UpdateAllLifecyclePhases();
389 
390   WebGestureEvent touch_event(WebInputEvent::kGestureShowPress,
391                               WebInputEvent::kNoModifiers,
392                               WebInputEvent::GetStaticTimeStampForTests(),
393                               WebGestureDevice::kTouchscreen);
394   // This will touch the div with display:contents and cursor:pointer.
395   touch_event.SetPositionInWidget(gfx::PointF(20, 400));
396 
397   GestureEventWithHitTestResults targeted_event = GetTargetedEvent(touch_event);
398   const Node* touched_node = targeted_event.GetHitTestResult().InnerNode();
399   EXPECT_TRUE(touched_node->IsTextNode());
400   EXPECT_FALSE(web_view_impl->BestTapNode(targeted_event));
401 
402   web_view_impl->EnableTapHighlightAtPoint(targeted_event);
403   EXPECT_FALSE(GetLinkHighlightImpl());
404 }
405 
406 }  // namespace blink
407