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..4446dc1c --- /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..f0652ad7b 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..93047acb 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 "--".