Title: [219662] trunk
Revision
219662
Author
[email protected]
Date
2017-07-19 13:13:15 -0700 (Wed, 19 Jul 2017)

Log Message

[iOS] REGRESSION: Scrubbing media using built-in controls does not pause media
https://bugs.webkit.org/show_bug.cgi?id=174650
<rdar://problem/33401877>

Patch by Antoine Quint <[email protected]> on 2017-07-19
Reviewed by Dean Jackson.

Source/WebCore:

We would only pause when scrubbing on macOS because we only listened to "mousedown" events on the
scrubber's backing <input> element to identify that the user had started interacting with the
scrubber.

Implementing the same technique on iOS required a little more work than just listening to "touchstart"
events on the same element. On top of that, we needed to make sure that we would only respond to
"touchstart" events on the slider's thumb, and not on the track, since only on macOS should the user
be able to click anywhere on the track to scrub. So we turn off pointer-events for the <input> on iOS
only, and turn them back on specifically for the thumb.

There is also some finessing when dealing with touch events where we need to track the identifier of
the touch that started the user interaction. So we keep track of it in an ivar and wait until we get
a "touchend" event where the changedTouches list contains a touch with that same identifier to ensure
the same touch that initiates and ends the scrubbing interaction.

Finally, we fix another issue that was uncovered while turning back on the ScrubbingSupport tests
where we would not trash the cached _value ivar when we initiated scrubbing, which was important since
we would mistakenly use the pre-srubbing value during a scrub.

* Modules/modern-media-controls/controls/slider.css:
(.ios .slider > input):
(.slider > input::-webkit-slider-thumb):
* Modules/modern-media-controls/controls/slider.js:
(Slider.prototype.handleEvent):
(Slider.prototype._handleMousedownEvent):
(Slider.prototype._interactionEndTarget):
(Slider.prototype._handleTouchstartEvent):
(Slider.prototype._valueWillStartChanging):
(Slider.prototype._valueDidStopChanging):
(Slider.prototype._handleMouseupEvent):
(Slider.prototype._handleTouchendEvent):

LayoutTests:

Rebaseline and turn back on all the ScrubberSupport tests on macOS and iOS.

* media/modern-media-controls/scrubber-support/ipad/scrubber-support-drag-expected.txt:
* media/modern-media-controls/scrubber-support/ipad/scrubber-support-drag.html:
* media/modern-media-controls/scrubber-support/scrubber-support-media-api-expected.txt:
* media/modern-media-controls/scrubber-support/scrubber-support-media-api.html:
* platform/ios-simulator/TestExpectations:
* platform/mac/TestExpectations:

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (219661 => 219662)


--- trunk/LayoutTests/ChangeLog	2017-07-19 19:18:40 UTC (rev 219661)
+++ trunk/LayoutTests/ChangeLog	2017-07-19 20:13:15 UTC (rev 219662)
@@ -1,3 +1,20 @@
+2017-07-19  Antoine Quint  <[email protected]>
+
+        [iOS] REGRESSION: Scrubbing media using built-in controls does not pause media
+        https://bugs.webkit.org/show_bug.cgi?id=174650
+        <rdar://problem/33401877>
+
+        Reviewed by Dean Jackson.
+
+        Rebaseline and turn back on all the ScrubberSupport tests on macOS and iOS.
+
+        * media/modern-media-controls/scrubber-support/ipad/scrubber-support-drag-expected.txt:
+        * media/modern-media-controls/scrubber-support/ipad/scrubber-support-drag.html:
+        * media/modern-media-controls/scrubber-support/scrubber-support-media-api-expected.txt:
+        * media/modern-media-controls/scrubber-support/scrubber-support-media-api.html:
+        * platform/ios-simulator/TestExpectations:
+        * platform/mac/TestExpectations:
+
 2017-07-19  Nan Wang  <[email protected]>
 
         AX: Web page reloaded when a node is labelling multiple childnodes

Modified: trunk/LayoutTests/media/modern-media-controls/scrubber-support/ipad/scrubber-support-drag-expected.txt (219661 => 219662)


--- trunk/LayoutTests/media/modern-media-controls/scrubber-support/ipad/scrubber-support-drag-expected.txt	2017-07-19 19:18:40 UTC (rev 219661)
+++ trunk/LayoutTests/media/modern-media-controls/scrubber-support/ipad/scrubber-support-drag-expected.txt	2017-07-19 20:13:15 UTC (rev 219662)
@@ -3,7 +3,9 @@
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-FAIL media.currentTime should be within 0.01 of 3.0136. Was 0.
+PASS mediaController.controls.timeControl.elapsedTimeLabel.element.getBoundingClientRect().width became different from 0
+PASS media.currentTime became 3.1872
+
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/media/modern-media-controls/scrubber-support/ipad/scrubber-support-drag.html (219661 => 219662)


