Source/WebCore/ChangeLog

 12017-04-20 Aaron Chu <aaron_chu@apple.com>
 2
 3 AX: Modern Media Controls Timeline slider should be operable
 4 https://bugs.webkit.org/show_bug.cgi?id=170250
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Added a "change" event listener and aria-valuetext to the slider so that when a
 9 VoiceOver user operates the timeline control, VO speaks the updated timestamp
 10
 11 Test: media/modern-media-controls/scrubber/scrubber-has-correct-ax-label.html
 12
 13 * English.lproj/modern-media-controls-localized-strings.js:
 14 * Modules/modern-media-controls/controls/scrubber.js:
 15 (Scrubber.prototype.set inputAccessibleLabel):
 16 (Scrubber.prototype._formatTime):
 17 * Modules/modern-media-controls/controls/slider.js:
 18 (Slider.prototype.handleEvent):
 19 (Slider.prototype._handleInputEvent): Deleted.
 20 * Modules/modern-media-controls/controls/time-control.js:
 21 (TimeControl.prototype.updateScrubberLabel):
 22 * Modules/modern-media-controls/controls/time-label.js:
 23 (TimeLabel.prototype.commitProperty):
 24 (TimeLabel.prototype._formattedTime):
 25 * Modules/modern-media-controls/main.js:
 26 (formatTimeByUnit):
 27 (unitizeTime):
 28
1292017-04-16 Chris Dumez <cdumez@apple.com>
230
331 CMD+R / CMD+Q is considered as user interaction and beforeunload alert is shown

Source/WebCore/English.lproj/modern-media-controls-localized-strings.js

@@const UIStrings = {
66 "Exit Full Screen": "Exit Full Screen",
77 "Error": "Error",
88 "Forward": "Forward",
 9 "Hour": "Hour",
 10 "Hours": "Hours",
911 "Invalid": "Invalid",
1012 "Skip Back 30 seconds": "Skip Back 30 seconds",
1113 "Live Broadcast": "Live Broadcast",
1214 "Loading": "Loading",
 15 "Minute": "Minute",
 16 "Minutes": "Minutes",
1317 "Media Selection": "Media Selection",
1418 "Mute": "Mute",
1519 "Pause": "Pause",

@@const UIStrings = {
1822 "Rewind": "Rewind",
1923 "Scale to Fill": "Scale to Fill",
2024 "Scale to Fit": "Scale to Fit",
 25 "Second": "Second",
 26 "Seconds": "Seconds",
2127 "Subtitles": "Subtitles",
2228 "Start": "Start",
2329 "This video is playing in Picture in Picture": "This video is playing in Picture in Picture",

Source/WebCore/Modules/modern-media-controls/controls/scrubber.js

@@class Scrubber extends Slider
5858 this.needsLayout = true;
5959 }
6060
 61 set inputAccessibleLabel(timeValue)
 62 {
 63 this._input.element.setAttribute("aria-valuetext", this._formatTime(timeValue));
 64 }
 65
6166 layoutTraitsDidChange()
6267 {
6368 this.height = (this.layoutTraits & LayoutTraits.Compact) ? 15 : 23;

@@class Scrubber extends Slider
6570
6671 // Protected
6772
 73 _formatTime(timeInSeconds)
 74 {
 75 const time = formatTimeByUnit(timeInSeconds);
 76 const timeStrings = [unitizeTime(time.minutes, "Minute"), unitizeTime(time.seconds, "Second")];
 77 if (time.hours > 0)
 78 timeStrings.unshift(unitizeTime(time.hours, "Hour"));
 79
 80 return timeStrings.join(" ");
 81 }
 82
6883 draw(ctx)
6984 {
7085 const width = this.width;

Source/WebCore/Modules/modern-media-controls/controls/slider.js

@@class Slider extends LayoutItem
3838 this._input = new LayoutNode(`<input type="range" min="0" max="1" step="0.001" />`);
3939 this._input.element.addEventListener("mousedown", this);
4040 this._input.element.addEventListener("input", this);
 41 this._input.element.addEventListener("change", this);
4142
4243 this.isActive = false;
4344 this.value = 0;

@@class Slider extends LayoutItem
7576 case "mouseup":
7677 this._handleMouseupEvent();
7778 break;
 79 case "change":
7880 case "input":
79  this._handleInputEvent();
 81 this._valueDidChange();
8082 break;
8183 }
8284 }

@@class Slider extends LayoutItem
123125 this.needsLayout = true;
124126 }
125127
126  _handleInputEvent()
 128 _valueDidChange()
127129 {
128130 if (this.uiDelegate && typeof this.uiDelegate.controlValueDidChange === "function")
129131 this.uiDelegate.controlValueDidChange(this);

Source/WebCore/Modules/modern-media-controls/controls/time-control.js

@@class TimeControl extends LayoutItem
8888 this.remainingTimeLabel.x = this.scrubber.x + this.scrubber.width + ScrubberMargin;
8989 }
9090
 91 updateScrubberLabel()
 92 {
 93 this.scrubber.inputAccessibleLabel = this.elapsedTimeLabel.value;
 94 }
9195}

Source/WebCore/Modules/modern-media-controls/controls/time-label.js

