Show controls and update time indicators during seeking

This commit modifies the MediaControls to show the controls and update
the current time indicators (current time text + timeline thumb)
immediately during seek requests so the element behavior during rapid
seek requests (e.g. user is holding down the FF button on their BT
keyboard) look similar to when a user is scrubbing the timeline with
their mouse.

Bug: 748749

TEST=MediaControlsImplTest, manual testing
     out/Debug/bin/run_webkit_unit_tests -f *MediaControlsImplTest*

Change-Id: I6588aded2a93e081a6ca638ee1da534ea00abf4d
Reviewed-on: https://chromium-review.googlesource.com/c/1274945
Commit-Queue: Tommy Steimel <steimel@chromium.org>
Reviewed-by: Mounir Lamouri <mlamouri@chromium.org>
Reviewed-by: Tommy Steimel <steimel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#636493}
diff --git a/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc b/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc
index 157d644..c601ed5 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc
@@ -300,8 +300,12 @@
   EXPECT_CALL(*timeupdate_handler, Invoke(_, _)).Times(0);
   platform_->RunForPeriodSeconds(1);
 
-  // Seek to some time in the past. A completed seek should trigger a *single*
-  // timeupdate.
+  EXPECT_CALL(*timeupdate_handler, Invoke(_, _)).Times(1);
+  Video()->pause();
+  platform_->RunUntilIdle();
+
+  // Seek to some time in the past. A completed seek while paused should trigger
+  // a *single* timeupdate.
   EXPECT_CALL(*timeupdate_handler, Invoke(_, _)).Times(1);
   ASSERT_GE(WebMediaPlayer()->CurrentTime(), 1);
   Video()->setCurrentTime(WebMediaPlayer()->CurrentTime() - 1);
@@ -354,10 +358,14 @@
   ASSERT_GE(WebMediaPlayer()->CurrentTime(), 1);
   Video()->setCurrentTime(WebMediaPlayer()->CurrentTime() - 1);
   WebMediaPlayer()->FinishSeek();
+
+  // Expect another timeupdate after FinishSeek due to
+  // seeking -> begin scrubbing -> pause -> timeupdate.
+  EXPECT_CALL(*timeupdate_handler, Invoke(_, _)).Times(1);
   platform_->RunUntilIdle();
 
   // Advancing the remainder of the last periodic timeupdate interval should be
-  // insufficient to triggger a new timeupdate event because the seek's
+  // insufficient to trigger a new timeupdate event because the seek's
   // timeupdate occurred only 125ms ago. We desire to fire periodic timeupdates
   // exactly every 250ms from the last timeupdate, and the seek's timeupdate
   // should reset that 250ms ms countdown.
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc b/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
index 45bf4c0..14ed7d3 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
@@ -938,9 +938,7 @@
 
   UpdatePlayState();
 
-  UpdateCurrentTimeDisplay();
-
-  timeline_->SetPosition(MediaElement().currentTime());
+  UpdateTimeIndicators();
 
   OnVolumeChange();
   OnTextTracksAddedOrRemoved();
@@ -955,6 +953,11 @@
   OnControlsListUpdated();
 }
 
+void MediaControlsImpl::UpdateTimeIndicators() {
+  timeline_->SetPosition(MediaElement().currentTime());
+  UpdateCurrentTimeDisplay();
+}
+
 void MediaControlsImpl::OnControlsListUpdated() {
   BatchedControlUpdate batch(this);
 
@@ -1125,6 +1128,9 @@
   if (panel_->KeepDisplayedForAccessibility())
     return false;
 
+  if (MediaElement().seeking())
+    return false;
+
   return true;
 }
 
@@ -1620,8 +1626,7 @@
       is_mouse_over_controls_ = true;
       if (!MediaElement().paused()) {
         MakeOpaqueFromPointerEvent();
-        if (ShouldHideMediaControls())
-          StartHideMediaControlsTimer();
+        StartHideMediaControlsIfNecessary();
       }
     }
   } else if (event->type() == event_type_names::kPointerout) {
@@ -1804,6 +1809,11 @@
   overlay_cast_button_->SetIsWanted(false);
 }
 