--- trunk/LayoutTests/media/modern-media-controls/scrubber-support/ipad/scrubber-support-drag.html	2017-07-19 19:18:40 UTC (rev 219661)
+++ trunk/LayoutTests/media/modern-media-controls/scrubber-support/ipad/scrubber-support-drag.html	2017-07-19 20:13:15 UTC (rev 219662)
@@ -7,13 +7,10 @@
         position: absolute;
         top: 0;
         left: 0;
-    }
-
-    video {
         width: 720px;
         height: 240px;
     }
-    
+
 </style>
 <video src="" autoplay controls></video>
 <div id="host"></div>
@@ -26,51 +23,39 @@
 const container = document.querySelector("div#host");
 const media = document.querySelector("video");
 const mediaController = createControls(container, media, null);
+let bounds;
 
-let numberOfEvents = 0;
-scheduler.frameDidFire = function() {
-    if (media.paused)
-        return;
-
+media.addEventListener("play", () => {
     media.pause();
+    if (isNaN(media.duration))
+        media.addEventListener("durationchange", runTest);
+    else
+        runTest();
+});
 
-    const input = mediaController.controls.timeControl.scrubber.children[1].element;
-    const bounds = input.getBoundingClientRect();
+function runTest()
+{
+    shouldBecomeDifferent("mediaController.controls.timeControl.elapsedTimeLabel.element.getBoundingClientRect().width", "0", () => {
+        const bounds = mediaController.controls.timeControl.scrubber.children[1].element.getBoundingClientRect();
+        const minX = bounds.left + 12;
+        const dragStartX = minX;
+        const dragEndX = minX + bounds.width / 2;
+        const centerY = bounds.top + bounds.height / 2;
 
-    if (bounds.width === 0)
-        return;
+        testRunner.runUIScript(`
+            uiController.dragFromPointToPoint(${dragStartX}, ${centerY}, ${dragEndX}, ${centerY}, 0.15, function() {
+                uiController.uiScriptComplete("Done");
+            });`);
 
-    media.addEventListener("timeupdate", () => {
-        numberOfEvents++;
-
-        debug("timeupdate event with media.currentTime = " + media.currentTime);
+        shouldBecomeEqual("media.currentTime", "3.1872", () => {
+            debug("");
+            media.remove();
+            container.remove();
+            finishJSTest();
+        });
     });
+}
 
-    const minX = bounds.left + 7;
-    const dragStartX = minX;
-    const dragEndX = minX + bounds.width / 2;
-    const centerY = bounds.top + bounds.height / 2;
-
-    const point = document.body.appendChild(document.createElement("div"));
-    point.style.left = minX + "px";
-    point.style.top = centerY + "px";
-    point.style.width = "1px";
-    point.style.height = "1px";
-    point.style.position = "absolute";
-    point.style.backgroundColor = "red";
-
-    debug(`
-        uiController.dragFromPointToPoint(${dragStartX}, ${centerY}, ${dragEndX}, ${centerY}, 0.15, function() {
-            uiController.uiScriptComplete("Done");
-        });`);
-    testRunner.runUIScript(`
-        uiController.dragFromPointToPoint(${dragStartX}, ${centerY}, ${dragEndX}, ${centerY}, 0.15, function() {
-            uiController.uiScriptComplete("Done");
-        });`);
-
-    scheduler.frameDidFire = null;
-};
-
 </script>
 <script src=""
 </body>

Modified: trunk/LayoutTests/media/modern-media-controls/scrubber-support/scrubber-support-media-api-expected.txt (219661 => 219662)


--- trunk/LayoutTests/media/modern-media-controls/scrubber-support/scrubber-support-media-api-expected.txt	2017-07-19 19:18:40 UTC (rev 219661)
+++ trunk/LayoutTests/media/modern-media-controls/scrubber-support/scrubber-support-media-api-expected.txt	2017-07-19 20:13:15 UTC (rev 219662)
@@ -4,7 +4,7 @@
 
 
 PASS mediaController.controls.timeControl.scrubber.value is 0
-PASS mediaController.controls.timeControl.scrubber.value is 0.5
+PASS mediaController.controls.timeControl.scrubber.value became 0.5
 
 PASS successfullyParsed is true
 

Modified: trunk/LayoutTests/media/modern-media-controls/scrubber-support/scrubber-support-media-api.html (219661 => 219662)


--- trunk/LayoutTests/media/modern-media-controls/scrubber-support/scrubber-support-media-api.html	2017-07-19 19:18:40 UTC (rev 219661)
+++ trunk/LayoutTests/media/modern-media-controls/scrubber-support/scrubber-support-media-api.html	2017-07-19 20:13:15 UTC (rev 219662)
@@ -1,8 +1,13 @@
 <script src=""
 <script src="" type="text/_javascript_"></script>
 <body>
-<video src="" style="width: 320px; height: 240px;"></video>
-<div id="shadow"></div>
+<style type="text/css" media="screen">
+    video, #host {
+        position: absolute; width: 320px; height: 240px;
+    }
+</style>
+<video src="" controls autoplay></video>
+<div id="host"></div>
 <script type="text/_javascript_">
 
 window.jsTestIsAsync = true;
@@ -9,22 +14,32 @@
 
 description("Testing the <code>ScrubberSupport</code> behavior by using the media API.");
 
-const shadowRoot = document.querySelector("div#shadow").attachShadow({ mode: "open" });
+const host = document.querySelector("div#host");
 const media = document.querySelector("video");
-const mediaController = createControls(shadowRoot, media, null);
+const mediaController = createControls(host, media, null);
 
 shouldBe("mediaController.controls.timeControl.scrubber.value", "0");
 
-media.addEventListener("durationchange", () => {
+media.addEventListener("play", () => {
+    media.pause();
+    if (isNaN(media.duration))
+        media.addEventListener("durationchange", runTest);
+    else
+        runTest();
+});
+
+function runTest()
+{
     media.addEventListener("timeupdate", () => {
-        shouldBe("mediaController.controls.timeControl.scrubber.value", "0.5");
-        debug("");
-        shadowRoot.host.remove();
-        media.remove();
-        finishJSTest();
+        shouldBecomeEqual("mediaController.controls.timeControl.scrubber.value", "0.5", () => {
+            debug("");
+            host.remove();
+            media.remove();
+            finishJSTest();
+        });
     });
     media.currentTime = media.duration / 2;
-});
+}
 
 </script>
 <script src=""

Modified: trunk/LayoutTests/platform/ios-simulator/TestExpectations (219661 => 219662)


--- trunk/LayoutTests/platform/ios-simulator/TestExpectations	2017-07-19 19:18:40 UTC (rev 219661)
+++ trunk/LayoutTests/platform/ios-simulator/TestExpectations	2017-07-19 20:13:15 UTC (rev 219662)
@@ -96,6 +96,7 @@
 media/modern-media-controls/rewind-button [ Pass ]
 media/modern-media-controls/scheduler [ Pass ]
 media/modern-media-controls/scrubber [ Pass ]
+media/modern-media-controls/scrubber-support [ Pass ]
 media/modern-media-controls/skip-back-button [ Pass ]
 media/modern-media-controls/skip-forward-button [ Pass ]
 media/modern-media-controls/slider [ Pass ]
@@ -116,6 +117,7 @@
 media/modern-media-controls/mute-support/mute-support-press-on-button.html [ Skip ]
 media/modern-media-controls/pip-support/ipad/pip-support-tap.html [ Skip ]
 media/modern-media-controls/placard-support/ipad [ Skip ]
+media/modern-media-controls/scrubber-support/ipad/scrubber-support-drag.html [ Skip ]
 
 # These tests rely on fullscreen which do not use the WebKit media controls on iOS
 media/modern-media-controls/controls-visibility-support/controls-visibility-support-fullscreen-on-parent-element.html [ Skip ]
@@ -132,6 +134,8 @@
 media/modern-media-controls/media-documents/media-document-audio-mac-sizing.html [ Skip ]
 media/modern-media-controls/media-documents/media-document-video-mac-sizing.html [ Skip ]
 media/modern-media-controls/playback-support/playback-support-button-click.html [ Skip ]
+media/modern-media-controls/scrubber-support/scrubber-support-click.html [ Skip ]
+media/modern-media-controls/scrubber-support/scrubber-support-drag.html [ Skip ]
 
 webkit.org/b/172965 media/track/track-cue-overlap-snap-to-lines-not-set.html [ Failure ]
 

Modified: trunk/LayoutTests/platform/mac/TestExpectations (219661 => 219662)


--- trunk/LayoutTests/platform/mac/TestExpectations	2017-07-19 19:18:40 UTC (rev 219661)
+++ trunk/LayoutTests/platform/mac/TestExpectations	2017-07-19 20:13:15 UTC (rev 219662)
@@ -1577,6 +1577,7 @@
 media/modern-media-controls/rewind-button [ Pass ]
 media/modern-media-controls/scheduler [ Pass ]
 media/modern-media-controls/scrubber [ Pass ]
+media/modern-media-controls/scrubber-support [ Pass ]
 media/modern-media-controls/skip-back-button [ Pass ]
 media/modern-media-controls/skip-forward-button [ Pass ]
 media/modern-media-controls/slider [ Pass ]
@@ -1594,6 +1595,7 @@
 media/modern-media-controls/media-documents/ipad [ Skip ]
 media/modern-media-controls/media-documents/media-document-audio-ios-sizing.html [ Skip ]
 media/modern-media-controls/media-documents/media-document-video-ios-sizing.html [ Skip ]
+media/modern-media-controls/scrubber-support/ipad [ Skip ]
 
 # These tests use Picture-in-Picture which isn't supported on El Capitan.
 [ ElCapitan ] media/modern-media-controls/placard-support/placard-support-pip.html [ Skip ]

Modified: trunk/Source/WebCore/ChangeLog (219661 => 219662)


--- trunk/Source/WebCore/ChangeLog	2017-07-19 19:18:40 UTC (rev 219661)
+++ trunk/Source/WebCore/ChangeLog	2017-07-19 20:13:15 UTC (rev 219662)
@@ -1,3 +1,43 @@
+2017-07-19  Antoine Quint  <[email protected]>
+
+        [iOS] REGRESSION: Scrubbing media using built-in controls does not pause media
+        https://bugs.webkit.org/show_bug.cgi?id=174650
+        <rdar://problem/33401877>
+
+        Reviewed by Dean Jackson.
+
+        We would only pause when scrubbing on macOS because we only listened to "mousedown" events on the
+        scrubber's backing <input> element to identify that the user had started interacting with the
+        scrubber.
+
+        Implementing the same technique on iOS required a little more work than just listening to "touchstart"
+        events on the same element. On top of that, we needed to make sure that we would only respond to
+        "touchstart" events on the slider's thumb, and not on the track, since only on macOS should the user
+        be able to click anywhere on the track to scrub. So we turn off pointer-events for the <input> on iOS
+        only, and turn them back on specifically for the thumb.
+
+        There is also some finessing when dealing with touch events where we need to track the identifier of
+        the touch that started the user interaction. So we keep track of it in an ivar and wait until we get
+        a "touchend" event where the changedTouches list contains a touch with that same identifier to ensure
+        the same touch that initiates and ends the scrubbing interaction.
+
+        Finally, we fix another issue that was uncovered while turning back on the ScrubbingSupport tests
+        where we would not trash the cached _value ivar when we initiated scrubbing, which was important since
+        we would mistakenly use the pre-srubbing value during a scrub.
+
+        * Modules/modern-media-controls/controls/slider.css:
+        (.ios .slider > input):
+        (.slider > input::-webkit-slider-thumb):
+        * Modules/modern-media-controls/controls/slider.js:
+        (Slider.prototype.handleEvent):
+        (Slider.prototype._handleMousedownEvent):
+        (Slider.prototype._interactionEndTarget):
+        (Slider.prototype._handleTouchstartEvent):
+        (Slider.prototype._valueWillStartChanging):
+        (Slider.prototype._valueDidStopChanging):
+        (Slider.prototype._handleMouseupEvent):
+        (Slider.prototype._handleTouchendEvent):
+
 2017-07-19  Nan Wang  <[email protected]>
 
         AX: Web page reloaded when a node is labelling multiple childnodes

Modified: trunk/Source/WebCore/Modules/modern-media-controls/controls/slider.css (219661 => 219662)


--- trunk/Source/WebCore/Modules/modern-media-controls/controls/slider.css	2017-07-19 19:18:40 UTC (rev 219661)
+++ trunk/Source/WebCore/Modules/modern-media-controls/controls/slider.css	2017-07-19 20:13:15 UTC (rev 219662)
@@ -83,6 +83,16 @@
     -webkit-appearance: none !important;
 }
 
