Title: [209182] trunk
Revision
209182
Author
grao...@webkit.org
Date
2016-12-01 08:16:38 -0800 (Thu, 01 Dec 2016)

Log Message

[Modern Media Controls] Provide a UI object to show a list of tracks
https://bugs.webkit.org/show_bug.cgi?id=165239

Reviewed by Dean Jackson.

We add a new TracksPanel object which we will be using to display a list of
audio and text tracks. The tracks panel can be shown by calling showTracksPanel()
on a MacOSMediaControls object and will be dismissed by hitting the Escape key
or mousing down outside of the panel's bounds. While the tracks panel is up,
arrows can be used to focus individual tracks which can be activated by either
pressing the Space bar or Enter key.

Activating a track will briefly animate its background to indicate selection and
dismissing the tracks panel is also animated with a quick fade-out animation.

Data for the tracks panel is provided by specifying a dataSource property and
implementing the required methods to provide the number of sections in the panel,
the number of tracks in each section, etc.

Tests: media/modern-media-controls/tracks-panel/tracks-panel-hide-click-outside.html
       media/modern-media-controls/tracks-panel/tracks-panel-hide-esc-key.html
       media/modern-media-controls/tracks-panel/tracks-panel-hide.html
       media/modern-media-controls/tracks-panel/tracks-panel-population.html
       media/modern-media-controls/tracks-panel/tracks-panel-right-x.html
       media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-keyboard.html
       media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-mouse.html
       media/modern-media-controls/tracks-panel/tracks-panel.html

* Modules/modern-media-controls/controls/macos-fullscreen-media-controls.css:
(.media-controls.mac.fullscreen):
(.media-controls.mac.fullscreen > .controls-bar):
(.media-controls.mac.fullscreen .tracks-panel):
* Modules/modern-media-controls/controls/macos-inline-media-controls.css:
(.media-controls.mac.inline .tracks-panel):
* Modules/modern-media-controls/controls/macos-media-controls.js:
(MacOSMediaControls.prototype.showTracksPanel):
(MacOSMediaControls.prototype.hideTracksPanel):
(MacOSMediaControls):
* Modules/modern-media-controls/controls/media-controls.css:
(.media-controls):
* Modules/modern-media-controls/controls/placard.css:
(.placard):
* Modules/modern-media-controls/controls/status-label.css:
(.status-label):
* Modules/modern-media-controls/controls/tracks-panel.css: Added.
(.tracks-panel):
(.tracks-panel *):
(.tracks-panel.fade-out):
(.tracks-panel-section):
(.tracks-panel-section:first-of-type):
(.tracks-panel-section > h3):
(.tracks-panel-section > ul):
(.tracks-panel-section > ul > li):
(.tracks-panel-section > ul > li:focus):
(.tracks-panel-section > ul > li.selected:before):
(.tracks-panel-section > ul > li.animated):
(@keyframes tracks-panel-item-selection):
(22.22%):
* Modules/modern-media-controls/controls/tracks-panel.js: Added.
(TracksPanel.prototype.get presented):
(TracksPanel.prototype.presentInParent):
(TracksPanel.prototype.hide):
(TracksPanel.prototype.get rightX):
(TracksPanel.prototype.set rightX):
(TracksPanel.prototype.trackNodeSelectionAnimationDidEnd):
(TracksPanel.prototype.mouseMovedOverTrackNode):
(TracksPanel.prototype.mouseExitedTrackNode):
(TracksPanel.prototype.commitProperty):
(TracksPanel.prototype.handleEvent):
(TracksPanel.prototype._childrenFromDataSource.):
(TracksPanel.prototype._childrenFromDataSource):
(TracksPanel.prototype._handleMousedown):
(TracksPanel.prototype._handleKeydown):
(TracksPanel.prototype._dismiss):
(TracksPanel.prototype._focusTrackNode):
(TracksPanel.prototype._focusPreviousTrackNode):
(TracksPanel.prototype._focusNextTrackNode):
(TracksPanel.prototype._focusFirstTrackNode):
(TracksPanel.prototype._focusLastTrackNode):
(TrackNode):
(TrackNode.prototype.activate):
(TrackNode.prototype.handleEvent):
(TrackNode.prototype._animationDidEnd):
* Modules/modern-media-controls/js-files:
* WebCore.xcodeproj/project.pbxproj:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (209181 => 209182)


--- trunk/LayoutTests/ChangeLog	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/LayoutTests/ChangeLog	2016-12-01 16:16:38 UTC (rev 209182)
@@ -1,3 +1,32 @@
+2016-12-01  Antoine Quint  <grao...@apple.com>
+
+        [Modern Media Controls] Provide a UI object to show a list of tracks
+        https://bugs.webkit.org/show_bug.cgi?id=165239
+
+        Reviewed by Dean Jackson.
+
+        Adding new tests to cover new TracksPanel functionality.
+
+        * media/modern-media-controls/resources/media-controls-loader.js:
+        * media/modern-media-controls/tracks-panel/tracks-panel-expected.txt: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-hide-click-outside-expected.txt: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-hide-click-outside.html: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-hide-esc-key-expected.txt: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-hide-esc-key.html: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-hide-expected.txt: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-hide.html: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-population-expected.txt: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-population.html: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-right-x-expected.txt: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-right-x.html: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-keyboard-expected.txt: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-keyboard.html: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-mouse-expected.txt: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-mouse.html: Added.
+        * media/modern-media-controls/tracks-panel/tracks-panel.html: Added.
+        * platform/ios-simulator/TestExpectations:
+        * platform/mac/TestExpectations:
+
 2016-11-30  Yusuke Suzuki  <utatane....@gmail.com>
 
         [JSC] Specifying same module entry point multiple times cause TypeError

