1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/modules/media_controls/media_controls_display_cutout_delegate.h"
6
7 #include "third_party/blink/public/mojom/page/display_cutout.mojom-blink.h"
8 #include "third_party/blink/renderer/core/events/touch_event.h"
9 #include "third_party/blink/renderer/core/frame/local_frame.h"
10 #include "third_party/blink/renderer/core/frame/viewport_data.h"
11 #include "third_party/blink/renderer/core/frame/web_feature.h"
12 #include "third_party/blink/renderer/core/fullscreen/fullscreen.h"
13 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
14 #include "third_party/blink/renderer/core/input/touch.h"
15 #include "third_party/blink/renderer/core/input/touch_list.h"
16 #include "third_party/blink/renderer/core/loader/empty_clients.h"
17 #include "third_party/blink/renderer/core/testing/page_test_base.h"
18 #include "third_party/blink/renderer/modules/media_controls/media_controls_impl.h"
19 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
20 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
21 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
22 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
23
24 namespace blink {
25
26 namespace {
27
28 class DisplayCutoutMockChromeClient : public EmptyChromeClient {
29 public:
30 // ChromeClient overrides:
EnterFullscreen(LocalFrame & frame,const FullscreenOptions *,FullscreenRequestType)31 void EnterFullscreen(LocalFrame& frame,
32 const FullscreenOptions*,
33 FullscreenRequestType) override {
34 Fullscreen::DidResolveEnterFullscreenRequest(*frame.GetDocument(),
35 true /* granted */);
36 }
ExitFullscreen(LocalFrame & frame)37 void ExitFullscreen(LocalFrame& frame) override {
38 Fullscreen::DidExitFullscreen(*frame.GetDocument());
39 }
40 };
41
42 } // namespace
43
44 class MediaControlsDisplayCutoutDelegateTest
45 : public PageTestBase,
46 private ScopedDisplayCutoutAPIForTest,
47 private ScopedMediaControlsExpandGestureForTest {
48 public:
MediaControlsDisplayCutoutDelegateTest()49 MediaControlsDisplayCutoutDelegateTest()
50 : ScopedDisplayCutoutAPIForTest(true),
51 ScopedMediaControlsExpandGestureForTest(true) {}
SetUp()52 void SetUp() override {
53 chrome_client_ = MakeGarbageCollected<DisplayCutoutMockChromeClient>();
54
55 Page::PageClients clients;
56 FillWithEmptyClients(clients);
57 clients.chrome_client = chrome_client_.Get();
58 SetupPageWithClients(&clients,
59 MakeGarbageCollected<EmptyLocalFrameClient>());
60 GetDocument().write("<body><video id=video></body>");
61 }
62
SimulateEnterFullscreen()63 void SimulateEnterFullscreen() {
64 {
65 LocalFrame::NotifyUserActivation(
66 GetDocument().GetFrame(),
67 mojom::UserActivationNotificationType::kTest);
68 Fullscreen::RequestFullscreen(GetVideoElement());
69 }
70
71 test::RunPendingTasks();
72 GetDocument().ServiceScriptedAnimations(base::TimeTicks());
73
74 EXPECT_TRUE(GetVideoElement().IsFullscreen());
75 }
76
SimulateExitFullscreen()77 void SimulateExitFullscreen() {
78 Fullscreen::FullyExitFullscreen(GetDocument());
79
80 GetDocument().ServiceScriptedAnimations(base::TimeTicks());
81
82 EXPECT_FALSE(GetVideoElement().IsFullscreen());
83 }
84
SimulateContractingGesture()85 void SimulateContractingGesture() {
86 TouchList* list = CreateTouchListWithTwoPoints(5, 5, -5, -5);
87 SimulateEvent(
88 CreateTouchEventWithList(event_type_names::kTouchstart, list));
89
90 list = CreateTouchListWithTwoPoints(4, 4, -4, -4);
91 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchmove, list));
92
93 list = CreateTouchListWithTwoPoints(0, 0, 0, 0);
94 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchend, list));
95 }
96
SimulateExpandingGesture()97 void SimulateExpandingGesture() {
98 TouchList* list = CreateTouchListWithTwoPoints(1, 1, -1, -1);
99 SimulateEvent(
100 CreateTouchEventWithList(event_type_names::kTouchstart, list));
101
102 list = CreateTouchListWithTwoPoints(4, 4, -4, -4);
103 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchmove, list));
104
105 list = CreateTouchListWithTwoPoints(5, 5, -5, -5);
106 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchend, list));
107 }
108
SimulateSingleTouchGesture()109 void SimulateSingleTouchGesture() {
110 TouchList* list = CreateTouchListWithOnePoint(1, 1);
111 SimulateEvent(
112 CreateTouchEventWithList(event_type_names::kTouchstart, list));
113
114 list = CreateTouchListWithOnePoint(4, 4);
115 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchmove, list));
116
117 list = CreateTouchListWithOnePoint(5, 5);
118 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchend, list));
119 }
120
HasGestureState()121 bool HasGestureState() { return GetDelegate().previous_.has_value(); }
122
DirectionIsExpanding()123 bool DirectionIsExpanding() {
124 return GetDelegate().previous_->second ==
125 MediaControlsDisplayCutoutDelegate::Direction::kExpanding;
126 }
127
DirectionIsUnknown()128 bool DirectionIsUnknown() {
129 return GetDelegate().previous_->second ==
130 MediaControlsDisplayCutoutDelegate::Direction::kUnknown;
131 }
132
SimulateEvent(TouchEvent * event)133 void SimulateEvent(TouchEvent* event) {
134 DCHECK(event);
135 GetVideoElement().FireEventListeners(*event);
136 }
137
CreateTouchListWithOnePoint(int x,int y)138 TouchList* CreateTouchListWithOnePoint(int x, int y) {
139 TouchList* list = TouchList::Create();
140 list->Append(CreateTouchAtPoint(x, y));
141 return list;
142 }
143
CreateTouchListWithTwoPoints(int x1,int y1,int x2,int y2)144 TouchList* CreateTouchListWithTwoPoints(int x1, int y1, int x2, int y2) {
145 TouchList* list = TouchList::Create();
146 list->Append(CreateTouchAtPoint(x1, y1));
147 list->Append(CreateTouchAtPoint(x2, y2));
148 return list;
149 }
150
CreateTouchEventWithList(const AtomicString & type,TouchList * list)151 TouchEvent* CreateTouchEventWithList(const AtomicString& type,
152 TouchList* list) {
153 TouchEvent* event = TouchEvent::Create();
154 event->initEvent(type, true, false);
155 event->SetTouches(list);
156 return event;
157 }
158
CreateTouchAtPoint(int x,int y)159 Touch* CreateTouchAtPoint(int x, int y) {
160 return Touch::Create(GetDocument().GetFrame(), &GetVideoElement(),
161 1 /* identifier */, FloatPoint(x, y), FloatPoint(x, y),
162 FloatSize(1, 1), 90, 0, "test");
163 }
164
CurrentViewportFit() const165 mojom::ViewportFit CurrentViewportFit() const {
166 return GetDocument().GetViewportData().GetCurrentViewportFitForTests();
167 }
168
169 private:
GetDelegate()170 MediaControlsDisplayCutoutDelegate& GetDelegate() {
171 MediaControlsImpl* controls =
172 static_cast<MediaControlsImpl*>(GetVideoElement().GetMediaControls());
173 return *controls->display_cutout_delegate_;
174 }
175
GetVideoElement()176 HTMLVideoElement& GetVideoElement() {
177 return *To<HTMLVideoElement>(GetDocument().getElementById("video"));
178 }
179
180 Persistent<DisplayCutoutMockChromeClient> chrome_client_;
181 };
182
TEST_F(MediaControlsDisplayCutoutDelegateTest,CombinedGesture)183 TEST_F(MediaControlsDisplayCutoutDelegateTest, CombinedGesture) {
184 SimulateEnterFullscreen();
185
186 // Simulate the an expanding gesture but do not finish it.
187 TouchList* list = CreateTouchListWithTwoPoints(1, 1, -1, -1);
188 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchstart, list));
189 list = CreateTouchListWithTwoPoints(4, 4, -4, -4);
190 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchmove, list));
191
192 // Check the viewport fit value has been correctly set.
193 EXPECT_EQ(mojom::ViewportFit::kCoverForcedByUserAgent, CurrentViewportFit());
194
195 // Finish the gesture by contracting.
196 list = CreateTouchListWithTwoPoints(0, 0, 0, 0);
197 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchend, list));
198
199 // Check the viewport fit value has been correctly set.
200 EXPECT_EQ(mojom::ViewportFit::kAuto, CurrentViewportFit());
201
202 // Make sure we recorded a UseCounter metric.
203 EXPECT_TRUE(GetDocument().IsUseCounted(
204 WebFeature::kMediaControlsDisplayCutoutGesture));
205 }
206
TEST_F(MediaControlsDisplayCutoutDelegateTest,ContractingGesture)207 TEST_F(MediaControlsDisplayCutoutDelegateTest, ContractingGesture) {
208 // Go fullscreen and simulate an expanding gesture.
209 SimulateEnterFullscreen();
210 SimulateExpandingGesture();
211
212 // Check the viewport fit value has been correctly set.
213 EXPECT_EQ(mojom::ViewportFit::kCoverForcedByUserAgent, CurrentViewportFit());
214
215 // Simulate a contracting gesture and check the value has been restored.
216 SimulateContractingGesture();
217 EXPECT_EQ(mojom::ViewportFit::kAuto, CurrentViewportFit());
218
219 // Make sure we recorded a UseCounter metric.
220 EXPECT_TRUE(GetDocument().IsUseCounted(
221 WebFeature::kMediaControlsDisplayCutoutGesture));
222 }
223
TEST_F(MediaControlsDisplayCutoutDelegateTest,ContractingGesture_Noop)224 TEST_F(MediaControlsDisplayCutoutDelegateTest, ContractingGesture_Noop) {
225 // Go fullscreen and simulate a contracting gesture.
226 SimulateEnterFullscreen();
227 SimulateContractingGesture();
228
229 // Check that the value did not change.
230 EXPECT_EQ(mojom::ViewportFit::kAuto, CurrentViewportFit());
231 }
232
TEST_F(MediaControlsDisplayCutoutDelegateTest,ExpandingGesture)233 TEST_F(MediaControlsDisplayCutoutDelegateTest, ExpandingGesture) {
234 // Go fullscreen and simulate an expanding gesture.
235 SimulateEnterFullscreen();
236 SimulateExpandingGesture();
237
238 // Check the viewport fit value has been correctly set.
239 EXPECT_EQ(mojom::ViewportFit::kCoverForcedByUserAgent, CurrentViewportFit());
240
241 // Exit fullscreen and check the value has been restored.
242 SimulateExitFullscreen();
243 EXPECT_EQ(mojom::ViewportFit::kAuto, CurrentViewportFit());
244
245 // Make sure we recorded a UseCounter metric.
246 EXPECT_TRUE(GetDocument().IsUseCounted(
247 WebFeature::kMediaControlsDisplayCutoutGesture));
248 }
249
TEST_F(MediaControlsDisplayCutoutDelegateTest,ExpandingGesture_DoubleNoop)250 TEST_F(MediaControlsDisplayCutoutDelegateTest, ExpandingGesture_DoubleNoop) {
251 // Go fullscreen and simulate an expanding gesture.
252 SimulateEnterFullscreen();
253 SimulateExpandingGesture();
254
255 // Check the viewport fit value has been correctly set.
256 EXPECT_EQ(mojom::ViewportFit::kCoverForcedByUserAgent, CurrentViewportFit());
257
258 // Simulate another expanding gesture and make sure nothing changed.
259 SimulateExpandingGesture();
260 EXPECT_EQ(mojom::ViewportFit::kCoverForcedByUserAgent, CurrentViewportFit());
261 }
262
TEST_F(MediaControlsDisplayCutoutDelegateTest,IncompleteGestureClearsState)263 TEST_F(MediaControlsDisplayCutoutDelegateTest, IncompleteGestureClearsState) {
264 SimulateEnterFullscreen();
265
266 // Simulate a gesture and check we have state.
267 TouchList* list = CreateTouchListWithTwoPoints(1, 1, -1, -1);
268 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchstart, list));
269
270 list = CreateTouchListWithTwoPoints(2, 2, -2, -2);
271 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchmove, list));
272 EXPECT_TRUE(DirectionIsExpanding());
273
274 // Simulate another start gesture and make sure we do not have a direction.
275 list = CreateTouchListWithTwoPoints(3, 3, -3, -3);
276 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchstart, list));
277 EXPECT_TRUE(DirectionIsUnknown());
278 }
279
TEST_F(MediaControlsDisplayCutoutDelegateTest,MetricsNoop)280 TEST_F(MediaControlsDisplayCutoutDelegateTest, MetricsNoop) {
281 EXPECT_FALSE(GetDocument().IsUseCounted(
282 WebFeature::kMediaControlsDisplayCutoutGesture));
283 }
284
TEST_F(MediaControlsDisplayCutoutDelegateTest,NoFullscreen_Noop)285 TEST_F(MediaControlsDisplayCutoutDelegateTest, NoFullscreen_Noop) {
286 // Simulate an expanding gesture and make sure it had no effect.
287 SimulateExpandingGesture();
288 EXPECT_EQ(mojom::ViewportFit::kAuto, CurrentViewportFit());
289 }
290
TEST_F(MediaControlsDisplayCutoutDelegateTest,SingleTouchGesture_Noop)291 TEST_F(MediaControlsDisplayCutoutDelegateTest, SingleTouchGesture_Noop) {
292 // Simulate a single touch gesture and make sure it had no effect.
293 SimulateEnterFullscreen();
294 SimulateSingleTouchGesture();
295 mojom::ViewportFit expected =
296 RuntimeEnabledFeatures::MediaControlsUseCutOutByDefaultEnabled()
297 ? mojom::ViewportFit::kCoverForcedByUserAgent
298 : mojom::ViewportFit::kAuto;
299 EXPECT_EQ(expected, CurrentViewportFit());
300 }
301
TEST_F(MediaControlsDisplayCutoutDelegateTest,TouchCancelShouldClearState)302 TEST_F(MediaControlsDisplayCutoutDelegateTest, TouchCancelShouldClearState) {
303 SimulateEnterFullscreen();
304
305 // Simulate a gesture and check we have state.
306 TouchList* list = CreateTouchListWithTwoPoints(1, 1, -1, -1);
307 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchstart, list));
308 EXPECT_TRUE(HasGestureState());
309
310 // Simulate a touchcancel gesture and check that clears the state.
311 list = CreateTouchListWithTwoPoints(1, 1, -1, -1);
312 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchcancel, list));
313 EXPECT_FALSE(HasGestureState());
314 mojom::ViewportFit expected =
315 RuntimeEnabledFeatures::MediaControlsUseCutOutByDefaultEnabled()
316 ? mojom::ViewportFit::kCoverForcedByUserAgent
317 : mojom::ViewportFit::kAuto;
318 EXPECT_EQ(expected, CurrentViewportFit());
319 }
320
TEST_F(MediaControlsDisplayCutoutDelegateTest,TouchEndShouldClearState)321 TEST_F(MediaControlsDisplayCutoutDelegateTest, TouchEndShouldClearState) {
322 SimulateEnterFullscreen();
323
324 // Simulate a gesture and check we have state.
325 TouchList* list = CreateTouchListWithTwoPoints(1, 1, -1, -1);
326 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchstart, list));
327 EXPECT_TRUE(HasGestureState());
328
329 // Simulate a touchend gesture and check that clears the state.
330 list = CreateTouchListWithTwoPoints(1, 1, -1, -1);
331 SimulateEvent(CreateTouchEventWithList(event_type_names::kTouchend, list));
332 EXPECT_FALSE(HasGestureState());
333
334 mojom::ViewportFit expected =
335 RuntimeEnabledFeatures::MediaControlsUseCutOutByDefaultEnabled()
336 ? mojom::ViewportFit::kCoverForcedByUserAgent
337 : mojom::ViewportFit::kAuto;
338 EXPECT_EQ(expected, CurrentViewportFit());
339 }
340
TEST_F(MediaControlsDisplayCutoutDelegateTest,DefaultExpand)341 TEST_F(MediaControlsDisplayCutoutDelegateTest, DefaultExpand) {
342 ScopedMediaControlsUseCutOutByDefaultForTest scoped_default_expand(true);
343
344 SimulateEnterFullscreen();
345 EXPECT_EQ(mojom::ViewportFit::kCoverForcedByUserAgent, CurrentViewportFit());
346 }
347
TEST_F(MediaControlsDisplayCutoutDelegateTest,DefaultNotExpand)348 TEST_F(MediaControlsDisplayCutoutDelegateTest, DefaultNotExpand) {
349 ScopedMediaControlsUseCutOutByDefaultForTest scoped_default_expand(false);
350
351 SimulateEnterFullscreen();
352 EXPECT_EQ(mojom::ViewportFit::kAuto, CurrentViewportFit());
353 }
354
355 } // namespace blink
356