+.ios .slider > input {
+    /* On iOS only, we want to only capture events on the thumb and not the track. */
+    pointer-events: none;
+
+    /* Make sure we don't show any UI as we drag the scrubber on iOS. */
+    -webkit-user-select: none !important;
+    -webkit-touch-callout: none !important;
+    -webkit-tap-highlight-color: transparent;
+}
+
 .slider > input::-webkit-slider-thumb {
     width: 9px;
     height: 100%;
@@ -90,6 +100,7 @@
     box-shadow: none;
     background-color: transparent;
     -webkit-appearance: none !important;
+    pointer-events: all;
 }
 
 /* When disabled, we only want to show the track and turn off interaction. */

Modified: trunk/Source/WebCore/Modules/modern-media-controls/controls/slider.js (219661 => 219662)


--- trunk/Source/WebCore/Modules/modern-media-controls/controls/slider.js	2017-07-19 19:18:40 UTC (rev 219661)
+++ trunk/Source/WebCore/Modules/modern-media-controls/controls/slider.js	2017-07-19 20:13:15 UTC (rev 219662)
@@ -38,7 +38,7 @@
         this._container.children = [this._track, this._primaryFill, this._secondaryFill, this._knob];
 
         this._input = new LayoutNode(`<input type="range" min="0" max="1" step="0.001" />`);
