Adding overflow menu to media player.

Adding an overflow menu makes the media player more scalable to support additional actions and features.

BUG=601247
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2

Review-Url: https://codereview.chromium.org/2243473002
Cr-Commit-Position: refs/heads/master@{#417921}
diff --git a/content/app/strings/content_strings.grd b/content/app/strings/content_strings.grd
index a04e495..24cb84f 100644
--- a/content/app/strings/content_strings.grd
+++ b/content/app/strings/content_strings.grd
@@ -852,6 +852,36 @@
         Please lengthen this text to <ph name="MIN_CHARACTERS">$2<ex>101</ex></ph> characters or more (you are currently using <ph name="CURRENT_LENGTH">$1<ex>100</ex></ph> characters).
       </message>
 
+      <message name="IDS_AX_MEDIA_OVERFLOW_BUTTON" desc="Media controls overflow menu.">
+        Overflow menu
+      </message>
+      <message name="IDS_MEDIA_OVERFLOW_MENU_CLOSED_CAPTIONS" desc="Media controls overflow menu item label for a closed captions button. The text for this overflow menu should be short.">
+        Captions
+      </message>
+      <message name="IDS_MEDIA_OVERFLOW_MENU_CAST" desc="Media controls overflow menu item label for a cast button.">
+        Cast
+      </message>
+      <message name="IDS_MEDIA_OVERFLOW_MENU_ENTER_FULLSCREEN" desc="Media controls overflow menu item label for a button to enter fullscreen.">
+        Fullscreen
+      </message>
+      <message name="IDS_MEDIA_OVERFLOW_MENU_EXIT_FULLSCREEN" desc="Media controls overflow menu item label for a button to exit fullscreen.">
+        Exit fullscreen
+      </message>
+      <message name="IDS_MEDIA_OVERFLOW_MENU_STOP_CAST" desc="Media controls overflow menu item label for a button to stop casting.">
+        Stop casting
+      </message>
+      <message name="IDS_MEDIA_OVERFLOW_MENU_MUTE" desc="Media controls overflow menu item label for a mute button.">
+        Mute
+      </message>
+      <message name="IDS_MEDIA_OVERFLOW_MENU_UNMUTE" desc="Media controls overflow menu item label for an unmute button.">
+        Unmute
+      </message>
+      <message name="IDS_MEDIA_OVERFLOW_MENU_PLAY" desc="Media controls overflow menu item label for a play button.">
+        Play
+      </message>
+      <message name="IDS_MEDIA_OVERFLOW_MENU_PAUSE" desc="Media controls overflow menu item label for a pause button.">
+        Pause
+      </message>
       <message name="IDS_MEDIA_TRACKS_NO_LABEL" desc="Menu item label for a text track that has no name specified">
         Unknown
       </message>
diff --git a/content/child/blink_platform_impl.cc b/content/child/blink_platform_impl.cc
index 2b04ce8..c65fc9b 100644
--- a/content/child/blink_platform_impl.cc
+++ b/content/child/blink_platform_impl.cc
@@ -244,6 +244,26 @@
       return IDS_FORM_OTHER_TIME_LABEL;
     case WebLocalizedString::OtherWeekLabel:
       return IDS_FORM_OTHER_WEEK_LABEL;
+    case WebLocalizedString::AxMediaOverflowButton:
+      return IDS_AX_MEDIA_OVERFLOW_BUTTON;
+    case WebLocalizedString::OverflowMenuCaptions:
+      return IDS_MEDIA_OVERFLOW_MENU_CLOSED_CAPTIONS;
+    case WebLocalizedString::OverflowMenuCast:
+      return IDS_MEDIA_OVERFLOW_MENU_CAST;
+    case WebLocalizedString::OverflowMenuEnterFullscreen:
+      return IDS_MEDIA_OVERFLOW_MENU_ENTER_FULLSCREEN;
+    case WebLocalizedString::OverflowMenuExitFullscreen:
+      return IDS_MEDIA_OVERFLOW_MENU_EXIT_FULLSCREEN;
+    case WebLocalizedString::OverflowMenuStopCast:
+      return IDS_MEDIA_OVERFLOW_MENU_STOP_CAST;
+    case WebLocalizedString::OverflowMenuMute:
+      return IDS_MEDIA_OVERFLOW_MENU_MUTE;
+    case WebLocalizedString::OverflowMenuUnmute:
+      return IDS_MEDIA_OVERFLOW_MENU_UNMUTE;
+    case WebLocalizedString::OverflowMenuPlay:
+      return IDS_MEDIA_OVERFLOW_MENU_PLAY;
+    case WebLocalizedString::OverflowMenuPause:
+      return IDS_MEDIA_OVERFLOW_MENU_PAUSE;
     case WebLocalizedString::PlaceholderForDayOfMonthField:
       return IDS_FORM_PLACEHOLDER_FOR_DAY_OF_MONTH_FIELD;
     case WebLocalizedString::PlaceholderForMonthField:
@@ -626,6 +646,9 @@
     {"mediaplayerSubtitlesIconNew",
      IDR_MEDIAPLAYER_SUBTITLES_ICON_NEW,
      ui::SCALE_FACTOR_100P},
+    {"mediaplayerOverflowMenu",
+     IDR_MEDIAPLAYER_OVERFLOW_MENU_ICON,
+     ui::SCALE_FACTOR_100P},
     {"searchCancel", IDR_SEARCH_CANCEL, ui::SCALE_FACTOR_100P},
     {"searchCancelPressed", IDR_SEARCH_CANCEL_PRESSED, ui::SCALE_FACTOR_100P},
     {"textAreaResizeCorner", IDR_TEXTAREA_RESIZER, ui::SCALE_FACTOR_100P},
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index 86d51e16..df093130 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -239,7 +239,7 @@
 crbug.com/518915 [ Android ] accessibility/aria-labelledby-on-input.html [ Crash Pass ]
 crbug.com/445100 fast/block/float/marquee-shrink-to-avoid-floats.html [ Failure Pass ]
 
-crbug.com/636239 [ Mac10.10 Mac10.11 Retina ] media/video-zoom-controls.html [ Failure Pass ]
+crbug.com/601247 [ Mac ] media/video-zoom-controls.html [ NeedsRebaseline ]
 crbug.com/636239 [ Win7 ] media/video-zoom-controls.html [ Failure ]
 
 crbug.com/548226 fast/events/pointerevents/mouse-pointer-event-properties.html [ Failure Pass ]
diff --git a/third_party/WebKit/LayoutTests/media/fullscreen-controls-visible-last.html b/third_party/WebKit/LayoutTests/media/fullscreen-controls-visible-last.html
index abf9bfb5..f46b8f29 100644
--- a/third_party/WebKit/LayoutTests/media/fullscreen-controls-visible-last.html
+++ b/third_party/WebKit/LayoutTests/media/fullscreen-controls-visible-last.html
@@ -1,18 +1,29 @@
 <!DOCTYPE html>
-<title>Tests that the fullscreen button control is visible last.</title>
+<title>Tests that the fullscreen button control is the antepenultimate media controls button visible.</title>
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
 <script src="media-file.js"></script>
 <script src="media-controls.js"></script>
+<script src="overflow-menu.js"></script>
 <video controls></video>
 <script>
 async_test(function(t) {
     var video = document.querySelector("video");
     video.src = findMediaFile("video", "content/test");
-    video.setAttribute("width", "70");
+    // Since this is a video, we'll have three elements visible when the width
+    // is 120. These three should be: play, fullscreen, overflow.
+    video.setAttribute("width", "120");
     video.addTextTrack("captions");
     video.onloadeddata = t.step_func_done(function() {
+        // Play button should be visible
+        var playButton = mediaControlsButton(video, "play-button");
+        assert_not_equals(getComputedStyle(playButton).display, "none");
+        // Full screen button should be visible
         assert_true(hasFullscreenButton(video));
+        // Overflow menu button should be visible
+        var overflowMenuButton = getOverflowMenuButton(video);
+        assert_not_equals(getComputedStyle(overflowMenuButton).display, "none");
+        // Closed captions shouldn't
         assert_false(isClosedCaptionsButtonVisible(video));
     });
 });
diff --git a/third_party/WebKit/LayoutTests/media/media-controls-play-button-visible.html b/third_party/WebKit/LayoutTests/media/media-controls-play-button-visible.html
deleted file mode 100644
index 5d8fbc1..0000000
--- a/third_party/WebKit/LayoutTests/media/media-controls-play-button-visible.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <title>video controls play button always visible</title>
-        <script src="../resources/testharness.js"></script>
-        <script src="../resources/testharnessreport.js"></script>
-        <script src="media-controls.js"></script>
-        <style>
-          audio {
-              width: 24px;
-          }
-        </style>
-    </head>
-    <body onload="async_test(testPlayButtonVisible)">
-        <video id="video" width="24px" controls></video>
-        <audio id="audio" controls></video>
-        <script>
-        function checkOneElement(test, id) {
-          var element = document.getElementById(id);
-
-          // Check that the play button is shown.
-          var playButton = mediaControlsButton(element, "play-button");
-          assert_true(getComputedStyle(playButton).display != "none",
-              "play button should not be hidden for " + id);
-
-          // Also check for something that should be hidden, just to be sure
-          // that things are being recomputed properly before the test runs.
-          // This only works with the new media playback UI, since the old one
-          // doesn't hide anything based on width.
-          if (window.internals.runtimeFlags.newMediaPlaybackUiEnabled) {
-              var timeline = mediaControlsButton(element, "timeline");
-              assert_true(getComputedStyle(timeline).display == "none",
-                  "timeline should be hidden for " + id);
-          }
-        }
-
-        function testPlayButtonVisible(test) {
-          checkOneElement(test, "video");
-          checkOneElement(test, "audio");
-          test.done();
-        }
-        </script>
-        </video>
-    </body>
-</html>
diff --git a/third_party/WebKit/LayoutTests/media/overflow-menu.js b/third_party/WebKit/LayoutTests/media/overflow-menu.js
new file mode 100644
index 0000000..b03abfe
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/overflow-menu.js
@@ -0,0 +1,34 @@
+// We expect the items in the overflow to appear in the following ordering.
+var overflowButtonsCSS = [
+    "-webkit-media-controls-mute-button",
+    "-internal-media-controls-cast-button",
+    "-webkit-media-controls-toggle-closed-captions-button",
+    "-webkit-media-controls-fullscreen-button",
+    "-webkit-media-controls-play-button"];
+//  PseudoID for the overflow button
+var menuID = "-internal-overflow-menu-button";
+//  PseudoID for the overflow list
+var listID = "-internal-media-controls-overflow-menu-list";
+
+// Returns the overflow menu button within the given media element
+function getOverflowMenuButton(media) {
+  return mediaControlsElement(internals.shadowRoot(media).firstChild, menuID);
+}
+
+// Returns the overflow menu list of overflow controls
+function getOverflowList(media) {
+  return mediaControlsElement(internals.shadowRoot(media).firstChild, listID);
+}
+
+// Location of media control element in the overflow button
+var OverflowMenuButtons = {
+  MUTE: 0,
+  CAST: 1,
+  CLOSED_CAPTIONS: 2,
+  FULLSCREEN: 3,
+  PLAY: 4,
+};
+
+// Default text within the overflow menu
+var overflowMenuText = ["Mute", "Cast", "Captions", "Fullscreen", "Play"];
+
diff --git a/third_party/WebKit/LayoutTests/media/track/cue-style-invalidation.html b/third_party/WebKit/LayoutTests/media/track/cue-style-invalidation.html
index cea2f2e..d29b02d0 100644
--- a/third_party/WebKit/LayoutTests/media/track/cue-style-invalidation.html
+++ b/third_party/WebKit/LayoutTests/media/track/cue-style-invalidation.html
@@ -37,7 +37,7 @@
         ascendant.offsetTop;
         ascendant.classList.add("cue");
         if (window.internals)
-            assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 9);
+            assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 10);
         assert_equals(getComputedStyle(cueNode).backgroundColor, green);
 
         assert_equals(getComputedStyle(cNode).backgroundColor, red);
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-appears-when-expected.html b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-appears-when-expected.html
new file mode 100644
index 0000000..c7cc650
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-appears-when-expected.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Overflow menu appears at the right time.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="media-controls.js"></script>
+<script src="media-file.js"></script>
+<script src="overflow-menu.js"></script>
+
+<!--Padding ensures the overflow menu is visible for the tests. -->
+<body style="padding-top: 200px; padding-left: 100px">
+<video controls></video>
+<script>
+async_test(function(t) {
+  // Set up video
+  var video = document.querySelector("video");
+  video.src = findMediaFile("video", "content/test");
+  // At this width, the mute and cast button don't fit. Since we have
+  // two elements that don't fit, the overflow menu should be visible.
+  video.setAttribute("width", "240");
+  // Add captions
+  var track = video.addTextTrack("captions");
+  // Pretend we have a cast device
+  internals.mediaPlayerRemoteRouteAvailabilityChanged(video, true);
+
+  video.onloadeddata = t.step_func_done(function() {
+    var overflowMenu = getOverflowMenuButton(video);
+    var overflowList = getOverflowList(video);
+    var children = overflowList.children;
+
+    // Overflow menu button should be visible
+    assert_not_equals(getComputedStyle(overflowMenu).display, "none");
+  });
+});
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-closed-captions-button.html b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-closed-captions-button.html
new file mode 100644
index 0000000..418d70ae
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-closed-captions-button.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Clicking on the overflow closed captions button shows the closed captions menu.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="media-controls.js"></script>
+<script src="media-file.js"></script>
+<script src="overflow-menu.js"></script>
+
+<!--Padding ensures the overflow menu is visible for the tests. -->
+<body style="padding-top: 200px; padding-left: 100px">
+<video controls></video>
+<script>
+async_test(function(t) {
+  // Set up video
+  var video = document.querySelector("video");
+  video.src = findMediaFile("video", "content/test");
+  video.setAttribute("width", "60");
+  // Add captions
+  var track = video.addTextTrack("captions");
+
+  video.onloadeddata = t.step_func_done(function() {
+    var overflowList = getOverflowList(video);
+    var overflowMenu = getOverflowMenuButton(video);
+
+    // Get the menu that displays the list of text tracks
+   var captionsList = mediaControlsElement(internals.shadowRoot(video).firstChild, "-internal-media-controls-text-track-list");
+
+    // Initially the list should not be visible
+    assert_equals(getComputedStyle(captionsList).display, "none");
+
+    // Click on the overflow menu button
+    var coords = elementCoordinates(overflowMenu);
+    clickAtCoordinates(coords[0], coords[1]);
+
+    // Click on the closed captions button
+    var coords = elementCoordinates(overflowList.children[OverflowMenuButtons.CLOSED_CAPTIONS]);
+    clickAtCoordinates(coords[0], coords[1]);
+    assert_not_equals(getComputedStyle(captionsList).display, "none");
+  });
+});
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-correct-ordering.html b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-correct-ordering.html
new file mode 100644
index 0000000..d02a6063
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-correct-ordering.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Overflow menu children appear in correct order.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="media-controls.js"></script>
+<script src="media-file.js"></script>
+<script src="overflow-menu.js"></script>
+
+<!--Padding ensures the overflow menu is visible for the tests. -->
+<body style="padding-top: 200px; padding-left: 100px">
+<video controls></video>
+<script>
+async_test(function(t) {
+  // Set up video
+  var video = document.querySelector("video");
+  video.src = findMediaFile("video", "content/test");
+  video.setAttribute("width", "60");
+  // Add captions
+  var track = video.addTextTrack("captions");
+  // Pretend we have a cast device
+  internals.mediaPlayerRemoteRouteAvailabilityChanged(video, true);
+
+  video.onloadeddata = t.step_func_done(function() {
+    var overflowList = getOverflowList(video);
+    var children = overflowList.children;
+
+    // The overflow menu should always have the same number of elements.
+    // Their visibility will change over time based on the size of the video.
+    assert_equals(children.length, 5);
+
+    // Ensure that all of the buttons are visible in the right order
+    for (var i = 0; i < children.length; i++) {
+      var child = children[i];
+      var innerButton = child.children[0];
+      assert_equals(internals.shadowPseudoId(child), "-internal-media-controls-overflow-menu-list-item");
+      assert_equals(internals.shadowPseudoId(innerButton), overflowButtonsCSS[i]);
+      // Items should be visible
+      assert_not_equals(getComputedStyle(child).display, "none");
+      // Buttons shouldn't be visible
+      assert_equals(getComputedStyle(innerButton).display, "none");
+    }
+  });
+});
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-fullscreen-button.html b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-fullscreen-button.html
new file mode 100644
index 0000000..db1fd286
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-fullscreen-button.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Clicking on the overflow fullscreen button opens the video in fullscreen.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="media-controls.js"></script>
+<script src="media-file.js"></script>
+<script src="overflow-menu.js"></script>
+
+<!--Padding ensures the overflow menu is visible for the tests. -->
+<body style="padding-top: 200px; padding-left: 100px">
+<video controls></video>
+<script>
+async_test(function(t) {
+  // Set up video
+  var video = document.querySelector("video");
+  video.src = findMediaFile("video", "content/test");
+  video.setAttribute("width", "60");
+
+  window.addEventListener("load", t.step_func(function() {
+    var overflowList = getOverflowList(video);
+    var overflowMenu = getOverflowMenuButton(video);
+
+    // Click on the overflow menu button
+    var coords = elementCoordinates(overflowMenu);
+    clickAtCoordinates(coords[0], coords[1]);
+
+    // Clicking on the fullscreen button should open up the video in fullscreen
+    var coords = elementCoordinates(overflowList.children[OverflowMenuButtons.FULLSCREEN]);
+    clickAtCoordinates(coords[0], coords[1]);
+
+    document.onwebkitfullscreenchange = t.step_func_done(() => {
+      assert_equals(document.fullscreenElement, video);
+      assert_equals(getComputedStyle(overflowMenu).display, "none");
+    });
+  }));
+});
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-last-button-visible.html b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-last-button-visible.html
new file mode 100644
index 0000000..4446dc1
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-last-button-visible.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Overflow menu should be the last media controls button visible.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="media-controls.js"></script>
+<script src="media-file.js"></script>
+<script src="overflow-menu.js"></script>
+
+<!--Padding ensures the overflow menu is visible for the tests. -->
+<body style="padding-top: 200px; padding-left: 100px">
+<video controls style="width: 24px"></video>
+<audio controls style="width: 24px"></audio>
+<script>
+async_test(function(t) {
+   // Set up video
+  var video = document.querySelector("video");
+  video.src = findMediaFile("video", "content/test");
+
+  var audio = document.querySelector("audio");
+  audio.src = findMediaFile("audio", "content/test");
+
+  video.onloadeddata = t.step_func_done(function() {
+    var overflowVideo = getOverflowMenuButton(video);
+    var overflowAudio = getOverflowMenuButton(audio);
+
+    // Overflow menu button should be visible
+    assert_not_equals(getComputedStyle(overflowVideo).display, "none");
+    assert_not_equals(getComputedStyle(overflowAudio).display, "none");
+  });
+});
+</script>
+</body>
+
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-mute-button.html b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-mute-button.html
new file mode 100644
index 0000000..9ce5a68
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-mute-button.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Clicking on the overflow mute button mutes the video.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="media-controls.js"></script>
+<script src="media-file.js"></script>
+<script src="overflow-menu.js"></script>
+
+<!--Padding ensures the overflow menu is visible for the tests. -->
+<body style="padding-top: 200px; padding-left: 100px">
+<video controls></video>
+<script>
+async_test(function(t) {
+  // Set up video
+  var video = document.querySelector("video");
+  video.src = findMediaFile("video", "content/test");
+  video.setAttribute("width", "60");
+
+  video.onloadeddata = t.step_func_done(function() {
+    var overflowList = getOverflowList(video);
+    var overflowMenu = getOverflowMenuButton(video);
+
+    // Video is unmuted to begin with.
+    assert_false(video.muted);
+
+    // Click on the overflow menu button
+    var coords = elementCoordinates(overflowMenu);
+    clickAtCoordinates(coords[0], coords[1]);
+
+    // Click on mute button
+    var coords = elementCoordinates(overflowList.children[OverflowMenuButtons.MUTE]);
+    clickAtCoordinates(coords[0], coords[1]);
+    assert_true(video.muted);
+  });
+});
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-play-button.html b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-play-button.html
new file mode 100644
index 0000000..07a0562
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-play-button.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Clicking on the overflow play button plays the video.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="media-controls.js"></script>
+<script src="media-file.js"></script>
+<script src="overflow-menu.js"></script>
+
+<!--Padding ensures the overflow menu is visible for the tests. -->
+<body style="padding-top: 200px; padding-left: 100px">
+<video controls></video>
+<script>
+async_test(function(t) {
+  // Set up video
+  var video = document.querySelector("video");
+  video.src = findMediaFile("video", "content/test");
+  video.setAttribute("width", "60");
+
+  video.onloadeddata = t.step_func_done(function() {
+    var overflowList = getOverflowList(video);
+    var overflowMenu = getOverflowMenuButton(video);
+
+    // Video is paused to being with
+    assert_true(video.paused);
+
+    // Click on the overflow menu button
+    var coords = elementCoordinates(overflowMenu);
+    clickAtCoordinates(coords[0], coords[1]);
+
+    // Click on mute button
+    var coords = elementCoordinates(overflowList.children[OverflowMenuButtons.PLAY]);
+    clickAtCoordinates(coords[0], coords[1]);
+    assert_false(video.paused);
+  });
+});
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-text.html b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-text.html
new file mode 100644
index 0000000..9ea0cd2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-text.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Overflow menu displays the correct text.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="media-controls.js"></script>
+<script src="media-file.js"></script>
+<script src="overflow-menu.js"></script>
+
+<!--Padding ensures the overflow menu is visible for the tests. -->
+<body style="padding-top: 200px; padding-left: 100px">
+<video controls></video>
+<script>
+async_test(function(t) {
+  // Set up video
+  var video = document.querySelector("video");
+  video.src = findMediaFile("video", "content/test");
+  // Add captions
+  var track = video.addTextTrack("captions");
+  // Pretend we have a cast device
+  internals.mediaPlayerRemoteRouteAvailabilityChanged(video, true);
+
+  video.onloadeddata = t.step_func_done(function() {
+  var overflowList = getOverflowList(video);
+    var children = overflowList.children;
+    // Ensure that all of the buttons are visible in the right order
+    for (var i = 0; i < children.length; i++) {
+      var child = children[i];
+      var innerButton = child.children[0];
+      assert_equals(child.textContent, overflowMenuText[i]);
+    }
+  });
+});
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-updates-appropriately.html b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-updates-appropriately.html
new file mode 100644
index 0000000..930ca1ea
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-updates-appropriately.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Overflow menu updates properly.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="media-controls.js"></script>
+<script src="media-file.js"></script>
+<script src="overflow-menu.js"></script>
+
+<!--Padding ensures the overflow menu is visible for the tests. -->
+<body style="padding-top: 200px; padding-left: 100px">
+<video controls></video>
+<script>
+async_test(function(t) {
+  // Set up video
+  var video = document.querySelector("video");
+  video.src = findMediaFile("video", "content/test");
+  video.setAttribute("width", "60");
+  // Add captions
+  var trackElement = document.createElement("track");
+  video.appendChild(trackElement);
+  // Pretend we have a cast device
+  internals.mediaPlayerRemoteRouteAvailabilityChanged(video, true);
+
+  video.onloadeddata = t.step_func_done(function() {
+    var overflowList = getOverflowList(video);
+
+    // Remove cast device and ensure the overflow menu updates as expected
+    // Cast option in overflow should no longer be visible, but the other
+    // options should all be.
+    var buttonsWithoutCast = overflowButtonsCSS;
+    buttonsWithoutCast[1] = undefined;
+    internals.mediaPlayerRemoteRouteAvailabilityChanged(video, false);
+
+    var children = overflowList.children;
+    // Ensure that all of the buttons are visible in the right order
+    for (var i = 0; i < children.length; i++) {
+      var child = children[i];
+      if (buttonsWithoutCast[i]) {
+        assert_not_equals(getComputedStyle(child).display, "none");
+      } else {
+        assert_equals(getComputedStyle(child).display, "none");
+      }
+    }
+    internals.mediaPlayerRemoteRouteAvailabilityChanged(video, true);
+    assert_not_equals(getComputedStyle(children[1]).display, "none");
+
+    // Removing closed captions hides button in overflow menu
+    assert_not_equals(getComputedStyle(children[2]).display, "none");
+    video.removeChild(trackElement);
+    assert_equals(getComputedStyle(children[2]).display, "none");
+  });
+});
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-visibility.html b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-visibility.html
new file mode 100644
index 0000000..0c19c30
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/media/video-controls-overflow-menu-visibility.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Ensure overflow menu buttons are visible when expected.</title>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="media-controls.js"></script>
+<script src="media-file.js"></script>
+<script src="overflow-menu.js"></script>
+
+<!--Padding ensures the overflow menu is visible for the tests. -->
+<body style="padding-top: 200px; padding-left: 100px">
+<video controls></video>
+<script>
+async_test(function(t) {
+  // Set up video
+  var video = document.querySelector("video");
+  video.src = findMediaFile("video", "content/test");
+  video.setAttribute("width", "60");
+  // Add captions
+  var track = video.addTextTrack("captions");
+  // Pretend we have a cast device
+  internals.mediaPlayerRemoteRouteAvailabilityChanged(video, true);
+
+  video.onloadeddata = t.step_func_done(function() {
+    var overflowList = getOverflowList(video);
+    var overflowMenu = getOverflowMenuButton(video);
+
+    // Overflow menu button should be visible
+    assert_not_equals(getComputedStyle(overflowMenu).display, "none");
+
+    // Overflow list shouldn't be visible until it's clicked on
+    assert_equals(getComputedStyle(overflowList).display, "none");
+
+    // Clicking on the overflow menu button should make the overflow list visible
+    var coords = elementCoordinates(overflowMenu);
+    clickAtCoordinates(coords[0], coords[1]);
+    assert_not_equals(getComputedStyle(overflowList).display, "none");
+
+    // Click on the overflow menu button, again. Should close overflow list.
+    var coords = elementCoordinates(overflowMenu);
+    clickAtCoordinates(coords[0], coords[1]);
+    assert_equals(getComputedStyle(overflowList).display, "none");
+  });
+});
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/video-mute-repaint-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/video-mute-repaint-expected.txt
index d0e9a04b..8b5fe4e 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/video-mute-repaint-expected.txt
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/video-mute-repaint-expected.txt
@@ -63,6 +63,10 @@
   ],
   "objectPaintInvalidations": [
     {
+      "object": "LayoutVideo VIDEO id='v'",
+      "reason": "style change"
+    },
+    {
       "object": "LayoutButton INPUT",
       "reason": "full"
     },
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/video-unmute-repaint-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/video-unmute-repaint-expected.txt
index 9757a68..17afb693 100644
--- a/third_party/WebKit/LayoutTests/paint/invalidation/video-unmute-repaint-expected.txt
+++ b/third_party/WebKit/LayoutTests/paint/invalidation/video-unmute-repaint-expected.txt
@@ -63,6 +63,10 @@
   ],
   "objectPaintInvalidations": [
     {
+      "object": "LayoutVideo VIDEO id='v'",
+      "reason": "style change"
+    },
+    {
       "object": "LayoutButton INPUT",
       "reason": "full"
     },
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png
index cf7063e6..ab718d36 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.png
Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.txt
index 861dd84..e34644dc 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/media/video-zoom-controls-expected.txt
@@ -13,7 +13,7 @@
     LayoutBlockFlow {DIV} at (0,132) size 240x48
 layer at (57,85) size 240x117
   LayoutFlexibleBox (relative positioned) {DIV} at (0,0) size 240x117
-layer at (57,217) size 240x48 scrollWidth 314 scrollHeight 60
+layer at (57,217) size 240x48 scrollWidth 271 scrollHeight 60
   LayoutFlexibleBox (relative positioned) {DIV} at (0,0) size 240x48 [bgcolor=#FAFAFA]
     LayoutButton {INPUT} at (0,0) size 48x48
     LayoutFlexibleBox {DIV} at (48,0) size 35x48 [color=#5A5A5A]
@@ -24,12 +24,9 @@
       LayoutFlexibleBox {DIV} at (0,0) size 37.50x3
         LayoutBlockFlow {DIV} at (-27,-34.50) size 91.50x72
           LayoutBlockFlow {DIV} at (0,0) size 54x72
-    LayoutSlider {INPUT} at (201.50,22.50) size 37.50x3
-      LayoutFlexibleBox {DIV} at (0,0) size 37.50x3
-        LayoutBlockFlow {DIV} at (-27,-34.50) size 91.50x72
-          LayoutBlockFlow {DIV} at (37.50,0) size 54x72
-layer at (323,217) size 48x48 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
-  LayoutButton {INPUT} at (266,0) size 48x48
+    LayoutButton {INPUT} at (174.50,0) size 48x48
+layer at (280,217) size 48x48 backgroundClip at (280,217) size 17x48 clip at (280,217) size 17x48
+  LayoutButton {INPUT} at (222.50,0) size 48x48
 layer at (57,310) size 240x180
   LayoutVideo {VIDEO} at (45,298) size 240x180
 layer at (57,310) size 240x180
@@ -37,7 +34,7 @@
     LayoutBlockFlow {DIV} at (0,132) size 240x48
 layer at (57,310) size 240x117
   LayoutFlexibleBox (relative positioned) {DIV} at (0,0) size 240x117
-layer at (57,442) size 240x48 scrollWidth 314 scrollHeight 60
+layer at (57,442) size 240x48 scrollWidth 271 scrollHeight 60
   LayoutFlexibleBox (relative positioned) {DIV} at (0,0) size 240x48 [bgcolor=#FAFAFA]
     LayoutButton {INPUT} at (0,0) size 48x48
     LayoutFlexibleBox {DIV} at (48,0) size 35x48 [color=#5A5A5A]
@@ -48,9 +45,6 @@
       LayoutFlexibleBox {DIV} at (0,0) size 37.50x3
         LayoutBlockFlow {DIV} at (-27,-34.50) size 91.50x72
           LayoutBlockFlow {DIV} at (0,0) size 54x72
-    LayoutSlider {INPUT} at (201.50,22.50) size 37.50x3
-      LayoutFlexibleBox {DIV} at (0,0) size 37.50x3
-        LayoutBlockFlow {DIV} at (-27,-34.50) size 91.50x72
-          LayoutBlockFlow {DIV} at (37.50,0) size 54x72
-layer at (323,442) size 48x48 backgroundClip at (0,0) size 0x0 clip at (0,0) size 0x0
-  LayoutButton {INPUT} at (266,0) size 48x48
+    LayoutButton {INPUT} at (174.50,0) size 48x48
+layer at (280,442) size 48x48 backgroundClip at (280,442) size 12x27 clip at (280,442) size 12x27
+  LayoutButton {INPUT} at (222.50,0) size 48x48
diff --git a/third_party/WebKit/Source/core/css/CSSPrimitiveValueMappings.h b/third_party/WebKit/Source/core/css/CSSPrimitiveValueMappings.h
index c74b659f..07e8686a 100644
--- a/third_party/WebKit/Source/core/css/CSSPrimitiveValueMappings.h
+++ b/third_party/WebKit/Source/core/css/CSSPrimitiveValueMappings.h
@@ -465,6 +465,9 @@
     case MediaSubtitlesIconPart:
         m_value.valueID = CSSValueInternalMediaSubtitlesIcon;
         break;
+    case MediaOverflowMenuButtonPart:
+        m_value.valueID = CSSValueInternalOverflowMenuButton;
+        break;
     case MenulistPart:
         m_value.valueID = CSSValueMenulist;
         break;
diff --git a/third_party/WebKit/Source/core/css/CSSValueKeywords.in b/third_party/WebKit/Source/core/css/CSSValueKeywords.in
index bea9811..3e2f9271 100644
--- a/third_party/WebKit/Source/core/css/CSSValueKeywords.in
+++ b/third_party/WebKit/Source/core/css/CSSValueKeywords.in
@@ -647,6 +647,7 @@
 -internal-media-track-selection-checkmark
 -internal-media-closed-captions-icon
 -internal-media-subtitles-icon
+-internal-overflow-menu-button
 menulist
 menulist-button
 menulist-text
diff --git a/third_party/WebKit/Source/core/css/mediaControlsNew.css b/third_party/WebKit/Source/core/css/mediaControlsNew.css
index 1011a2ab..348ac85 100644
--- a/third_party/WebKit/Source/core/css/mediaControlsNew.css
+++ b/third_party/WebKit/Source/core/css/mediaControlsNew.css
@@ -359,7 +359,7 @@
     display: none;
 }
 
-video::-internal-media-controls-text-track-list {
+video::-internal-media-controls-text-track-list, video::-internal-media-controls-overflow-menu-list, audio::-internal-media-controls-overflow-menu-list {
     position: absolute;
     bottom: 48px;
     right: 0px;
@@ -383,7 +383,7 @@
     text-overflow: ellipsis;
 }
 
-video::-internal-media-controls-text-track-list-item:hover {
+video::-internal-media-controls-text-track-list-item:hover, video::-internal-media-controls-overflow-menu-list-item:hover, audio::-internal-media-controls-overflow-menu-list-item:hover {
     background-color: #e0e0e0;
 }
 
@@ -418,6 +418,28 @@
     vertical-align: middle;
 }
 
+video::-internal-overflow-menu-button, audio::-internal-overflow-menu-button {
+    -webkit-appearance: -internal-overflow-menu-button;
+    display: flex;
+    flex: none;
+    box-sizing: border-box;
+    width: 32px;
+    height: 32px;
+    padding: 0px;
+    border-width: 0px;
+    margin-left: 0px;
+    margin-right: 0px;
+    background-color: initial;
+    color: inherit;
+}
+
+video::-internal-media-controls-overflow-menu-list-item, audio::-internal-media-controls-overflow-menu-list-item {
+    display: block;
+    color: #424242;
+    line-height: 40px;
+    padding-left: 28px;
+}
+
 video::-webkit-media-text-track-container {
     position: relative;
     width: inherit;
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControlElementTypes.cpp b/third_party/WebKit/Source/core/html/shadow/MediaControlElementTypes.cpp
index df448fed..09b128d 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControlElementTypes.cpp
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControlElementTypes.cpp
@@ -33,9 +33,12 @@
 #include "core/CSSValueKeywords.h"
 #include "core/HTMLNames.h"
 #include "core/css/StylePropertySet.h"
+#include "core/dom/Text.h"
 #include "core/events/MouseEvent.h"
+#include "core/html/HTMLLabelElement.h"
 #include "core/html/HTMLMediaElement.h"
 #include "core/html/shadow/MediaControls.h"
+#include "platform/text/PlatformLocale.h"
 
 namespace blink {
 
@@ -116,10 +119,34 @@
         object->setShouldDoFullPaintInvalidation();
 }
 
+void MediaControlElement::shouldShowButtonInOverflowMenu(bool shouldShow)
+{
+    if (!hasOverflowButton())
+        return;
+    if (shouldShow) {
+        m_overflowMenuElement->removeInlineStyleProperty(CSSPropertyDisplay);
+    } else {
+        m_overflowMenuElement->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
+    }
+}
+
+String MediaControlElement::getOverflowMenuString()
+{
+    return mediaElement().locale().queryString(getOverflowStringName());
+}
+
+void MediaControlElement::updateOverflowString()
+{
+    if (m_overflowMenuElement && m_overflowMenuText)
+        m_overflowMenuText->replaceWholeText(getOverflowMenuString());
+}
+
 DEFINE_TRACE(MediaControlElement)
 {
     visitor->trace(m_mediaControls);
     visitor->trace(m_element);
+    visitor->trace(m_overflowMenuElement);
+    visitor->trace(m_overflowMenuText);
 }
 
 // ----------------------------
@@ -149,6 +176,26 @@
     return false;
 }
 
+HTMLElement* MediaControlInputElement::createOverflowElement(MediaControls& mediaControls, MediaControlInputElement* button)
+{
+    if (!button)
+        return nullptr;
+
+    // We don't want the button visible within the overflow menu.
+    button->setIsWanted(false);
+
+    m_overflowMenuText = Text::create(mediaControls.document(), button->getOverflowMenuString());
+
+    HTMLLabelElement* element = HTMLLabelElement::create(mediaControls.document());
+    element->setShadowPseudoId(AtomicString("-internal-media-controls-overflow-menu-list-item"));
+    // Appending a button to a label element ensures that clicks on the label
+    // are passed down to the button, performing the action we'd expect.
+    element->appendChild(button);
+    element->appendChild(m_overflowMenuText);
+    m_overflowMenuElement = element;
+    return element;
+}
+
 DEFINE_TRACE(MediaControlInputElement)
 {
     MediaControlElement::trace(visitor);
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControlElementTypes.h b/third_party/WebKit/Source/core/html/shadow/MediaControlElementTypes.h
index 00241eaa..8ba41a0 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControlElementTypes.h
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControlElementTypes.h
@@ -34,6 +34,7 @@
 #include "core/html/HTMLDivElement.h"
 #include "core/html/HTMLInputElement.h"
 #include "core/layout/LayoutBlock.h"
+#include "public/platform/WebLocalizedString.h"
 
 namespace blink {
 
@@ -67,6 +68,8 @@
     MediaCastOnButton,
     MediaOverlayCastOffButton,
     MediaOverlayCastOnButton,
+    MediaOverflowButton,
+    MediaOverflowList,
 };
 
 CORE_EXPORT const HTMLMediaElement* toParentMediaElement(const Node*);
@@ -89,6 +92,21 @@
 
     MediaControlElementType displayType() const { return m_displayType; }
 
+    // By default, media controls elements are not added to the overflow menu.
+    // Controls that can be added to the overflow menu should override this
+    // function and return true.
+    virtual bool hasOverflowButton() { return false; }
+
+    // If true, shows the overflow menu item if it exists. Hides it if false.
+    void shouldShowButtonInOverflowMenu(bool);
+
+    // Returns a string representation of the media control element. Used for
+    // the overflow menu.
+    String getOverflowMenuString();
+
+    // Updates the value of the Text string shown in the overflow menu.
+    void updateOverflowString();
+
     DECLARE_VIRTUAL_TRACE();
 
 protected:
@@ -103,11 +121,29 @@
 
     void setDisplayType(MediaControlElementType);
 
+    // Represents the overflow menu element for this media control.
+    // The Element contains the button that the user can click on, but having
+    // the button within an Element enables us to style the overflow menu.
+    // Setting this pointer is optional so it may be null.
+    Member<Element> m_overflowMenuElement;
+
+    // The text representation of the button within the overflow menu.
+    Member<Text> m_overflowMenuText;
+
 private:
     // Hide or show based on our fits / wanted state.  We want to show
     // if and only if we're wanted and we fit.
     void updateShownState();
 
+    // Returns a string representation of the media control element.
+    // Subclasses should override this method to return the string representation
+    // of the overflow button.
+    virtual WebLocalizedString::Name getOverflowStringName()
+    {
+        NOTREACHED();
+        return WebLocalizedString::AXAMPMFieldText;
+    }
+
     Member<MediaControls> m_mediaControls;
     MediaControlElementType m_displayType;
     Member<HTMLElement> m_element;
@@ -136,6 +172,9 @@
 public:
     DECLARE_VIRTUAL_TRACE();
 
+    // Creates an overflow menu element with the given button as a child.
+    HTMLElement* createOverflowElement(MediaControls&, MediaControlInputElement*);
+
 protected:
     MediaControlInputElement(MediaControls&, MediaControlElementType);
 
@@ -143,6 +182,9 @@
     virtual void updateDisplayType() { }
     bool isMediaControlElement() const final { return true; }
     bool isMouseFocusable() const override;
+
+    // Creates an overflow menu HTML element.
+    virtual MediaControlInputElement* createOverflowButton(MediaControls&) { return nullptr; }
 };
 
 // ----------------------------
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControlElements.cpp b/third_party/WebKit/Source/core/html/shadow/MediaControlElements.cpp
index bf8c045..7b3cb56 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControlElements.cpp
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControlElements.cpp
@@ -300,6 +300,14 @@
 void MediaControlMuteButtonElement::updateDisplayType()
 {
     setDisplayType(mediaElement().muted() ? MediaUnMuteButton : MediaMuteButton);
+    updateOverflowString();
+}
+
+WebLocalizedString::Name MediaControlMuteButtonElement::getOverflowStringName()
+{
+    if (mediaElement().muted())
+        return WebLocalizedString::OverflowMenuUnmute;
+    return WebLocalizedString::OverflowMenuMute;
 }
 
 // ----------------------------
@@ -342,6 +350,14 @@
 void MediaControlPlayButtonElement::updateDisplayType()
 {
     setDisplayType(mediaElement().paused() ? MediaPlayButton : MediaPauseButton);
+    updateOverflowString();
+}
+
+WebLocalizedString::Name MediaControlPlayButtonElement::getOverflowStringName()
+{
+    if (mediaElement().paused())
+        return WebLocalizedString::OverflowMenuPlay;
+    return WebLocalizedString::OverflowMenuPause;
 }
 
 // ----------------------------
@@ -407,6 +423,10 @@
 void MediaControlToggleClosedCaptionsButtonElement::defaultEventHandler(Event* event)
 {
     if (event->type() == EventTypeNames::click) {
+        // If the user opens up the closed captions menu from the overfow menu,
+        // the overflow menu should no longer be visible.
+        if (mediaControls().overflowMenuVisible())
+            mediaControls().toggleOverflowMenu();
         mediaControls().toggleTextTrackList();
         updateDisplayType();
         event->setDefaultHandled();
@@ -415,6 +435,11 @@
     HTMLInputElement::defaultEventHandler(event);
 }
 
+WebLocalizedString::Name MediaControlToggleClosedCaptionsButtonElement::getOverflowStringName()
+{
+    return WebLocalizedString::OverflowMenuCaptions;
+}
+
 // ----------------------------
 
 MediaControlTextTrackListElement::MediaControlTextTrackListElement(MediaControls& mediaControls)
@@ -554,6 +579,54 @@
 }
 
 // ----------------------------
+MediaControlOverflowMenuButtonElement::MediaControlOverflowMenuButtonElement(MediaControls& mediaControls)
+    : MediaControlInputElement(mediaControls, MediaOverflowButton)
+{
+}
+
+MediaControlOverflowMenuButtonElement* MediaControlOverflowMenuButtonElement::create(MediaControls& mediaControls)
+{
+    MediaControlOverflowMenuButtonElement* button = new MediaControlOverflowMenuButtonElement(mediaControls);
+    button->ensureUserAgentShadowRoot();
+    button->setType(InputTypeNames::button);
+    button->setShadowPseudoId(AtomicString("-internal-overflow-menu-button"));
+    button->setIsWanted(false);
+    return button;
+}
+
+void MediaControlOverflowMenuButtonElement::defaultEventHandler(Event* event)
+{
+    if (event->type() == EventTypeNames::click) {
+        mediaControls().toggleOverflowMenu();
+        event->setDefaultHandled();
+    }
+
+    HTMLInputElement::defaultEventHandler(event);
+}
+
+// ----------------------------
+MediaControlOverflowMenuListElement::MediaControlOverflowMenuListElement(MediaControls& mediaControls)
+    : MediaControlDivElement(mediaControls, MediaOverflowList)
+{
+}
+
+MediaControlOverflowMenuListElement* MediaControlOverflowMenuListElement::create(MediaControls& mediaControls)
+{
+    MediaControlOverflowMenuListElement* element = new MediaControlOverflowMenuListElement(mediaControls);
+    element->setIsWanted(false);
+    element->setShadowPseudoId(AtomicString("-internal-media-controls-overflow-menu-list"));
+    return element;
+}
+
+void MediaControlOverflowMenuListElement::defaultEventHandler(Event* event)
+{
+    if (event->type() == EventTypeNames::click)
+        event->setDefaultHandled();
+
+    MediaControlDivElement::defaultEventHandler(event);
+}
+
+// ----------------------------
 
 MediaControlTimelineElement::MediaControlTimelineElement(MediaControls& mediaControls)
     : MediaControlInputElement(mediaControls, MediaSlider)
@@ -738,6 +811,13 @@
     setDisplayType(isFullscreen ? MediaExitFullscreenButton : MediaEnterFullscreenButton);
 }
 
+WebLocalizedString::Name MediaControlFullscreenButtonElement::getOverflowStringName()
+{
+    if (mediaElement().isFullscreen())
+        return WebLocalizedString::OverflowMenuExitFullscreen;
+    return WebLocalizedString::OverflowMenuEnterFullscreen;
+}
+
 // ----------------------------
 
 MediaControlCastButtonElement::MediaControlCastButtonElement(MediaControls& mediaControls, bool isOverlayButton)
@@ -799,6 +879,14 @@
             setDisplayType(MediaCastOffButton);
         }
     }
+    updateOverflowString();
+}
+
+WebLocalizedString::Name MediaControlCastButtonElement::getOverflowStringName()
+{
+    if (mediaElement().isPlayingRemotely())
+        return WebLocalizedString::OverflowMenuStopCast;
+    return WebLocalizedString::OverflowMenuCast;
 }
 
 void MediaControlCastButtonElement::tryShowOverlay()
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControlElements.h b/third_party/WebKit/Source/core/html/shadow/MediaControlElements.h
index c9aa9952..f0652ad7 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControlElements.h
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControlElements.h
@@ -31,6 +31,7 @@
 #define MediaControlElements_h
 
 #include "core/html/shadow/MediaControlElementTypes.h"
+#include "public/platform/WebLocalizedString.h"
 
 namespace blink {
 
@@ -94,6 +95,10 @@
     bool willRespondToMouseClickEvents() override { return true; }
     void updateDisplayType() override;
 
+    WebLocalizedString::Name getOverflowStringName() override;
+
+    bool hasOverflowButton() override { return true; }
+
 private:
     explicit MediaControlMuteButtonElement(MediaControls&);
 
@@ -109,6 +114,10 @@
     bool willRespondToMouseClickEvents() override { return true; }
     void updateDisplayType() override;
 
+    WebLocalizedString::Name getOverflowStringName() override;
+
+    bool hasOverflowButton() override { return true; }
+
 private:
     explicit MediaControlPlayButtonElement(MediaControls&);
 
@@ -140,6 +149,10 @@
 
     void updateDisplayType() override;
 
+    WebLocalizedString::Name getOverflowStringName() override;
+
+    bool hasOverflowButton() override { return true; }
+
 private:
     explicit MediaControlToggleClosedCaptionsButtonElement(MediaControls&);
 
@@ -173,6 +186,35 @@
 };
 
 // ----------------------------
+// Represents the overflow menu which is displayed when the width of the media
+// player is small enough that at least two buttons are no longer visible.
+class MediaControlOverflowMenuButtonElement final : public MediaControlInputElement {
+public:
+    static MediaControlOverflowMenuButtonElement* create(MediaControls&);
+
+    // The overflow button should respond to mouse clicks since we want a click
+    // to open up the menu.
+    bool willRespondToMouseClickEvents() override { return true; }
+
+private:
+    explicit MediaControlOverflowMenuButtonElement(MediaControls&);
+
+    void defaultEventHandler(Event*) override;
+};
+
+// ----------------------------
+// Holds a list of elements within the overflow menu.
+class MediaControlOverflowMenuListElement final : public MediaControlDivElement {
+public:
+    static MediaControlOverflowMenuListElement* create(MediaControls&);
+
+private:
+    explicit MediaControlOverflowMenuListElement(MediaControls&);
+
+    void defaultEventHandler(Event*) override;
+};
+
+// ----------------------------
 
 class MediaControlTimelineElement final : public MediaControlInputElement {
 public:
@@ -202,6 +244,10 @@
 
     void setIsFullscreen(bool);
 
+    WebLocalizedString::Name getOverflowStringName() override;
+
+    bool hasOverflowButton() override { return true; }
+
 private:
     explicit MediaControlFullscreenButtonElement(MediaControls&);
 
@@ -218,6 +264,10 @@
 
     void setIsPlayingRemotely(bool);
 
+    WebLocalizedString::Name getOverflowStringName() override;
+
+    bool hasOverflowButton() override { return true; }
+
     // This will show a cast button if it is not covered by another element.
     // This MUST be called for cast button elements that are overlay elements.
     void tryShowOverlay();
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControls.cpp b/third_party/WebKit/Source/core/html/shadow/MediaControls.cpp
index f4a1b24..93b086d 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControls.cpp
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControls.cpp
@@ -117,6 +117,7 @@
     , m_volumeSlider(nullptr)
     , m_toggleClosedCaptionsButton(nullptr)
     , m_textTrackList(nullptr)
+    , m_overflowList(nullptr)
     , m_castButton(nullptr)
     , m_fullscreenButton(nullptr)
     , m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired)
@@ -242,6 +243,21 @@
     MediaControlTextTrackListElement* textTrackList = MediaControlTextTrackListElement::create(*this);
     m_textTrackList = textTrackList;
     appendChild(textTrackList);
+
+    MediaControlOverflowMenuButtonElement* overflowMenu = MediaControlOverflowMenuButtonElement::create(*this);
+    m_overflowMenu = overflowMenu;
+    panel->appendChild(overflowMenu);
+
+    MediaControlOverflowMenuListElement* overflowList = MediaControlOverflowMenuListElement::create(*this);
+    m_overflowList = overflowList;
+    appendChild(overflowList);
+
+    // The order in which we append elements to the overflow list does matter.
+    m_overflowList->appendChild(m_muteButton->createOverflowElement(*this, MediaControlMuteButtonElement::create(*this)));
+    m_overflowList->appendChild(m_castButton->createOverflowElement(*this, MediaControlCastButtonElement::create(*this, false)));
+    m_overflowList->appendChild(m_toggleClosedCaptionsButton->createOverflowElement(*this, MediaControlToggleClosedCaptionsButtonElement::create(*this)));
+    m_overflowList->appendChild(m_fullscreenButton->createOverflowElement(*this, MediaControlFullscreenButtonElement::create(*this)));
+    m_overflowList->appendChild(m_playButton->createOverflowElement(*this, MediaControlPlayButtonElement::create(*this)));
 }
 
 void MediaControls::reset()