+void MediaControlsImpl::StartHideMediaControlsIfNecessary() {
+  if (ShouldHideMediaControls())
+    StartHideMediaControlsTimer();
+}
+
 void MediaControlsImpl::StartHideMediaControlsTimer() {
   hide_media_controls_timer_.StartOneShot(
       GetTimeWithoutMouseMovementBeforeHidingMediaControls(), FROM_HERE);
@@ -1877,8 +1887,7 @@
 }
 
 void MediaControlsImpl::OnTimeUpdate() {
-  timeline_->SetPosition(MediaElement().currentTime());
-  UpdateCurrentTimeDisplay();
+  UpdateTimeIndicators();
 
   // 'timeupdate' might be called in a paused state. The controls should not
   // become transparent in that case.
@@ -1920,8 +1929,7 @@
 
 void MediaControlsImpl::OnPlay() {
   UpdatePlayState();
-  timeline_->SetPosition(MediaElement().currentTime());
-  UpdateCurrentTimeDisplay();
+  UpdateTimeIndicators();
   UpdateCSSClassFromState();
 }
 
@@ -1934,8 +1942,7 @@
 
 void MediaControlsImpl::OnPause() {
   UpdatePlayState();
-  timeline_->SetPosition(MediaElement().currentTime());
-  UpdateCurrentTimeDisplay();
+  UpdateTimeIndicators();
   MakeOpaque();
 
   StopHideMediaControlsTimer();
@@ -1943,6 +1950,32 @@
   UpdateCSSClassFromState();
 }
 
+void MediaControlsImpl::OnSeeking() {
+  UpdateTimeIndicators();
+  if (!is_scrubbing_) {
+    is_scrubbing_ = true;
+    UpdateCSSClassFromState();
+  }
+
+  // Don't try to show the controls if the seek was caused by the video being
+  // looped.
+  if (MediaElement().Loop() && MediaElement().currentTime() == 0)
+    return;
+
+  if (!MediaElement().ShouldShowControls())
+    return;
+
+  MaybeShow();
+  StopHideMediaControlsTimer();
+}
+
+void MediaControlsImpl::OnSeeked() {
+  StartHideMediaControlsIfNecessary();
+
+  is_scrubbing_ = false;
+  UpdateCSSClassFromState();
+}
+
 void MediaControlsImpl::OnTextTracksAddedOrRemoved() {
   toggle_closed_captions_button_->UpdateDisplayType();
   toggle_closed_captions_button_->SetIsWanted(
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl.h b/third_party/blink/renderer/modules/media_controls/media_controls_impl.h
index d3017292..6766552 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl.h
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl.h
@@ -282,6 +282,9 @@
 
   void ElementSizeChangedTimerFired(TimerBase*);
 
+  // Update any visible indicators of the current time.
+  void UpdateTimeIndicators();
+
   // Hide elements that don't fit, and show those things that we want which
   // do fit.  This requires that m_effectiveWidth and m_effectiveHeight are
   // current.
@@ -338,6 +341,8 @@
   void OnPlay();
   void OnPlaying();
   void OnPause();
+  void OnSeeking();
+  void OnSeeked();
   void OnTextTracksAddedOrRemoved();
   void OnTextTracksChanged();
   void OnError();
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
index 0b92a45..3b97ac8 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl_test.cc
@@ -222,6 +222,9 @@
 
   void SimulateLoadedMetadata() { media_controls_->OnLoadedMetadata(); }
 
+  void SimulateOnSeeking() { media_controls_->OnSeeking(); }
+  void SimulateOnSeeked() { media_controls_->OnSeeked(); }
+
   MediaControlsImpl& MediaControls() { return *media_controls_; }
   MediaControlVolumeSliderElement* VolumeSliderElement() const {
     return media_controls_->volume_slider_;
@@ -624,6 +627,32 @@
   EXPECT_EQ(duration / 2, current_time_display->CurrentValue());
 }
 
+TEST_F(MediaControlsImplTest, TimeIndicatorsUpdatedOnSeeking) {
+  EnsureSizing();
+
+  MediaControlCurrentTimeDisplayElement* current_time_display =
+      GetCurrentTimeDisplayElement();
+  MediaControlTimelineElement* timeline = TimelineElement();
+  double duration = 1000;
+  LoadMediaWithDuration(duration);
+
+  EXPECT_EQ(0, current_time_display->CurrentValue());
+  EXPECT_EQ(0, timeline->valueAsNumber());
+
+  MediaControls().MediaElement().setCurrentTime(duration / 4);
+
+  // Time indicators are not yet updated.
+  EXPECT_EQ(0, current_time_display->CurrentValue());
+  EXPECT_EQ(0, timeline->valueAsNumber());
+
+  SimulateOnSeeking();
+
+  // The time indicators should be updated immediately when the 'seeking' event
+  // is fired.
+  EXPECT_EQ(duration / 4, current_time_display->CurrentValue());
+  EXPECT_EQ(duration / 4, timeline->valueAsNumber());
+}
+
 TEST_F(MediaControlsImplTest, TimelineMetricsWidth) {
   MediaControls().MediaElement().SetSrc("https://example.com/foo.mp4");
   test::RunPendingTasks();
@@ -863,6 +892,45 @@
 
 }  // namespace
 
+TEST_F(MediaControlsImplTestWithMockScheduler, SeekingShowsControls) {
+  Element* panel = GetElementByShadowPseudoId(MediaControls(),
+                                              "-webkit-media-controls-panel");
+  ASSERT_NE(nullptr, panel);
+
+  MediaControls().MediaElement().SetSrc("http://example.com");
+  MediaControls().MediaElement().Play();
+
+  // Hide the controls to start.
+  MediaControls().Hide();
+  EXPECT_FALSE(IsElementVisible(*panel));
+
+  // Seeking should cause the controls to become visible.
+  SimulateOnSeeking();
+  EXPECT_TRUE(IsElementVisible(*panel));
+}
+
+TEST_F(MediaControlsImplTestWithMockScheduler,
+       SeekingDoesNotShowControlsWhenNoControlsAttr) {
+  Element* panel = GetElementByShadowPseudoId(MediaControls(),
+                                              "-webkit-media-controls-panel");
+  ASSERT_NE(nullptr, panel);
+
+  MediaControls().MediaElement().SetBooleanAttribute(html_names::kControlsAttr,
+                                                     false);
+
+  MediaControls().MediaElement().SetSrc("http://example.com");
+  MediaControls().MediaElement().Play();
+
+  // Hide the controls to start.
+  MediaControls().Hide();
+  EXPECT_FALSE(IsElementVisible(*panel));
+
+  // Seeking should not cause the controls to become visible because the
+  // controls attribute is not set.
+  SimulateOnSeeking();
+  EXPECT_FALSE(IsElementVisible(*panel));
+}
+
 TEST_F(MediaControlsImplTestWithMockScheduler,
        ControlsRemainVisibleDuringKeyboardInteraction) {
   EnsureSizing();
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_media_event_listener.cc b/third_party/blink/renderer/modules/media_controls/media_controls_media_event_listener.cc
index 3581220..0fe65f1 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_media_event_listener.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_media_event_listener.cc
@@ -36,6 +36,8 @@
   GetMediaElement().addEventListener(event_type_names::kPause, this, false);
   GetMediaElement().addEventListener(event_type_names::kDurationchange, this,
                                      false);
+  GetMediaElement().addEventListener(event_type_names::kSeeking, this, false);
+  GetMediaElement().addEventListener(event_type_names::kSeeked, this, false);
   GetMediaElement().addEventListener(event_type_names::kError, this, false);
   GetMediaElement().addEventListener(event_type_names::kLoadedmetadata, this,
                                      false);
@@ -177,6 +179,14 @@
     media_controls_->OnPause();
     return;
   }
+  if (event->type() == event_type_names::kSeeking) {
+    media_controls_->OnSeeking();
+    return;
+  }
+  if (event->type() == event_type_names::kSeeked) {
+    media_controls_->OnSeeked();
+    return;
+  }
   if (event->type() == event_type_names::kError) {
     media_controls_->OnError();
     return;
diff --git a/third_party/blink/web_tests/paint/invalidation/video-paint-invalidation-expected.txt b/third_party/blink/web_tests/paint/invalidation/video-paint-invalidation-expected.txt
index 9f880ce..4ab9a280 100644
--- a/third_party/blink/web_tests/paint/invalidation/video-paint-invalidation-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/video-paint-invalidation-expected.txt
@@ -24,7 +24,7 @@
       "drawsContent": false
     },
     {
-      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small test-mode phase-ready state-stopped'",
+      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small test-mode phase-ready state-scrubbing'",
       "position": [8, 8],
       "bounds": [320, 240]
     },
diff --git a/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt
index e90e866..a2be4ec 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/video/video-poster-expected.txt
@@ -76,7 +76,7 @@
       "drawsContent": false
     },
     {
-      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small phase-ready state-stopped'",
+      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small phase-ready state-scrubbing'",
       "position": [8, 8],
       "bounds": [352, 288]
     },
diff --git a/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt b/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt
index 1737809..fd66c2f 100644
--- a/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt
+++ b/third_party/blink/web_tests/platform/win/compositing/video/video-poster-expected.txt
@@ -76,7 +76,7 @@
       "drawsContent": false
     },
     {
-      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small phase-ready state-stopped'",
+      "name": "LayoutFlexibleBox (relative positioned) DIV class='sizing-small phase-ready state-scrubbing'",
       "position": [8, 8],
       "bounds": [352, 288]
     },