-        this._input.element.addEventListener("mousedown", this);
+        this._input.element.addEventListener(GestureRecognizer.SupportsTouches ? "touchstart" : "mousedown", this);
         this._input.element.addEventListener("input", this);
         this._input.element.addEventListener("change", this);
 
@@ -112,9 +112,15 @@
         case "mousedown":
             this._handleMousedownEvent();
             break;
+        case "touchstart":
+            this._handleTouchstartEvent(event);
+            break;
         case "mouseup":
             this._handleMouseupEvent();
             break;
+        case "touchend":
+            this._handleTouchendEvent(event);
+            break;
         case "change":
         case "input":
             this._valueDidChange();
@@ -154,10 +160,38 @@
 
     _handleMousedownEvent()
     {
-        const mediaControls = this.parentOfType(MediaControls);
-        this._mouseupTarget = (!mediaControls || mediaControls instanceof MacOSInlineMediaControls) ? window : mediaControls.element;
+        this._mouseupTarget = this._interactionEndTarget();
         this._mouseupTarget.addEventListener("mouseup", this, true);
 
+        this._valueWillStartChanging();
+    }
+
+    _interactionEndTarget()
+    {
+        const mediaControls = this.parentOfType(MediaControls);
+        return (!mediaControls || mediaControls instanceof MacOSInlineMediaControls) ? window : mediaControls.element;
+    }
+
+    _handleTouchstartEvent(event)
+    {
+        // We're only interested in the very first touch on the <input>.
+        if (event.touches.length !== 1)
+            return;
+
+        this._initialTouchIdentifier = event.touches[0].identifier;
+
+        this._touchendTarget = this._interactionEndTarget();
+        this._touchendTarget.addEventListener("touchend", this, true);
+
+        this._valueWillStartChanging();
+    }
+
+    _valueWillStartChanging()
+    {
+        // We should no longer cache the value since we'll be interacting with the <input>
+        // so the value should be read back from it dynamically.
+        delete this._value;
+
         if (this.uiDelegate && typeof this.uiDelegate.controlValueWillStartChanging === "function")
             this.uiDelegate.controlValueWillStartChanging(this);
         this.isActive = true;
@@ -172,11 +206,8 @@
         this.needsLayout = true;
     }
 
-    _handleMouseupEvent()
+    _valueDidStopChanging()
     {
-        this._mouseupTarget.removeEventListener("mouseup", this, true);
-        delete this._mouseupTarget;
-
         this.isActive = false;
         if (this.uiDelegate && typeof this.uiDelegate.controlValueDidStopChanging === "function")
             this.uiDelegate.controlValueDidStopChanging(this);
@@ -184,4 +215,24 @@
         this.needsLayout = true;
     }
 
+    _handleMouseupEvent()
+    {
+        this._mouseupTarget.removeEventListener("mouseup", this, true);
+        delete this._mouseupTarget;
+
+        this._valueDidStopChanging();
+    }
+
+    _handleTouchendEvent(event)
+    {
+        if (!Array.from(event.changedTouches).find(touch => touch.identifier === this._initialTouchIdentifier))
+            return;
+
+        this._touchendTarget.removeEventListener("touchend", this, true);
+        delete this._touchendTarget;
+        delete this._initialTouchIdentifier;
+
+        this._valueDidStopChanging();
+    }
+
 }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to