@@ -688,7 +704,8 @@
 
     // Controls that we'll hide / show, in order of decreasing priority.
     MediaControlElement* elements[] = {
-        // Exclude m_playButton; we handle it specially.
+        // Exclude m_overflowMenu; we handle it specially.
+        m_playButton.get(),
         m_fullscreenButton.get(),
         m_toggleClosedCaptionsButton.get(),
         m_timeline.get(),
@@ -702,20 +719,18 @@
     int usedWidth = 0;
 
     // Assume that all controls require 48px, unless we can get the computed
-    // style for the play button.  Since the play button is always shown, it
-    // should be available the first time we're called after layout.  This will
+    // style for the play button.  Since the play button or overflow is always
+    // shown, one of the two buttons should be available the first time we're
+    // called after layout.  This will
     // also be the first time we have m_panelWidth!=0, so it won't matter if
     // we get this wrong before that.
     int minimumWidth = 48;
     if (m_playButton->layoutObject() && m_playButton->layoutObject()->style()) {
         const ComputedStyle* style = m_playButton->layoutObject()->style();
         minimumWidth = ceil(style->width().pixels() / style->effectiveZoom());
-    }
-
-    // Special-case the play button; it always fits.
-    if (m_playButton->isWanted()) {
-        m_playButton->setDoesFit(true);
-        usedWidth += minimumWidth;
+    } else if (m_overflowMenu->layoutObject() && m_overflowMenu->layoutObject()->style()) {
+        const ComputedStyle* style = m_overflowMenu->layoutObject()->style();
+        minimumWidth = ceil(style->width().pixels() / style->effectiveZoom());
     }
 
     if (!m_panelWidth) {
@@ -730,12 +745,21 @@
         return;
     }
 
+    // Insert an overflow menu. However, if we see that the overflow menu
+    // doesn't end up containing at least two elements, we will not display it
+    // but instead make place for the first element that was dropped.
+    m_overflowMenu->setDoesFit(true);
+    m_overflowMenu->setIsWanted(true);
+    usedWidth = minimumWidth;
+
+    std::list<MediaControlElement*> overflowElements;
+    MediaControlElement* firstDisplacedElement = nullptr;
     // For each control that fits, enable it in order of decreasing priority.
     bool droppedCastButton = false;
     for (MediaControlElement* element : elements) {
         if (!element)
             continue;
-
+        element->shouldShowButtonInOverflowMenu(false);
         if (element->isWanted()) {
             if (usedWidth + minimumWidth <= m_panelWidth) {
                 element->setDoesFit(true);
@@ -744,10 +768,30 @@
                 element->setDoesFit(false);
                 if (element == m_castButton.get())
                     droppedCastButton = true;
+                element->shouldShowButtonInOverflowMenu(true);
+                if (element->hasOverflowButton())
+                    overflowElements.push_front(element);
+                // We want a way to access the first media element that was
+                // removed. If we don't end up needing an overflow menu, we can
+                // use the space the overflow menu would have taken up to
+                // instead display that media element.
+                if (!element->hasOverflowButton() && !firstDisplacedElement)
+                    firstDisplacedElement = element;
             }
         }
     }
 
+    // If we don't have at least two overflow elements, we will not show the
+    // overflow menu.
+    if (overflowElements.empty()) {
+        m_overflowMenu->setIsWanted(false);
+        if (firstDisplacedElement)
+            firstDisplacedElement->setDoesFit(true);
+    } else if (overflowElements.size() == 1) {
+        m_overflowMenu->setIsWanted(false);
+        overflowElements.front()->setDoesFit(true);
+    }
+
     // Special case for cast: if we want a cast button but dropped it, then
     // show the overlay cast button instead.
     if (m_castButton->isWanted()) {
@@ -784,6 +828,16 @@
     invalidate(m_volumeSlider);
 }
 
+bool MediaControls::overflowMenuVisible()
+{
+    return m_overflowList->isWanted();
+}
+
+void MediaControls::toggleOverflowMenu()
+{
+    m_overflowList->setIsWanted(!m_overflowList->isWanted());
+}
+
 DEFINE_TRACE(MediaControls)
 {
     visitor->trace(m_mediaElement);
@@ -800,6 +854,8 @@
     visitor->trace(m_durationDisplay);
     visitor->trace(m_enclosure);
     visitor->trace(m_textTrackList);
+    visitor->trace(m_overflowMenu);
+    visitor->trace(m_overflowList);
     visitor->trace(m_castButton);
     visitor->trace(m_overlayCastButton);
     HTMLDivElement::trace(visitor);
diff --git a/third_party/WebKit/Source/core/html/shadow/MediaControls.h b/third_party/WebKit/Source/core/html/shadow/MediaControls.h
index 0a7c6944..6c4d508 100644
--- a/third_party/WebKit/Source/core/html/shadow/MediaControls.h
+++ b/third_party/WebKit/Source/core/html/shadow/MediaControls.h
@@ -86,6 +86,10 @@
     // Notify us that the media element's network state has changed.
     void networkStateChanged();
 
+    void toggleOverflowMenu();
+
+    bool overflowMenuVisible();
+
     DECLARE_VIRTUAL_TRACE();
 
 private:
@@ -143,6 +147,9 @@
     Member<MediaControlVolumeSliderElement> m_volumeSlider;
     Member<MediaControlToggleClosedCaptionsButtonElement> m_toggleClosedCaptionsButton;
     Member<MediaControlTextTrackListElement> m_textTrackList;
+    Member<MediaControlOverflowMenuButtonElement> m_overflowMenu;
+    Member<MediaControlOverflowMenuListElement> m_overflowList;
+
     Member<MediaControlCastButtonElement> m_castButton;
     Member<MediaControlFullscreenButtonElement> m_fullscreenButton;
 
diff --git a/third_party/WebKit/Source/core/paint/MediaControlsPainter.cpp b/third_party/WebKit/Source/core/paint/MediaControlsPainter.cpp
index 53a1c8f7..93047ac 100644
--- a/third_party/WebKit/Source/core/paint/MediaControlsPainter.cpp
+++ b/third_party/WebKit/Source/core/paint/MediaControlsPainter.cpp
@@ -604,6 +604,16 @@
     return paintMediaButton(paintInfo.context, rect, mediaSubtitlesIcon);
 }
 
+bool MediaControlsPainter::paintMediaOverflowMenu(const LayoutObject& object, const PaintInfo& paintInfo, const IntRect& rect)
+{
+    const HTMLMediaElement* mediaElement = toParentMediaElement(object);
+    if (!mediaElement)
+        return false;
+
+    static Image* mediaOverflowButton = platformResource("mediaplayerOverflowMenu");
+    return paintMediaButton(paintInfo.context, rect, mediaOverflowButton);
+}
+
 void MediaControlsPainter::adjustMediaSliderThumbSize(ComputedStyle& style)
 {
     static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb",
diff --git a/third_party/WebKit/Source/core/paint/MediaControlsPainter.h b/third_party/WebKit/Source/core/paint/MediaControlsPainter.h
index cd2589b1..eac87da8 100644
--- a/third_party/WebKit/Source/core/paint/MediaControlsPainter.h
+++ b/third_party/WebKit/Source/core/paint/MediaControlsPainter.h
@@ -54,6 +54,7 @@
     static bool paintMediaTrackSelectionCheckmark(const LayoutObject&, const PaintInfo&, const IntRect&);
     static bool paintMediaClosedCaptionsIcon(const LayoutObject&, const PaintInfo&, const IntRect&);
     static bool paintMediaSubtitlesIcon(const LayoutObject&, const PaintInfo&, const IntRect&);
+    static bool paintMediaOverflowMenu(const LayoutObject&, const PaintInfo&, const IntRect&);
     static void adjustMediaSliderThumbSize(ComputedStyle&);
 
 private:
diff --git a/third_party/WebKit/Source/core/paint/ThemePainter.cpp b/third_party/WebKit/Source/core/paint/ThemePainter.cpp
index a0c3082..91b32b79 100644
--- a/third_party/WebKit/Source/core/paint/ThemePainter.cpp
+++ b/third_party/WebKit/Source/core/paint/ThemePainter.cpp
@@ -141,6 +141,8 @@
         return MediaControlsPainter::paintMediaClosedCaptionsIcon(o, paintInfo, r);
     case MediaSubtitlesIconPart:
         return MediaControlsPainter::paintMediaSubtitlesIcon(o, paintInfo, r);
+    case MediaOverflowMenuButtonPart:
+        return MediaControlsPainter::paintMediaOverflowMenu(o, paintInfo, r);
     case MenulistButtonPart:
     case TextFieldPart:
     case TextAreaPart:
diff --git a/third_party/WebKit/Source/platform/ThemeTypes.h b/third_party/WebKit/Source/platform/ThemeTypes.h
index e0c9448..bf5c0ed 100644
--- a/third_party/WebKit/Source/platform/ThemeTypes.h
+++ b/third_party/WebKit/Source/platform/ThemeTypes.h
@@ -51,7 +51,7 @@
     MediaOverlayPlayButtonPart, MediaToggleClosedCaptionsButtonPart,
     MediaSliderPart, MediaSliderThumbPart, MediaVolumeSliderContainerPart, MediaVolumeSliderPart, MediaVolumeSliderThumbPart,
     MediaControlsBackgroundPart, MediaControlsFullscreenBackgroundPart, MediaCurrentTimePart, MediaTimeRemainingPart, MediaCastOffButtonPart,
-    MediaOverlayCastOffButtonPart, MediaTrackSelectionCheckmarkPart, MediaClosedCaptionsIconPart, MediaSubtitlesIconPart,
+    MediaOverlayCastOffButtonPart, MediaTrackSelectionCheckmarkPart, MediaClosedCaptionsIconPart, MediaSubtitlesIconPart, MediaOverflowMenuButtonPart,
     MenulistPart, MenulistButtonPart, MenulistTextPart, MenulistTextFieldPart, MeterPart, ProgressBarPart, ProgressBarValuePart,
     SliderHorizontalPart, SliderVerticalPart, SliderThumbHorizontalPart,
     SliderThumbVerticalPart, CaretPart, SearchFieldPart,
diff --git a/third_party/WebKit/public/blink_image_resources.grd b/third_party/WebKit/public/blink_image_resources.grd
index 17738590a..0f6a4a6 100644
--- a/third_party/WebKit/public/blink_image_resources.grd
+++ b/third_party/WebKit/public/blink_image_resources.grd
@@ -48,6 +48,7 @@
       <structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_TRACKSELECTION_CHECKMARK_NEW" file="blink/mediaplayer_trackselection_checkmark_new.png" />
       <structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_CLOSEDCAPTIONS_ICON" file="blink/mediaplayer_closedcaptions_icon.png" />
       <structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_CLOSEDCAPTIONS_ICON_NEW" file="blink/mediaplayer_closedcaptions_icon_new.png" />
+      <structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_OVERFLOW_MENU_ICON" file="blink/mediaplayer_overflow_menu.png" />
       <structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_SUBTITLES_ICON" file="blink/mediaplayer_subtitles_icon.png" />
       <structure type="chrome_scaled_image" name="IDR_MEDIAPLAYER_SUBTITLES_ICON_NEW" file="blink/mediaplayer_subtitles_icon_new.png" />
       <structure type="chrome_scaled_image" name="IDR_SEARCH_CANCEL" file="blink/search_cancel.png" />
diff --git a/third_party/WebKit/public/default_100_percent/blink/mediaplayer_overflow_menu.png b/third_party/WebKit/public/default_100_percent/blink/mediaplayer_overflow_menu.png
new file mode 100644
index 0000000..7a7eafd
--- /dev/null
+++ b/third_party/WebKit/public/default_100_percent/blink/mediaplayer_overflow_menu.png
Binary files differ
diff --git a/third_party/WebKit/public/platform/WebLocalizedString.h b/third_party/WebKit/public/platform/WebLocalizedString.h
index 5715f59..5087f5b 100644
--- a/third_party/WebKit/public/platform/WebLocalizedString.h
+++ b/third_party/WebKit/public/platform/WebLocalizedString.h
@@ -68,6 +68,7 @@
         AXMediaHideClosedCaptionsButtonHelp,
         AXMediaMuteButton,
         AXMediaMuteButtonHelp,
+        AxMediaOverflowButton,
         AXMediaPauseButton,
         AXMediaPauseButtonHelp,
         AXMediaPlayButton,
@@ -118,6 +119,15 @@
         OtherMonthLabel,
         OtherTimeLabel,
         OtherWeekLabel,
+        OverflowMenuCaptions,
+        OverflowMenuCast,
+        OverflowMenuEnterFullscreen,
+        OverflowMenuExitFullscreen,
+        OverflowMenuStopCast,
+        OverflowMenuMute,
+        OverflowMenuUnmute,
+        OverflowMenuPlay,
+        OverflowMenuPause,
         // PlaceholderForDayOfMonthField is for day placeholder text, e.g.
         // "dd", for date field used in multiple fields "date", "datetime", and
         // "datetime-local" input UI instead of "--".