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 "--".