@@class TimeLabel extends LayoutNode
5454
5555 commitProperty(propertyName)
5656 {
57  if (propertyName === "value")
 57 if (propertyName === "value") {
5858 this.element.textContent = this._formattedTime();
 59 if (this._timeControl)
 60 this._timeControl.updateScrubberLabel();
 61 }
5962 else
6063 super.commitProperty(propertyName);
6164 }

@@class TimeLabel extends LayoutNode
6467
6568 _formattedTime()
6669 {
67  const time = this._value || 0;
68  const absTime = Math.abs(time);
69  const intSeconds = Math.floor(absTime % 60).toFixed(0);
70  const intMinutes = Math.floor((absTime / 60) % 60).toFixed(0);
71  const intHours = Math.floor(absTime / (60 * 60)).toFixed(0);
 70 const value = this._value || 0;
 71 const time = formatTimeByUnit(value);
 72 const timeStrings = [time.minutes, time.seconds];
 73 if (time.hours > 0 || (this._timeControl && this._timeControl.useSixDigitsForTimeLabels))
 74 timeStrings.unshift(time.hours);
7275
73  const timeStrings = [intMinutes, intSeconds];
74  if (intHours > 0 || (this._timeControl && this._timeControl.useSixDigitsForTimeLabels))
75  timeStrings.unshift(intHours);
76 
77  const sign = time < 0 ? "-" : "";
 76 const sign = value < 0 ? "-" : "";
7877 return sign + timeStrings.map(x => `00${x}`.slice(-2)).join(":");
7978 }
8079

Source/WebCore/Modules/modern-media-controls/main.js

@@function UIString(string)
4545 console.error(`Localization for "${string}" not found.`);
4646 return "LOCALIZED STRING NOT FOUND";
4747}
 48
 49function formatTimeByUnit(value)
 50{
 51 const time = value || 0;
 52 const absTime = Math.abs(time);
 53 return {
 54 "seconds": Math.floor(absTime % 60).toFixed(0),
 55 "minutes": Math.floor((absTime / 60) % 60).toFixed(0),
 56 "hours": Math.floor(absTime / (60 * 60)).toFixed(0)
 57 };
 58}
 59
 60function unitizeTime(value, unit)
 61{
 62 let returnedUnit = UIString(unit);
 63 if (value > 1)
 64 returnedUnit = UIString(`${unit}s`);
 65
 66 return `${value} ${returnedUnit}`;
 67}

LayoutTests/ChangeLog

 12017-04-20 Aaron Chu <aaron_chu@apple.com>
 2
 3 AX: Modern Media Controls Timeline slider should be operable
 4 https://bugs.webkit.org/show_bug.cgi?id=170250
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 * media/modern-media-controls/scrubber/scrubber-has-correct-ax-label-expected.txt: Added.
 9 * media/modern-media-controls/scrubber/scrubber-has-correct-ax-label.html: Added.
 10
1112017-04-16 Joseph Pecoraro <pecoraro@apple.com>
212
313 test262: test262/test/built-ins/Object/prototype/toLocaleString/primitive_this_value.js

LayoutTests/media/modern-media-controls/scrubber/scrubber-has-correct-ax-label-expected.txt

 1Testing for an accessibility text on the scrubber that correctly reflects the changes in media.currentTime.
 2
 3On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 4
 5
 6PASS scrubberInput.getAttribute('aria-valuetext') is "0 Minute 0 Second"
 7PASS media.currentTime is 2
 8PASS scrubberInput.getAttribute('aria-valuetext') === '0 Minute 2 Seconds' became true
 9PASS successfullyParsed is true
 10
 11TEST COMPLETE
 12
 13

LayoutTests/media/modern-media-controls/scrubber/scrubber-has-correct-ax-label.html

 1<script src="../../../resources/js-test-pre.js"></script>
 2<script src="../resources/media-controls-loader.js" type="text/javascript"></script>
 3<script src="../resources/media-controls-utils.js" type="text/javascript"></script>
 4<body>
 5<style type="text/css" media="screen">
 6
 7 video, #host {
 8 position: absolute;
 9 top: 0;
 10 left: 0;
 11 width: 800px;
 12 height: 240px;
 13 }
 14
 15</style>
 16<video src="../../content/test.mp4" style="width: 320px; height: 240px;" controls></video>
 17<div id="host"></div>
 18<script type="text/javascript">
 19
 20window.jsTestIsAsync = true;
 21
 22description("Testing for an accessibility text on the scrubber that correctly reflects the changes in media.currentTime.");
 23
 24const container = document.querySelector("div#host");
 25const media = document.querySelector("video");
 26const mediaController = createControls(container, media, null);
 27
 28mediaController.controls.showsStartButton = false;
 29
 30const scrubberNodes = mediaController.controls.timeControl.scrubber.children;
 31const scrubberInput = scrubberNodes[scrubberNodes.length - 1].element;
 32
 33media.addEventListener("canplay", () => {
 34
 35 shouldBeEqualToString("scrubberInput.getAttribute('aria-valuetext')", "0 Minute 0 Second");
 36 media.currentTime = 2;
 37 shouldBe("media.currentTime", "2");
 38 shouldBecomeEqual("scrubberInput.getAttribute('aria-valuetext') === '0 Minute 2 Seconds'", "true", finishJSTest);
 39})
 40
 41</script>
 42<script src="../../../resources/js-test-post.js"></script>
 43</body>