Modified: trunk/LayoutTests/media/modern-media-controls/resources/media-controls-loader.js (209181 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/resources/media-controls-loader.js	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/LayoutTests/media/modern-media-controls/resources/media-controls-loader.js	2016-12-01 16:16:38 UTC (rev 209182)
@@ -3,7 +3,7 @@
     const layoutTestsPath = window.location.href.substr(0, window.location.href.indexOf("/LayoutTests/"));
     const modulePath = layoutTestsPath ? layoutTestsPath + "/Source/WebCore/Modules/modern-media-controls" : "/modern-media-controls";
 
-    ["media-controls", "scrubber", "volume-slider", "slider", "button", "start-button", "icon-button", "airplay-button", "time-label", "status-label", "macos-inline-media-controls", "macos-fullscreen-media-controls", "ios-inline-media-controls", "buttons-container", "placard"].forEach(cssFile => {
+    ["media-controls", "scrubber", "volume-slider", "slider", "button", "start-button", "icon-button", "airplay-button", "time-label", "status-label", "macos-inline-media-controls", "macos-fullscreen-media-controls", "ios-inline-media-controls", "buttons-container", "placard", "tracks-panel"].forEach(cssFile => {
         document.write(`<link rel="stylesheet" type="text/css" href=""
     });
 

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-expected.txt (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-expected.txt	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,15 @@
+Testing the TracksPanel class properties.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Basic properties
+PASS tracksPanel.element.localName is "div"
+PASS tracksPanel.element.className is "tracks-panel"
+PASS tracksPanel.presented is false
+PASS tracksPanel.rightX is 0
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-click-outside-expected.txt (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-click-outside-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-click-outside-expected.txt	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,16 @@
+Testing a TracksPanel is hidden upon clicking outside of it.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+mediaControls.showTracksPanel()
+
+Clicking outside of the panel
+
+Transition ended
+PASS mediaControls.tracksPanel.presented is false
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-click-outside.html (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-click-outside.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-click-outside.html	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,39 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<body>
+<script type="text/_javascript_">
+
+window.jsTestIsAsync = true;
+
+description("Testing a <code>TracksPanel</code> is hidden upon clicking outside of it.");
+
+const mediaControls = new MacOSInlineMediaControls({ width: 680, height: 300 });
+document.body.appendChild(mediaControls.element);
+
+debug("mediaControls.showTracksPanel()");
+mediaControls.showTracksPanel();
+
+scheduler.frameDidFire = function()
+{
+    window.requestAnimationFrame(() => {
+        debug("");
+        debug("Clicking outside of the panel");
+        eventSender.mouseMoveTo(10, 10);
+        eventSender.mouseDown();
+        eventSender.mouseUp();
+        mediaControls.tracksPanel.element.addEventListener("transitionend", (event) => {
+            debug("");
+            debug("Transition ended");
+            shouldBeFalse("mediaControls.tracksPanel.presented");
+
+            debug("");
+            mediaControls.element.remove();
+            finishJSTest();
+        });
+    });
+    scheduler.frameDidFire = null;
+}
+
+</script>
+<script src=""
+</body>

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-esc-key-expected.txt (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-esc-key-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-esc-key-expected.txt	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,16 @@
+Testing a TracksPanel is hidden upon hitting the Esc. key.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+mediaControls.showTracksPanel()
+
+Pressing the Escape key
+
+Transition ended
+PASS mediaControls.tracksPanel.presented is false
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-esc-key.html (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-esc-key.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-esc-key.html	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,37 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<body>
+<script type="text/_javascript_">
+
+window.jsTestIsAsync = true;
+
+description("Testing a <code>TracksPanel</code> is hidden upon hitting the Esc. key.");
+
+const mediaControls = new MacOSInlineMediaControls({ width: 680, height: 300 });
+document.body.appendChild(mediaControls.element);
+
+debug("mediaControls.showTracksPanel()");
+mediaControls.showTracksPanel();
+
+scheduler.frameDidFire = function()
+{
+    window.requestAnimationFrame(() => {
+        debug("");
+        debug("Pressing the Escape key");
+        eventSender.keyDown("Escape");
+        mediaControls.tracksPanel.element.addEventListener("transitionend", (event) => {
+            debug("");
+            debug("Transition ended");
+            shouldBeFalse("mediaControls.tracksPanel.presented");
+
+            debug("");
+            mediaControls.element.remove();
+            finishJSTest();
+        });
+    });
+    scheduler.frameDidFire = null;
+}
+
+</script>
+<script src=""
+</body>

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-expected.txt (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide-expected.txt	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,18 @@
+Testing a TracksPanel is hidden with an animation.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+tracksPanel.presentInParent(container)
+PASS tracksPanel.presented is true
+
+tracksPanel.hide()
+PASS tracksPanel.presented is true
+
+Transition ended
+PASS tracksPanel.presented is false
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide.html (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-hide.html	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,40 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<body>
+<script type="text/_javascript_">
+
+window.jsTestIsAsync = true;
+
+description("Testing a <code>TracksPanel</code> is hidden with an animation.");
+
+const tracksPanel = new TracksPanel;
+const container = new LayoutNode;
+
+document.body.appendChild(container.element);
+
+debug("tracksPanel.presentInParent(container)");
+tracksPanel.presentInParent(container);
+
+scheduler.frameDidFire = function()
+{
+    shouldBeTrue("tracksPanel.presented");
+
+    debug("");
+    debug("tracksPanel.hide()");
+    window.requestAnimationFrame(() => {
+        tracksPanel.hide();
+        shouldBeTrue("tracksPanel.presented");
+        tracksPanel.element.addEventListener("transitionend", (event) => {
+            debug("");
+            debug("Transition ended");
+            shouldBeFalse("tracksPanel.presented");
+            debug("");
+            finishJSTest();
+        });
+    });
+    scheduler.frameDidFire = null;
+}
+
+</script>
+<script src=""
+</body>

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-population-expected.txt (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-population-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-population-expected.txt	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,55 @@
+Populating a TracksPanel instance.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS tracksPanel.presented is true
+PASS tracksPanel.parent is container
+PASS tracksPanel.children.length is numberOfSections
+
+Hierarchy for section #0
+PASS tracksPanel.children[0].element.localName is "div"
+PASS tracksPanel.children[0].element.className is "tracks-panel-section"
+PASS tracksPanel.children[0].children.length is 2
+PASS tracksPanel.children[0].children[0].element.localName is "h3"
+PASS tracksPanel.children[0].children[0].element.textContent is "Title #0"
+PASS tracksPanel.children[0].children[1].element.localName is "ul"
+PASS tracksPanel.children[0].children[1].children.length is 3
+PASS tracksPanel.children[0].children[1].children[trackIndex].element.localName is "li"
+PASS tracksPanel.children[0].children[1].children[trackIndex].element.className is ""
+PASS tracksPanel.children[0].children[1].children[trackIndex].element.textContent is "Track 0x0"
+PASS tracksPanel.children[0].children[1].children[trackIndex].element.localName is "li"
+PASS tracksPanel.children[0].children[1].children[trackIndex].element.className is "selected"
+PASS tracksPanel.children[0].children[1].children[trackIndex].element.textContent is "Track 0x1"
+PASS tracksPanel.children[0].children[1].children[trackIndex].element.localName is "li"
+PASS tracksPanel.children[0].children[1].children[trackIndex].element.className is ""
+PASS tracksPanel.children[0].children[1].children[trackIndex].element.textContent is "Track 0x2"
+
+Hierarchy for section #1
+PASS tracksPanel.children[1].element.localName is "div"
+PASS tracksPanel.children[1].element.className is "tracks-panel-section"
+PASS tracksPanel.children[1].children.length is 2
+PASS tracksPanel.children[1].children[0].element.localName is "h3"
+PASS tracksPanel.children[1].children[0].element.textContent is "Title #1"
+PASS tracksPanel.children[1].children[1].element.localName is "ul"
+PASS tracksPanel.children[1].children[1].children.length is 5
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.localName is "li"
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.className is ""
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.textContent is "Track 1x0"
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.localName is "li"
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.className is ""
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.textContent is "Track 1x1"
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.localName is "li"
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.className is ""
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.textContent is "Track 1x2"
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.localName is "li"
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.className is "selected"
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.textContent is "Track 1x3"
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.localName is "li"
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.className is ""
+PASS tracksPanel.children[1].children[1].children[trackIndex].element.textContent is "Track 1x4"
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-population.html (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-population.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-population.html	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,70 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<body>
+<script type="text/_javascript_">
+
+description("Populating a <code>TracksPanel</code> instance.");
+
+const tracksPanel = new TracksPanel;
+
+const numberOfSections = 2;
+const numberOfTracksInSection = [3, 5];
+const selectedTracks = [[0, 1], [1, 3]];
+
+tracksPanel.dataSource = {
+    tracksPanelNumberOfSections: function()
+    {
+        return numberOfSections;
+    },
+
+    tracksPanelTitleForSection: function(sectionIndex)
+    {
+        return `Title #${sectionIndex}`;
+    },
+
+    tracksPanelNumberOfTracksInSection: function(sectionIndex)
+    {
+        return numberOfTracksInSection[sectionIndex];
+    },
+
+    tracksPanelTitleForTrackInSection: function(trackIndex, sectionIndex)
+    {
+        return `Track ${sectionIndex}x${trackIndex}`;
+    },
+
+    tracksPanelIsTrackInSectionSelected: function(trackIndex, sectionIndex)
+    {
+        return selectedTracks.some(selectedTrack => selectedTrack[0] === sectionIndex && selectedTrack[1] === trackIndex);
+    }
+};
+
+const container = new LayoutNode;
+tracksPanel.presentInParent(container);
+
+shouldBeTrue("tracksPanel.presented");
+shouldBe("tracksPanel.parent", "container");
+shouldBe("tracksPanel.children.length", "numberOfSections");
+
+let sectionIndex, trackIndex;
+for (sectionIndex = 0; sectionIndex < numberOfSections; ++sectionIndex) {
+    debug("");
+    debug(`Hierarchy for section #${sectionIndex}`);
+    shouldBeEqualToString(`tracksPanel.children[${sectionIndex}].element.localName`, "div");
+    shouldBeEqualToString(`tracksPanel.children[${sectionIndex}].element.className`, "tracks-panel-section");
+    shouldBe(`tracksPanel.children[${sectionIndex}].children.length`, "2");
+    shouldBeEqualToString(`tracksPanel.children[${sectionIndex}].children[0].element.localName`, "h3");
+    shouldBeEqualToString(`tracksPanel.children[${sectionIndex}].children[0].element.textContent`, tracksPanel.dataSource.tracksPanelTitleForSection(sectionIndex));
+    shouldBeEqualToString(`tracksPanel.children[${sectionIndex}].children[1].element.localName`, "ul");
+    shouldBe(`tracksPanel.children[${sectionIndex}].children[1].children.length`, `${numberOfTracksInSection[sectionIndex]}`);
+    for (trackIndex = 0; trackIndex < numberOfTracksInSection[sectionIndex]; ++trackIndex) {
+        shouldBeEqualToString(`tracksPanel.children[${sectionIndex}].children[1].children[trackIndex].element.localName`, "li");
+        shouldBeEqualToString(`tracksPanel.children[${sectionIndex}].children[1].children[trackIndex].element.className`, selectedTracks.some(selectedTrack => selectedTrack[0] === sectionIndex && selectedTrack[1] === trackIndex) ? "selected" : "");
+        shouldBeEqualToString(`tracksPanel.children[${sectionIndex}].children[1].children[trackIndex].element.textContent`, tracksPanel.dataSource.tracksPanelTitleForTrackInSection(trackIndex, sectionIndex));
+    }
+}
+
+debug("");
+
+</script>
+<script src=""
+</body>

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-right-x-expected.txt (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-right-x-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-right-x-expected.txt	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,12 @@
+Testing the TracksPanel class.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+tracksPanel.rightX = 10
+PASS tracksPanel.element.style.right is "10px"
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-right-x.html (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-right-x.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-right-x.html	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,25 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<body>
+<script type="text/_javascript_">
+
+window.jsTestIsAsync = true;
+
+description("Testing the <code>TracksPanel</code> class.");
+
+const tracksPanel = new TracksPanel;
+
+tracksPanel.rightX = 10;
+debug("tracksPanel.rightX = 10");
+
+scheduler.frameDidFire = function()
+{
+    shouldBeEqualToString("tracksPanel.element.style.right", "10px");
+
+    debug("");
+    finishJSTest();
+};
+
+</script>
+<script src=""
+</body>

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-keyboard-expected.txt (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-keyboard-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-keyboard-expected.txt	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,19 @@
+Selecting a track in a TracksPanel with the keyboard.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+mediaControls.showTracksPanel()
+
+Focusing the second track by pressing the down arrow key twice
+Obtained focus event
+
+Activating the focused track by pressing the Enter key
+mediaControls.tracksPanel.uiDelegate.tracksPanelSelectionDidChange() called
+sectionIndex = 0
+trackIndex = 1
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-keyboard.html (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-keyboard.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-keyboard.html	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,91 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<body>
+<style type="text/css" media="screen">
+    
+    .media-controls {
+        position: absolute;
+        top: 0;
+        left: 0;
+    }
+    
+</style>
+<script type="text/_javascript_">
+
+window.jsTestIsAsync = true;
+
+description("Selecting a track in a <code>TracksPanel</code> with the keyboard.");
+
+const mediaControls = new MacOSInlineMediaControls({ width: 680, height: 300 });
+
+mediaControls.tracksPanel.dataSource = {
+    tracksPanelNumberOfSections: function()
+    {
+        return 1;
+    },
+
+    tracksPanelTitleForSection: function(sectionIndex)
+    {
+        return `Title`;
+    },
+
+    tracksPanelNumberOfTracksInSection: function(sectionIndex)
+    {
+        return 3;
+    },
+
+    tracksPanelTitleForTrackInSection: function(trackIndex, sectionIndex)
+    {
+        return `Track`;
+    },
+
+    tracksPanelIsTrackInSectionSelected: function(trackIndex, sectionIndex)
+    {
+        return false;
+    }
+};
+
+mediaControls.tracksPanel.uiDelegate = {
+    tracksPanelSelectionDidChange: function(trackIndex, sectionIndex)
+    {
+        debug("mediaControls.tracksPanel.uiDelegate.tracksPanelSelectionDidChange() called");
+        debug(`sectionIndex = ${sectionIndex}`);
+        debug(`trackIndex = ${trackIndex}`);
+
+        debug("");
+        mediaControls.element.remove();
+        finishJSTest();
+    }
+};
+
+document.body.appendChild(mediaControls.element);
+
+let trackElement;
+
+debug("mediaControls.showTracksPanel()");
+mediaControls.showTracksPanel();
+
+scheduler.frameDidFire = function()
+{
+    window.requestAnimationFrame(() => {
+        debug("");
+        debug("Focusing the second track by pressing the down arrow key twice");
+
+        trackElement = mediaControls.tracksPanel.element.querySelectorAll("li")[1];
+        trackElement.addEventListener("focus", () => {
+            debug("Obtained focus event");
+
+            debug("");
+            debug("Activating the focused track by pressing the Enter key");
+            eventSender.keyDown("Enter");
+        });
+
+        eventSender.keyDown("ArrowDown");
+        eventSender.keyDown("ArrowDown");
+    });
+    scheduler.frameDidFire = null;
+}
+
+</script>
+<script src=""
+</body>

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-mouse-expected.txt (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-mouse-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-mouse-expected.txt	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,19 @@
+Selecting a track in a TracksPanel with the mouse.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+mediaControls.showTracksPanel()
+
+Mousing over the second track in the panel
+Obtained focus event
+
+Clicking on focused track
+mediaControls.tracksPanel.uiDelegate.tracksPanelSelectionDidChange() called
+sectionIndex = 0
+trackIndex = 1
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-mouse.html (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-mouse.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-mouse.html	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,92 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<body>
+<style type="text/css" media="screen">
+    
+    .media-controls {
+        position: absolute;
+        top: 0;
+        left: 0;
+    }
+    
+</style>
+<script type="text/_javascript_">
+
+window.jsTestIsAsync = true;
+
+description("Selecting a track in a <code>TracksPanel</code> with the mouse.");
+
+const mediaControls = new MacOSInlineMediaControls({ width: 680, height: 300 });
+
+mediaControls.tracksPanel.dataSource = {
+    tracksPanelNumberOfSections: function()
+    {
+        return 1;
+    },
+
+    tracksPanelTitleForSection: function(sectionIndex)
+    {
+        return `Title`;
+    },
+
+    tracksPanelNumberOfTracksInSection: function(sectionIndex)
+    {
+        return 3;
+    },
+
+    tracksPanelTitleForTrackInSection: function(trackIndex, sectionIndex)
+    {
+        return `Track`;
+    },
+
+    tracksPanelIsTrackInSectionSelected: function(trackIndex, sectionIndex)
+    {
+        return false;
+    }
+};
+
+mediaControls.tracksPanel.uiDelegate = {
+    tracksPanelSelectionDidChange: function(trackIndex, sectionIndex)
+    {
+        debug("mediaControls.tracksPanel.uiDelegate.tracksPanelSelectionDidChange() called");
+        debug(`sectionIndex = ${sectionIndex}`);
+        debug(`trackIndex = ${trackIndex}`);
+
+        debug("");
+        mediaControls.element.remove();
+        finishJSTest();
+    }
+};
+
+document.body.appendChild(mediaControls.element);
+
+let trackElement;
+
+debug("mediaControls.showTracksPanel()");
+mediaControls.showTracksPanel();
+
+scheduler.frameDidFire = function()
+{
+    window.requestAnimationFrame(() => {
+        debug("");
+        debug("Mousing over the second track in the panel");
+
+        trackElement = mediaControls.tracksPanel.element.querySelectorAll("li")[1];
+        trackElement.addEventListener("focus", () => {
+            debug("Obtained focus event");
+
+            debug("");
+            debug("Clicking on focused track");
+            eventSender.mouseDown();
+            eventSender.mouseUp();
+        });
+
+        const bounds = trackElement.getBoundingClientRect();
+        eventSender.mouseMoveTo(bounds.left + 1, bounds.top + 1);
+    });
+    scheduler.frameDidFire = null;
+}
+
+</script>
+<script src=""
+</body>

Added: trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel.html (0 => 209182)


--- trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/tracks-panel/tracks-panel.html	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,20 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<body>
+<script type="text/_javascript_">
+
+description("Testing the <code>TracksPanel</code> class properties.");
+
+const tracksPanel = new TracksPanel;
+
+debug("Basic properties");
+shouldBeEqualToString("tracksPanel.element.localName", "div");
+shouldBeEqualToString("tracksPanel.element.className", "tracks-panel");
+shouldBeFalse("tracksPanel.presented");
+shouldBe("tracksPanel.rightX", "0");
+
+debug("");
+
+</script>
+<script src=""
+</body>

Modified: trunk/LayoutTests/platform/ios-simulator/TestExpectations (209181 => 209182)


--- trunk/LayoutTests/platform/ios-simulator/TestExpectations	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/LayoutTests/platform/ios-simulator/TestExpectations	2016-12-01 16:16:38 UTC (rev 209182)
@@ -2751,6 +2751,9 @@
 media/modern-media-controls/media-controller/media-controller-fullscreen-change.html [ Skip ]
 media/modern-media-controls/media-controller/media-controller-fullscreen-ltr.html  [ Skip ]
 
+# These tests are all Mac-specific, we never show the tracks menu on iOS
+media/modern-media-controls/tracks-panel
+
 webkit.org/b/164594 http/tests/cache/disk-cache/disk-cache-request-headers.html [ Pass Timeout ]
 
 webkit.org/b/163046 js/regress-141098.html [ Pass Failure ]

Modified: trunk/LayoutTests/platform/mac/TestExpectations (209181 => 209182)


--- trunk/LayoutTests/platform/mac/TestExpectations	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/LayoutTests/platform/mac/TestExpectations	2016-12-01 16:16:38 UTC (rev 209182)
@@ -1452,6 +1452,7 @@
 [ Yosemite ] media/modern-media-controls/placard-support [ Skip ]
 [ Yosemite ] media/modern-media-controls/audio [ Skip ]
 [ Yosemite ] media/modern-media-controls/media-controller/media-controller-fullscreen-ltr.html [ Skip ]
+[ Yosemite ] media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-keyboard.html [ Skip ]
 
 webkit.org/b/164616 http/tests/media/modern-media-controls/skip-back-support/skip-back-support-button-click.html [ Pass Failure ]
 webkit.org/b/164323 media/modern-media-controls/airplay-support/airplay-support.html [ Pass Failure ]

Modified: trunk/Source/WebCore/ChangeLog (209181 => 209182)


--- trunk/Source/WebCore/ChangeLog	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/Source/WebCore/ChangeLog	2016-12-01 16:16:38 UTC (rev 209182)
@@ -1,3 +1,91 @@
+2016-12-01  Antoine Quint  <grao...@apple.com>
+
+        [Modern Media Controls] Provide a UI object to show a list of tracks
+        https://bugs.webkit.org/show_bug.cgi?id=165239
+
+        Reviewed by Dean Jackson.
+
+        We add a new TracksPanel object which we will be using to display a list of
+        audio and text tracks. The tracks panel can be shown by calling showTracksPanel()
+        on a MacOSMediaControls object and will be dismissed by hitting the Escape key
+        or mousing down outside of the panel's bounds. While the tracks panel is up,
+        arrows can be used to focus individual tracks which can be activated by either
+        pressing the Space bar or Enter key.
+
+        Activating a track will briefly animate its background to indicate selection and
+        dismissing the tracks panel is also animated with a quick fade-out animation.
+
+        Data for the tracks panel is provided by specifying a dataSource property and
+        implementing the required methods to provide the number of sections in the panel,
+        the number of tracks in each section, etc.
+
+        Tests: media/modern-media-controls/tracks-panel/tracks-panel-hide-click-outside.html
+               media/modern-media-controls/tracks-panel/tracks-panel-hide-esc-key.html
+               media/modern-media-controls/tracks-panel/tracks-panel-hide.html
+               media/modern-media-controls/tracks-panel/tracks-panel-population.html
+               media/modern-media-controls/tracks-panel/tracks-panel-right-x.html
+               media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-keyboard.html
+               media/modern-media-controls/tracks-panel/tracks-panel-select-track-with-mouse.html
+               media/modern-media-controls/tracks-panel/tracks-panel.html
+
+        * Modules/modern-media-controls/controls/macos-fullscreen-media-controls.css:
+        (.media-controls.mac.fullscreen):
+        (.media-controls.mac.fullscreen > .controls-bar):
+        (.media-controls.mac.fullscreen .tracks-panel):
+        * Modules/modern-media-controls/controls/macos-inline-media-controls.css:
+        (.media-controls.mac.inline .tracks-panel):
+        * Modules/modern-media-controls/controls/macos-media-controls.js:
+        (MacOSMediaControls.prototype.showTracksPanel):
+        (MacOSMediaControls.prototype.hideTracksPanel):
+        (MacOSMediaControls):
+        * Modules/modern-media-controls/controls/media-controls.css:
+        (.media-controls):
+        * Modules/modern-media-controls/controls/placard.css:
+        (.placard):
+        * Modules/modern-media-controls/controls/status-label.css:
+        (.status-label):
+        * Modules/modern-media-controls/controls/tracks-panel.css: Added.
+        (.tracks-panel):
+        (.tracks-panel *):
+        (.tracks-panel.fade-out):
+        (.tracks-panel-section):
+        (.tracks-panel-section:first-of-type):
+        (.tracks-panel-section > h3):
+        (.tracks-panel-section > ul):
+        (.tracks-panel-section > ul > li):
+        (.tracks-panel-section > ul > li:focus):
+        (.tracks-panel-section > ul > li.selected:before):
+        (.tracks-panel-section > ul > li.animated):
+        (@keyframes tracks-panel-item-selection):
+        (22.22%):
+        * Modules/modern-media-controls/controls/tracks-panel.js: Added.
+        (TracksPanel.prototype.get presented):
+        (TracksPanel.prototype.presentInParent):
+        (TracksPanel.prototype.hide):
+        (TracksPanel.prototype.get rightX):
+        (TracksPanel.prototype.set rightX):
+        (TracksPanel.prototype.trackNodeSelectionAnimationDidEnd):
+        (TracksPanel.prototype.mouseMovedOverTrackNode):
+        (TracksPanel.prototype.mouseExitedTrackNode):
+        (TracksPanel.prototype.commitProperty):
+        (TracksPanel.prototype.handleEvent):
+        (TracksPanel.prototype._childrenFromDataSource.):
+        (TracksPanel.prototype._childrenFromDataSource):
+        (TracksPanel.prototype._handleMousedown):
+        (TracksPanel.prototype._handleKeydown):
+        (TracksPanel.prototype._dismiss):
+        (TracksPanel.prototype._focusTrackNode):
+        (TracksPanel.prototype._focusPreviousTrackNode):
+        (TracksPanel.prototype._focusNextTrackNode):
+        (TracksPanel.prototype._focusFirstTrackNode):
+        (TracksPanel.prototype._focusLastTrackNode):
+        (TrackNode):
+        (TrackNode.prototype.activate):
+        (TrackNode.prototype.handleEvent):
+        (TrackNode.prototype._animationDidEnd):
+        * Modules/modern-media-controls/js-files:
+        * WebCore.xcodeproj/project.pbxproj:
+
 2016-12-01  Andreas Kling  <akl...@apple.com>
 
         Log some basic memory usage stats at interesting points in time

Modified: trunk/Source/WebCore/Modules/modern-media-controls/controls/macos-fullscreen-media-controls.css (209181 => 209182)


--- trunk/Source/WebCore/Modules/modern-media-controls/controls/macos-fullscreen-media-controls.css	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/Source/WebCore/Modules/modern-media-controls/controls/macos-fullscreen-media-controls.css	2016-12-01 16:16:38 UTC (rev 209182)
@@ -25,10 +25,15 @@
 
 /* Controls bar */
 
+.media-controls.mac.fullscreen {
+    --controls-bar-width: 468px;
+    --tracks-panel-right-margin: 40px;
+}
+
 .media-controls.mac.fullscreen > .controls-bar {
     left: 50%;
     bottom: 25px;
-    width: 468px;
+    width: var(--controls-bar-width);
     height: 75px;
     transform: translateX(-50%);
     overflow: hidden;
@@ -125,3 +130,11 @@
 .media-controls.mac.fullscreen .scrubber {
     top: 7px;
 }
+
+/* Tracks Panel */
+
+.media-controls.mac.fullscreen .tracks-panel {
+    /* Half of the screen width minus half of the controls bar width minus the distance to the right edge of the tracks button */
+    right: calc(50% - var(--controls-bar-width) / 2 + var(--tracks-panel-right-margin));
+    bottom: 234px;
+}

Modified: trunk/Source/WebCore/Modules/modern-media-controls/controls/macos-inline-media-controls.css (209181 => 209182)


--- trunk/Source/WebCore/Modules/modern-media-controls/controls/macos-inline-media-controls.css	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/Source/WebCore/Modules/modern-media-controls/controls/macos-inline-media-controls.css	2016-12-01 16:16:38 UTC (rev 209182)
@@ -126,3 +126,9 @@
     left: -15px;
     top: 40px;
 }
+
+/* Tracks Panel */
+
+.media-controls.mac.inline .tracks-panel {
+    bottom: 51px;
+}

Modified: trunk/Source/WebCore/Modules/modern-media-controls/controls/macos-media-controls.js (209181 => 209182)


--- trunk/Source/WebCore/Modules/modern-media-controls/controls/macos-media-controls.js	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/Source/WebCore/Modules/modern-media-controls/controls/macos-media-controls.js	2016-12-01 16:16:38 UTC (rev 209182)
@@ -34,7 +34,26 @@
 
         this.muteButton = new MuteButton(this);
         this.tracksButton = new TracksButton(this);
+        this.tracksPanel = new TracksPanel;
         this.volumeSlider = new VolumeSlider;
     }
 
+    // Public
+
+    showTracksPanel()
+    {
+        this.tracksButton.on = true;
+        this.tracksButton.element.blur();
+        this.controlsBar.userInteractionEnabled = false;
+        this.tracksPanel.presentInParent(this);
+    }
+
+    hideTracksPanel()
+    {
+        this.tracksButton.on = false;
+        this.tracksButton.element.focus();
+        this.controlsBar.userInteractionEnabled = true;
+        this.tracksPanel.hide();
+    }
+
 }

Modified: trunk/Source/WebCore/Modules/modern-media-controls/controls/media-controls.css (209181 => 209182)


--- trunk/Source/WebCore/Modules/modern-media-controls/controls/media-controls.css	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/Source/WebCore/Modules/modern-media-controls/controls/media-controls.css	2016-12-01 16:16:38 UTC (rev 209182)
@@ -31,6 +31,7 @@
 /* We need to use relative positioning due to webkit.org/b/163603 */
 .media-controls {
     position: relative;
+    font-family: -apple-system;
 }
 
 .media-controls > .controls-bar {

Modified: trunk/Source/WebCore/Modules/modern-media-controls/controls/placard.css (209181 => 209182)


--- trunk/Source/WebCore/Modules/modern-media-controls/controls/placard.css	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/Source/WebCore/Modules/modern-media-controls/controls/placard.css	2016-12-01 16:16:38 UTC (rev 209182)
@@ -32,8 +32,6 @@
 
     background-color: black;
     color: rgb(164, 164, 164);
-    
-    font-family: -apple-system;
 }
 
 .placard .container {

Modified: trunk/Source/WebCore/Modules/modern-media-controls/controls/status-label.css (209181 => 209182)


--- trunk/Source/WebCore/Modules/modern-media-controls/controls/status-label.css	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/Source/WebCore/Modules/modern-media-controls/controls/status-label.css	2016-12-01 16:16:38 UTC (rev 209182)
@@ -30,7 +30,6 @@
     white-space: nowrap;
     overflow: hidden;
 
-    font-family: -apple-system;
     font-size: 14px;
 
     color: rgba(255, 255, 255, 0.572);

Added: trunk/Source/WebCore/Modules/modern-media-controls/controls/tracks-panel.css (0 => 209182)


--- trunk/Source/WebCore/Modules/modern-media-controls/controls/tracks-panel.css	                        (rev 0)
+++ trunk/Source/WebCore/Modules/modern-media-controls/controls/tracks-panel.css	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.tracks-panel {
+    position: absolute;
+    display: inline-block;
+    border-radius: 7px;
+    /* FIXME: we want to use the real System Dark treatment here, see <rdar://problem/19993961> */
+    background-color: rgba(30, 30, 30, 0.45);
+    -webkit-backdrop-filter: saturate(180%) blur(20px);
+}
+
+.tracks-panel * {
+    font-size: 14px;
+    font-weight: 500;
+}
+
+.tracks-panel.fade-out {
+    transition-property: opacity;
+    transition-duration: 265ms;
+    opacity: 0;
+}
+
+.tracks-panel-section {
+    border-top: 2px solid rgb(104, 104, 104);
+}
+
+.tracks-panel-section:first-of-type {
+    border-top: 0;
+}
+
+.tracks-panel-section > h3 {
+    color: rgb(150, 150, 150);
+    padding: 5px 20px 1px 21px;
+    margin: 0;
+}
+
+.tracks-panel-section > ul {
+    list-style-type: none;
+    margin-top: 0;
+    padding: 0;
+}
+
+.tracks-panel-section > ul > li {
+    position: relative;
+    padding: 1px 20px 1px 33px;
+    color: white;
+}
+
+.tracks-panel-section > ul > li:focus {
+    background-color: rgba(26, 68, 243, 0.6);
+    -webkit-backdrop-filter: saturate(180%) blur(20px);
+    outline: none;
+}
+
+.tracks-panel-section > ul > li.selected:before {
+    position: absolute;
+    top: 3px;
+    left: 12px;
+    width: 12px;
+    display: inline-block;
+    content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300"><polygon fill="white" points="252.301,4.477 126.667,194.104 43.358,108.3 6.868,161.408 132.515,290.814 297.732,49.926"/></svg>');
+}
+
+.tracks-panel-section > ul > li.animated {
+    animation-name: tracks-panel-item-selection;
+    animation-duration: 150ms;
+    animation-timing-function: step-end;
+    animation-fill-mode: forwards;
+}
+
+@keyframes tracks-panel-item-selection {
+    0%, 55.55% {
+        background-color: rgba(26, 68, 243, 0.6);
+        -webkit-backdrop-filter: saturate(180%) blur(20px);
+    }
+
+    22.22% {
+        background: none;
+        -webkit-backdrop-filter: none;
+    }
+}

Added: trunk/Source/WebCore/Modules/modern-media-controls/controls/tracks-panel.js (0 => 209182)


--- trunk/Source/WebCore/Modules/modern-media-controls/controls/tracks-panel.js	                        (rev 0)
+++ trunk/Source/WebCore/Modules/modern-media-controls/controls/tracks-panel.js	2016-12-01 16:16:38 UTC (rev 209182)
@@ -0,0 +1,277 @@
+
+class TracksPanel extends LayoutNode
+{
+
+    constructor()
+    {
+        super(`<div class="tracks-panel">`);
+        this._rightX = 0;
+    }
+
+    // Public
+
+    get presented()
+    {
+        return !!this.parent;
+    }
+
+    presentInParent(node)
+    {
+        if (this.parent === node)
+            return;
+
+        this.children = this._childrenFromDataSource();
+
+        node.addChild(this);
+
+        this.element.removeEventListener("transitionend", this);
+        this.element.classList.remove("fade-out");
+
+        window.addEventListener("mousedown", this, true);
+        window.addEventListener("keydown", this, true);
+
+        this._focusedTrackNode = null;
+    }
+
+    hide()
+    {
+        if (!this.presented)
+            return;
+
+        window.removeEventListener("mousedown", this, true);
+        window.removeEventListener("keydown", this, true);
+
+        this.element.addEventListener("transitionend", this);
+        this.element.classList.add("fade-out");
+    }
+
+    get rightX()
+    {
+        return this._rightX;
+    }
+
+    set rightX(x)
+    {
+        if (this._rightX === x)
+            return;
+
+        this._rightX = x;
+        this.markDirtyProperty("rightX");
+    }
+
+    // Protected
+
+    trackNodeSelectionAnimationDidEnd(trackNode)
+    {
+        if (this.uiDelegate && typeof this.uiDelegate.tracksPanelSelectionDidChange === "function")
+            this.uiDelegate.tracksPanelSelectionDidChange(trackNode.index, trackNode.sectionIndex);
+    }
+
+    mouseMovedOverTrackNode(trackNode)
+    {
+        this._focusTrackNode(trackNode);
+    }
+
+    mouseExitedTrackNode(trackNode)
+    {
+        this._focusedTrackNode.element.blur();
+        delete this._focusedTrackNode;
+    }
+
+    commitProperty(propertyName)
+    {
+        if (propertyName === "rightX")
+            this.element.style.right = `${this._rightX}px`;
+        else
+            super.commitProperty(propertyName);
+    }
+
+    handleEvent(event)
+    {
+        switch (event.type) {
+        case "mousedown":
+            this._handleMousedown(event);
+            break;
+        case "keydown":
+            this._handleKeydown(event);
+            break;
+        case "transitionend":
+            this.remove();
+            break;
+        }
+    }
+
+    // Private
+
+    _childrenFromDataSource()
+    {
+        const children = [];
+
+        this._trackNodes = [];
+        
+        const dataSource = this.dataSource;
+        if (!dataSource)
+            return children;
+        
+        const numberOfSections = dataSource.tracksPanelNumberOfSections();
+        if (numberOfSections == 0)
+            return children;
+
+        for (let sectionIndex = 0; sectionIndex < numberOfSections; ++sectionIndex) {
+            let sectionNode = new LayoutNode(`<div class="tracks-panel-section"></div>`);
+            sectionNode.addChild(new LayoutNode(`<h3>${dataSource.tracksPanelTitleForSection(sectionIndex)}</h3>`));
+
+            let tracksListNode = sectionNode.addChild(new LayoutNode(`<ul></ul>`));
+            let numberOfTracks = dataSource.tracksPanelNumberOfTracksInSection(sectionIndex);
+            for (let trackIndex = 0; trackIndex < numberOfTracks; ++trackIndex) {
+                let trackTitle = dataSource.tracksPanelTitleForTrackInSection(trackIndex, sectionIndex);
+                let trackSelected = dataSource.tracksPanelIsTrackInSectionSelected(trackIndex, sectionIndex)
+                let trackNode = tracksListNode.addChild(new TrackNode(trackIndex, sectionIndex, trackTitle, trackSelected, this));
+                this._trackNodes.push(trackNode);
+            }
+            children.push(sectionNode);
+        }
+        
+        return children;
+    }
+
+    _handleMousedown(event)
+    {
+        if (this.element.contains(event.target))
+            return;
+
+        this._dismiss();
+
+        event.preventDefault();
+        event.stopPropagation();
+    }
+
+    _handleKeydown(event)
+    {
+        switch (event.key) {
+        case "Home":
+        case "PageUp":
+            this._focusFirstTrackNode();
+            break;
+        case "End":
+        case "PageDown":
+            this._focusLastTrackNode();
+            break;
+        case "ArrowDown":
+            if (event.altKey || event.metaKey)
+                this._focusLastTrackNode();
+            else
+                this._focusNextTrackNode();
+            break;
+        case "ArrowUp":
+            if (event.altKey || event.metaKey)
+                this._focusFirstTrackNode();
+            else
+                this._focusPreviousTrackNode();
+            break;
+        case " ":
+        case "Enter":
+            if (this._focusedTrackNode)
+                this._focusedTrackNode.activate();
+            break;
+        case "Escape":
+            this._dismiss();
+            break;
+        }
+    }
+
+    _dismiss()
+    {
+        if (this.parent && typeof this.parent.hideTracksPanel === "function")
+            this.parent.hideTracksPanel();
+    }
+
+    _focusTrackNode(trackNode)
+    {
+        if (!trackNode || trackNode === this._focusedTrackNode)
+            return;
+
+        trackNode.element.focus();
+        this._focusedTrackNode = trackNode;
+    }
+
+    _focusPreviousTrackNode()
+    {
+        const previousIndex = this._focusedTrackNode ? this._trackNodes.indexOf(this._focusedTrackNode) - 1 : this._trackNodes.length - 1;
+        this._focusTrackNode(this._trackNodes[previousIndex]);
+    }
+
+    _focusNextTrackNode()
+    {
+        this._focusTrackNode(this._trackNodes[this._trackNodes.indexOf(this._focusedTrackNode) + 1]);
+    }
+
+    _focusFirstTrackNode()
+    {
+        this._focusTrackNode(this._trackNodes[0]);
+    }
+
+    _focusLastTrackNode()
+    {
+        this._focusTrackNode(this._trackNodes[this._trackNodes.length - 1]);
+    }
+
+}
+
+class TrackNode extends LayoutNode
+{
+
+    constructor(index, sectionIndex, title, selected, panel)
+    {
+        super(`<li tabindex="0">${title}</li>`);
+
+        this.index = index;
+        this.sectionIndex = sectionIndex;
+        this._panel = panel;
+        this._selected = selected;
+
+        if (selected)
+            this.element.classList.add("selected");
+
+        this.element.addEventListener("mousemove", this);
+        this.element.addEventListener("mouseleave", this);
+        this.element.addEventListener("click", this);
+    }
+
+    // Public
+
+    activate()
+    {
+        this.element.addEventListener("animationend", this);
+        this.element.classList.add("animated");
+    }
+
+    // Protected
+
+    handleEvent(event)
+    {
+        switch (event.type) {
+        case "mousemove":
+            this._panel.mouseMovedOverTrackNode(this);
+            break;
+        case "mouseleave":
+            this._panel.mouseExitedTrackNode(this);
+            break;
+        case "click":
+            this.activate();
+            break;
+        case "animationend":
+            this._animationDidEnd();
+            break;
+        }
+    }
+
+    // Private
+
+    _animationDidEnd()
+    {
+        this.element.removeEventListener("animationend", this);
+        this._panel.trackNodeSelectionAnimationDidEnd(this);
+    }
+
+}

Modified: trunk/Source/WebCore/Modules/modern-media-controls/js-files (209181 => 209182)


--- trunk/Source/WebCore/Modules/modern-media-controls/js-files	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/Source/WebCore/Modules/modern-media-controls/js-files	2016-12-01 16:16:38 UTC (rev 209182)
@@ -23,6 +23,7 @@
 controls/buttons-container.js
 controls/status-label.js
 controls/controls-bar.js
+controls/tracks-panel.js
 controls/media-controls.js
 controls/ios-inline-media-controls.js
 controls/macos-media-controls.js

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (209181 => 209182)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2016-12-01 16:15:27 UTC (rev 209181)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2016-12-01 16:16:38 UTC (rev 209182)
@@ -10038,6 +10038,8 @@
 		713E70AF1733E8B300A22D00 /* plugIns.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode._javascript_; path = plugIns.js; sourceTree = "<group>"; };
 		714131471DC9D6AF00336107 /* fullscreen-support.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode._javascript_; path = "fullscreen-support.js"; sourceTree = "<group>"; };
 		714131481DC9D6EF00336107 /* js-files */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "js-files"; sourceTree = "<group>"; };
+		7146DF8B1DEFC2ED0046F98B /* tracks-panel.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = "tracks-panel.css"; sourceTree = "<group>"; };
+		7146DF8C1DEFC2ED0046F98B /* tracks-panel.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode._javascript_; path = "tracks-panel.js"; sourceTree = "<group>"; };
 		714F5B051DEE5F740075BD17 /* invalid-placard.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode._javascript_; path = "invalid-placard.js"; sourceTree = "<group>"; };
 		714F5B061DEE5F8A0075BD17 /* invalid-plac...@1x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "invalid-plac...@1x.png"; sourceTree = "<group>"; };
 		714F5B071DEE5F8A0075BD17 /* invalid-plac...@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "invalid-plac...@2x.png"; sourceTree = "<group>"; };
@@ -18065,6 +18067,8 @@
 		716FA0D71DB26591007323CC /* controls */ = {
 			isa = PBXGroup;
 			children = (
+				7146DF8B1DEFC2ED0046F98B /* tracks-panel.css */,
+				7146DF8C1DEFC2ED0046F98B /* tracks-panel.js */,
 				716FA0D81DB26591007323CC /* airplay-button.css */,
 				716FA0D91DB26591007323CC /* airplay-button.js */,
 				716FA0DA1DB26591007323CC /* airplay-placard.js */,
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to