Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(888)

Side by Side Diff: third_party/WebKit/Source/core/html/shadow/MediaControlsTest.cpp

Issue 2795783004: Move core MediaControls implementation to modules/media_controls/. (Closed)
Patch Set: rebase Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 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 "core/html/shadow/MediaControls.h"
6
7 #include <limits>
8 #include <memory>
9 #include "core/HTMLNames.h"
10 #include "core/css/StylePropertySet.h"
11 #include "core/dom/Document.h"
12 #include "core/dom/ElementTraversal.h"
13 #include "core/dom/StyleEngine.h"
14 #include "core/events/Event.h"
15 #include "core/frame/Settings.h"
16 #include "core/html/HTMLElement.h"
17 #include "core/html/HTMLVideoElement.h"
18 #include "core/html/shadow/MediaControlElementTypes.h"
19 #include "core/layout/LayoutObject.h"
20 #include "core/loader/EmptyClients.h"
21 #include "core/testing/DummyPageHolder.h"
22 #include "platform/heap/Handle.h"
23 #include "platform/testing/EmptyWebMediaPlayer.h"
24 #include "platform/testing/HistogramTester.h"
25 #include "platform/testing/UnitTestHelpers.h"
26 #include "public/platform/WebSize.h"
27 #include "public/platform/modules/remoteplayback/WebRemotePlaybackAvailability.h "
28 #include "public/platform/modules/remoteplayback/WebRemotePlaybackClient.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30
31 namespace blink {
32
33 namespace {
34
35 class MockVideoWebMediaPlayer : public EmptyWebMediaPlayer {
36 public:
37 // WebMediaPlayer overrides:
38 WebTimeRanges seekable() const override { return m_seekable; }
39 bool hasVideo() const override { return true; }
40
41 WebTimeRanges m_seekable;
42 };
43
44 class MockWebRemotePlaybackClient : public WebRemotePlaybackClient {
45 public:
46 void stateChanged(WebRemotePlaybackState) override {}
47 void availabilityChanged(
48 WebRemotePlaybackAvailability availability) override {
49 m_availability = availability;
50 }
51 void promptCancelled() override {}
52 bool remotePlaybackAvailable() const override {
53 return m_availability == WebRemotePlaybackAvailability::DeviceAvailable;
54 }
55
56 private:
57 WebRemotePlaybackAvailability m_availability =
58 WebRemotePlaybackAvailability::Unknown;
59 };
60
61 class MockLayoutObject : public LayoutObject {
62 public:
63 MockLayoutObject() : LayoutObject(nullptr) {}
64
65 const char* name() const override { return "MockLayoutObject"; }
66 void layout() override {}
67 FloatRect localBoundingBoxRectForAccessibility() const override {
68 return FloatRect();
69 }
70
71 void setShouldDoFullPaintInvalidation(PaintInvalidationReason) {
72 m_fullPaintInvalidationCallCount++;
73 }
74
75 int fullPaintInvalidationCallCount() const {
76 return m_fullPaintInvalidationCallCount;
77 }
78
79 private:
80 int m_fullPaintInvalidationCallCount = 0;
81 };
82
83 class StubLocalFrameClient : public EmptyLocalFrameClient {
84 public:
85 static StubLocalFrameClient* create() { return new StubLocalFrameClient; }
86
87 std::unique_ptr<WebMediaPlayer> createWebMediaPlayer(
88 HTMLMediaElement&,
89 const WebMediaPlayerSource&,
90 WebMediaPlayerClient*) override {
91 return WTF::wrapUnique(new MockVideoWebMediaPlayer);
92 }
93
94 WebRemotePlaybackClient* createWebRemotePlaybackClient(
95 HTMLMediaElement&) override {
96 if (!m_remotePlaybackClient) {
97 m_remotePlaybackClient = WTF::wrapUnique(new MockWebRemotePlaybackClient);
98 }
99 return m_remotePlaybackClient.get();
100 }
101
102 private:
103 std::unique_ptr<MockWebRemotePlaybackClient> m_remotePlaybackClient;
104 };
105
106 Element* getElementByShadowPseudoId(Node& rootNode,
107 const char* shadowPseudoId) {
108 for (Element& element : ElementTraversal::descendantsOf(rootNode)) {
109 if (element.shadowPseudoId() == shadowPseudoId)
110 return &element;
111 }
112 return nullptr;
113 }
114
115 bool isElementVisible(Element& element) {
116 const StylePropertySet* inlineStyle = element.inlineStyle();
117
118 if (!inlineStyle)
119 return true;
120
121 if (inlineStyle->getPropertyValue(CSSPropertyDisplay) == "none")
122 return false;
123
124 if (inlineStyle->hasProperty(CSSPropertyOpacity) &&
125 inlineStyle->getPropertyValue(CSSPropertyOpacity).toDouble() == 0.0) {
126 return false;
127 }
128
129 if (inlineStyle->getPropertyValue(CSSPropertyVisibility) == "hidden")
130 return false;
131
132 if (Element* parent = element.parentElement())
133 return isElementVisible(*parent);
134
135 return true;
136 }
137
138 // This must match MediaControlDownloadButtonElement::DownloadActionMetrics.
139 enum DownloadActionMetrics {
140 Shown = 0,
141 Clicked,
142 Count // Keep last.
143 };
144
145 } // namespace
146
147 class MediaControlsTest : public ::testing::Test {
148 protected:
149 virtual void SetUp() {
150 m_pageHolder = DummyPageHolder::create(IntSize(800, 600), nullptr,
151 StubLocalFrameClient::create());
152 Document& document = this->document();
153
154 document.write("<video>");
155 HTMLVideoElement& video =
156 toHTMLVideoElement(*document.querySelector("video"));
157 m_mediaControls = video.mediaControls();
158
159 // If scripts are not enabled, controls will always be shown.
160 m_pageHolder->frame().settings()->setScriptEnabled(true);
161 }
162
163 void simulateRouteAvailabe() {
164 m_mediaControls->mediaElement().remoteRouteAvailabilityChanged(
165 WebRemotePlaybackAvailability::DeviceAvailable);
166 }
167
168 void ensureSizing() {
169 // Fire the size-change callback to ensure that the controls have
170 // been properly notified of the video size.
171 m_mediaControls->notifyElementSizeChanged(
172 m_mediaControls->mediaElement().getBoundingClientRect());
173 }
174
175 void simulateHideMediaControlsTimerFired() {
176 m_mediaControls->hideMediaControlsTimerFired(nullptr);
177 }
178
179 void simulateLoadedMetadata() { m_mediaControls->onLoadedMetadata(); }
180
181 MediaControls& mediaControls() { return *m_mediaControls; }
182 MockVideoWebMediaPlayer* webMediaPlayer() {
183 return static_cast<MockVideoWebMediaPlayer*>(
184 mediaControls().mediaElement().webMediaPlayer());
185 }
186 Document& document() { return m_pageHolder->document(); }
187
188 HistogramTester& histogramTester() { return m_histogramTester; }
189
190 void loadMediaWithDuration(double duration) {
191 mediaControls().mediaElement().setSrc("https://example.com/foo.mp4");
192 testing::runPendingTasks();
193 WebTimeRange timeRange(0.0, duration);
194 webMediaPlayer()->m_seekable.assign(&timeRange, 1);
195 mediaControls().mediaElement().durationChanged(duration,
196 false /* requestSeek */);
197 simulateLoadedMetadata();
198 }
199
200 private:
201 std::unique_ptr<DummyPageHolder> m_pageHolder;
202 Persistent<MediaControls> m_mediaControls;
203 HistogramTester m_histogramTester;
204 };
205
206 TEST_F(MediaControlsTest, HideAndShow) {
207 mediaControls().mediaElement().setBooleanAttribute(HTMLNames::controlsAttr,
208 true);
209
210 Element* panel = getElementByShadowPseudoId(mediaControls(),
211 "-webkit-media-controls-panel");
212 ASSERT_NE(nullptr, panel);
213
214 ASSERT_TRUE(isElementVisible(*panel));
215 mediaControls().hide();
216 ASSERT_FALSE(isElementVisible(*panel));
217 mediaControls().show();
218 ASSERT_TRUE(isElementVisible(*panel));
219 }
220
221 TEST_F(MediaControlsTest, Reset) {
222 mediaControls().mediaElement().setBooleanAttribute(HTMLNames::controlsAttr,
223 true);
224
225 Element* panel = getElementByShadowPseudoId(mediaControls(),
226 "-webkit-media-controls-panel");
227 ASSERT_NE(nullptr, panel);
228
229 ASSERT_TRUE(isElementVisible(*panel));
230 mediaControls().reset();
231 ASSERT_TRUE(isElementVisible(*panel));
232 }
233
234 TEST_F(MediaControlsTest, HideAndReset) {
235 mediaControls().mediaElement().setBooleanAttribute(HTMLNames::controlsAttr,
236 true);
237
238 Element* panel = getElementByShadowPseudoId(mediaControls(),
239 "-webkit-media-controls-panel");
240 ASSERT_NE(nullptr, panel);
241
242 ASSERT_TRUE(isElementVisible(*panel));
243 mediaControls().hide();
244 ASSERT_FALSE(isElementVisible(*panel));
245 mediaControls().reset();
246 ASSERT_FALSE(isElementVisible(*panel));
247 }
248
249 TEST_F(MediaControlsTest, ResetDoesNotTriggerInitialLayout) {
250 Document& document = this->document();
251 int oldElementCount = document.styleEngine().styleForElementCount();
252 // Also assert that there are no layouts yet.
253 ASSERT_EQ(0, oldElementCount);
254 mediaControls().reset();
255 int newElementCount = document.styleEngine().styleForElementCount();
256 ASSERT_EQ(oldElementCount, newElementCount);
257 }
258
259 TEST_F(MediaControlsTest, CastButtonRequiresRoute) {
260 ensureSizing();
261 mediaControls().mediaElement().setBooleanAttribute(HTMLNames::controlsAttr,
262 true);
263
264 Element* castButton = getElementByShadowPseudoId(
265 mediaControls(), "-internal-media-controls-cast-button");
266 ASSERT_NE(nullptr, castButton);
267
268 ASSERT_FALSE(isElementVisible(*castButton));
269
270 simulateRouteAvailabe();
271 ASSERT_TRUE(isElementVisible(*castButton));
272 }
273
274 TEST_F(MediaControlsTest, CastButtonDisableRemotePlaybackAttr) {
275 ensureSizing();
276 mediaControls().mediaElement().setBooleanAttribute(HTMLNames::controlsAttr,
277 true);
278
279 Element* castButton = getElementByShadowPseudoId(
280 mediaControls(), "-internal-media-controls-cast-button");
281 ASSERT_NE(nullptr, castButton);
282
283 ASSERT_FALSE(isElementVisible(*castButton));
284 simulateRouteAvailabe();
285 ASSERT_TRUE(isElementVisible(*castButton));
286
287 mediaControls().mediaElement().setBooleanAttribute(
288 HTMLNames::disableremoteplaybackAttr, true);
289 ASSERT_FALSE(isElementVisible(*castButton));
290
291 mediaControls().mediaElement().setBooleanAttribute(
292 HTMLNames::disableremoteplaybackAttr, false);
293 ASSERT_TRUE(isElementVisible(*castButton));
294 }
295
296 TEST_F(MediaControlsTest, CastOverlayDefault) {
297 Element* castOverlayButton = getElementByShadowPseudoId(
298 mediaControls(), "-internal-media-controls-overlay-cast-button");
299 ASSERT_NE(nullptr, castOverlayButton);
300
301 simulateRouteAvailabe();
302 ASSERT_TRUE(isElementVisible(*castOverlayButton));
303 }
304
305 TEST_F(MediaControlsTest, CastOverlayDisableRemotePlaybackAttr) {
306 Element* castOverlayButton = getElementByShadowPseudoId(
307 mediaControls(), "-internal-media-controls-overlay-cast-button");
308 ASSERT_NE(nullptr, castOverlayButton);
309
310 ASSERT_FALSE(isElementVisible(*castOverlayButton));
311 simulateRouteAvailabe();
312 ASSERT_TRUE(isElementVisible(*castOverlayButton));
313
314 mediaControls().mediaElement().setBooleanAttribute(
315 HTMLNames::disableremoteplaybackAttr, true);
316 ASSERT_FALSE(isElementVisible(*castOverlayButton));
317
318 mediaControls().mediaElement().setBooleanAttribute(
319 HTMLNames::disableremoteplaybackAttr, false);
320 ASSERT_TRUE(isElementVisible(*castOverlayButton));
321 }
322
323 TEST_F(MediaControlsTest, CastOverlayMediaControlsDisabled) {
324 Element* castOverlayButton = getElementByShadowPseudoId(
325 mediaControls(), "-internal-media-controls-overlay-cast-button");
326 ASSERT_NE(nullptr, castOverlayButton);
327
328 EXPECT_FALSE(isElementVisible(*castOverlayButton));
329 simulateRouteAvailabe();
330 EXPECT_TRUE(isElementVisible(*castOverlayButton));
331
332 document().settings()->setMediaControlsEnabled(false);
333 EXPECT_FALSE(isElementVisible(*castOverlayButton));
334
335 document().settings()->setMediaControlsEnabled(true);
336 EXPECT_TRUE(isElementVisible(*castOverlayButton));
337 }
338
339 TEST_F(MediaControlsTest, KeepControlsVisibleIfOverflowListVisible) {
340 Element* overflowList = getElementByShadowPseudoId(
341 mediaControls(), "-internal-media-controls-overflow-menu-list");
342 ASSERT_NE(nullptr, overflowList);
343
344 Element* panel = getElementByShadowPseudoId(mediaControls(),
345 "-webkit-media-controls-panel");
346 ASSERT_NE(nullptr, panel);
347
348 mediaControls().mediaElement().setSrc("http://example.com");
349 mediaControls().mediaElement().play();
350 testing::runPendingTasks();
351
352 mediaControls().show();
353 mediaControls().toggleOverflowMenu();
354 EXPECT_TRUE(isElementVisible(*overflowList));
355
356 simulateHideMediaControlsTimerFired();
357 EXPECT_TRUE(isElementVisible(*overflowList));
358 EXPECT_TRUE(isElementVisible(*panel));
359 }
360
361 TEST_F(MediaControlsTest, DownloadButtonDisplayed) {
362 ensureSizing();
363
364 Element* downloadButton = getElementByShadowPseudoId(
365 mediaControls(), "-internal-media-controls-download-button");
366 ASSERT_NE(nullptr, downloadButton);
367
368 mediaControls().mediaElement().setSrc("https://example.com/foo.mp4");
369 testing::runPendingTasks();
370 simulateLoadedMetadata();
371
372 // Download button should normally be displayed.
373 EXPECT_TRUE(isElementVisible(*downloadButton));
374 }
375
376 TEST_F(MediaControlsTest, DownloadButtonNotDisplayedEmptyUrl) {
377 ensureSizing();
378
379 Element* downloadButton = getElementByShadowPseudoId(
380 mediaControls(), "-internal-media-controls-download-button");
381 ASSERT_NE(nullptr, downloadButton);
382
383 // Download button should not be displayed when URL is empty.
384 mediaControls().mediaElement().setSrc("");
385 testing::runPendingTasks();
386 simulateLoadedMetadata();
387 EXPECT_FALSE(isElementVisible(*downloadButton));
388 }
389
390 TEST_F(MediaControlsTest, DownloadButtonDisplayedHiddenAndDisplayed) {
391 ensureSizing();
392
393 Element* downloadButton = getElementByShadowPseudoId(
394 mediaControls(), "-internal-media-controls-download-button");
395 ASSERT_NE(nullptr, downloadButton);
396
397 // Initially show button.
398 mediaControls().mediaElement().setSrc("https://example.com/foo.mp4");
399 testing::runPendingTasks();
400 simulateLoadedMetadata();
401 EXPECT_TRUE(isElementVisible(*downloadButton));
402 histogramTester().expectBucketCount("Media.Controls.Download",
403 DownloadActionMetrics::Shown, 1);
404
405 // Hide button.
406 mediaControls().mediaElement().setSrc("");
407 testing::runPendingTasks();
408 EXPECT_FALSE(isElementVisible(*downloadButton));
409 histogramTester().expectBucketCount("Media.Controls.Download",
410 DownloadActionMetrics::Shown, 1);
411
412 // Showing button again should not increment Shown count.
413 mediaControls().mediaElement().setSrc("https://example.com/foo.mp4");
414 testing::runPendingTasks();
415 EXPECT_TRUE(isElementVisible(*downloadButton));
416 histogramTester().expectBucketCount("Media.Controls.Download",
417 DownloadActionMetrics::Shown, 1);
418 }
419
420 TEST_F(MediaControlsTest, DownloadButtonRecordsClickOnlyOnce) {
421 ensureSizing();
422
423 MediaControlDownloadButtonElement* downloadButton =
424 static_cast<MediaControlDownloadButtonElement*>(
425 getElementByShadowPseudoId(
426 mediaControls(), "-internal-media-controls-download-button"));
427 ASSERT_NE(nullptr, downloadButton);
428
429 // Initially show button.
430 mediaControls().mediaElement().setSrc("https://example.com/foo.mp4");
431 testing::runPendingTasks();
432 simulateLoadedMetadata();
433 EXPECT_TRUE(isElementVisible(*downloadButton));
434 histogramTester().expectBucketCount("Media.Controls.Download",
435 DownloadActionMetrics::Shown, 1);
436
437 // Click button once.
438 downloadButton->dispatchSimulatedClick(
439 Event::createBubble(EventTypeNames::click), SendNoEvents);
440 histogramTester().expectBucketCount("Media.Controls.Download",
441 DownloadActionMetrics::Clicked, 1);
442
443 // Clicking button again should not increment Clicked count.
444 downloadButton->dispatchSimulatedClick(
445 Event::createBubble(EventTypeNames::click), SendNoEvents);
446 histogramTester().expectBucketCount("Media.Controls.Download",
447 DownloadActionMetrics::Clicked, 1);
448 }
449
450 TEST_F(MediaControlsTest, DownloadButtonNotDisplayedInfiniteDuration) {
451 ensureSizing();
452
453 Element* downloadButton = getElementByShadowPseudoId(
454 mediaControls(), "-internal-media-controls-download-button");
455 ASSERT_NE(nullptr, downloadButton);
456
457 mediaControls().mediaElement().setSrc("https://example.com/foo.mp4");
458 testing::runPendingTasks();
459
460 // Download button should not be displayed when duration is infinite.
461 mediaControls().mediaElement().durationChanged(
462 std::numeric_limits<double>::infinity(), false /* requestSeek */);
463 simulateLoadedMetadata();
464 EXPECT_FALSE(isElementVisible(*downloadButton));
465 }
466
467 TEST_F(MediaControlsTest, DownloadButtonNotDisplayedHLS) {
468 ensureSizing();
469
470 Element* downloadButton = getElementByShadowPseudoId(
471 mediaControls(), "-internal-media-controls-download-button");
472 ASSERT_NE(nullptr, downloadButton);
473
474 // Download button should not be displayed for HLS streams.
475 mediaControls().mediaElement().setSrc("https://example.com/foo.m3u8");
476 testing::runPendingTasks();
477 simulateLoadedMetadata();
478 EXPECT_FALSE(isElementVisible(*downloadButton));
479 }
480
481 TEST_F(MediaControlsTest, TimelineSeekToRoundedEnd) {
482 ensureSizing();
483
484 MediaControlTimelineElement* timeline =
485 static_cast<MediaControlTimelineElement*>(getElementByShadowPseudoId(
486 mediaControls(), "-webkit-media-controls-timeline"));
487 ASSERT_NE(nullptr, timeline);
488
489 // Tests the case where the real length of the video, |exactDuration|, gets
490 // rounded up slightly to |roundedUpDuration| when setting the timeline's
491 // |max| attribute (crbug.com/695065).
492 double exactDuration = 596.586667;
493 double roundedUpDuration = 596.587;
494 loadMediaWithDuration(exactDuration);
495
496 // Simulate a click slightly past the end of the track of the timeline's
497 // underlying <input type="range">. This would set the |value| to the |max|
498 // attribute, which can be slightly rounded relative to the duration.
499 timeline->setValueAsNumber(roundedUpDuration, ASSERT_NO_EXCEPTION);
500 ASSERT_EQ(roundedUpDuration, timeline->valueAsNumber());
501 EXPECT_EQ(0.0, mediaControls().mediaElement().currentTime());
502 timeline->dispatchInputEvent();
503 EXPECT_EQ(exactDuration, mediaControls().mediaElement().currentTime());
504 }
505
506 TEST_F(MediaControlsTest, TimelineImmediatelyUpdatesCurrentTime) {
507 ensureSizing();
508
509 MediaControlTimelineElement* timeline =
510 static_cast<MediaControlTimelineElement*>(getElementByShadowPseudoId(
511 mediaControls(), "-webkit-media-controls-timeline"));
512 ASSERT_NE(nullptr, timeline);
513 MediaControlCurrentTimeDisplayElement* currentTimeDisplay =
514 static_cast<MediaControlCurrentTimeDisplayElement*>(
515 getElementByShadowPseudoId(
516 mediaControls(), "-webkit-media-controls-current-time-display"));
517 ASSERT_NE(nullptr, currentTimeDisplay);
518
519 double duration = 600;
520 loadMediaWithDuration(duration);
521
522 // Simulate seeking the underlying range to 50%. Current time display should
523 // update synchronously (rather than waiting for media to finish seeking).
524 timeline->setValueAsNumber(duration / 2, ASSERT_NO_EXCEPTION);
525 timeline->dispatchInputEvent();
526 EXPECT_EQ(duration / 2, currentTimeDisplay->currentValue());
527 }
528
529 TEST_F(MediaControlsTest, VolumeSliderPaintInvalidationOnInput) {
530 ensureSizing();
531
532 MediaControlVolumeSliderElement* volumeSlider =
533 static_cast<MediaControlVolumeSliderElement*>(getElementByShadowPseudoId(
534 mediaControls(), "-webkit-media-controls-volume-slider"));
535 ASSERT_NE(nullptr, volumeSlider);
536
537 HTMLElement* element = volumeSlider;
538
539 MockLayoutObject layoutObject;
540 LayoutObject* prevLayoutObject = volumeSlider->layoutObject();
541 volumeSlider->setLayoutObject(&layoutObject);
542
543 Event* event = Event::create(EventTypeNames::input);
544 element->defaultEventHandler(event);
545 EXPECT_EQ(1, layoutObject.fullPaintInvalidationCallCount());
546
547 event = Event::create(EventTypeNames::input);
548 element->defaultEventHandler(event);
549 EXPECT_EQ(2, layoutObject.fullPaintInvalidationCallCount());
550
551 event = Event::create(EventTypeNames::input);
552 element->defaultEventHandler(event);
553 EXPECT_EQ(3, layoutObject.fullPaintInvalidationCallCount());
554
555 volumeSlider->setLayoutObject(prevLayoutObject);
556 }
557
558 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698