OLD | NEW |
| (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 | |
OLD | NEW |