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