Bug 1181055 - video play button does not work if the VIDEO element has onclick handler calling play(). r?gijs draft
authorJared Wein <jwein@mozilla.com>
Fri, 13 May 2016 21:42:33 -0400
changeset 367061 cc2b7fe9d3a9760d164e42d2b841e06018bb0fff
parent 365338 2f9351bae69d056e4615d21dda6bf42fec5d16b7
child 520905 cff0217f3e66bf39f05e76067c7f130797465a06
push id18128
push userjwein@mozilla.com
push dateSat, 14 May 2016 01:42:31 +0000
reviewersgijs
bugs1181055
milestone49.0a1
Bug 1181055 - video play button does not work if the VIDEO element has onclick handler calling play(). r?gijs MozReview-Commit-ID: c6V9LMIdK0
toolkit/content/tests/widgets/chrome.ini
toolkit/content/tests/widgets/test_videocontrols_onclickplay.html
toolkit/content/widgets/videocontrols.xml
--- a/toolkit/content/tests/widgets/chrome.ini
+++ b/toolkit/content/tests/widgets/chrome.ini
@@ -14,8 +14,9 @@ skip-if = os == 'linux' # Bug 1115088
 [test_menubar.xul]
 skip-if = os == 'mac'
 [test_popupanchor.xul]
 skip-if = os == 'android'
 [test_popupreflows.xul]
 [test_tree_column_reorder.xul]
 skip-if = toolkit == 'android'
 [test_videocontrols.html]
+[test_videocontrols_onclickplay.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/widgets/test_videocontrols_onclickplay.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Video controls test</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+
+<div id="content">
+  <video id="video" controls mozNoDynamicControls preload="auto"></video>
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+var video = document.getElementById("video");
+
+function startMediaLoad() {
+  // Kick off test once video has loaded, in its canplaythrough event handler.
+  video.src = "seek_with_sound.ogg";
+  video.addEventListener("canplaythrough", runTest, false);
+}
+
+function loadevent(event) {
+  SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startMediaLoad);
+}
+
+window.addEventListener("load",  loadevent, false);
+
+function runTest() {
+  video.addEventListener("click", function() {
+    this.play();
+  });
+  ok(video.paused, "video should be paused initially");
+
+  new Promise(resolve => {
+    let timeupdates = 0;
+    video.addEventListener("timeupdate", function timeupdate() {
+      ok(!video.paused, "video should not get paused after clicking in middle");
+
+      if (++timeupdates == 3) {
+        video.removeEventListener("timeupdate", timeupdate);
+        resolve();
+      }
+    });
+
+    synthesizeMouseAtCenter(video, {}, window);
+  }).then(function() {
+    new Promise(resolve => {
+      video.addEventListener("pause", function onpause() {
+        setTimeout(() => {
+          ok(video.paused, "video should still be paused 200ms after pause request");
+          // When the video reaches the end of playback it is automatically paused.
+          // Check during the pause event that the video has not reachd the end
+          // of playback.
+          ok(!video.ended, "video should not have paused due to playback ending");
+          resolve();
+        }, 200);
+      });
+
+      synthesizeMouse(video, 10, video.clientHeight - 10, {}, window);
+    }).then(SimpleTest.finish);
+  });
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -1091,34 +1091,40 @@
                 onFullscreenChange: function () {
                     if (this.isVideoInFullScreen()) {
                         Utils._hideControlsTimeout = setTimeout(this._hideControlsFn, this.HIDE_CONTROLS_TIMEOUT_MS);
                     }
                     this.setFullscreenButtonState();
                 },
 
                 clickToPlayClickHandler : function(e) {
-                    if (e.button != 0)
+                    if (e.button != undefined && e.button != 0)
                         return;
                     if (this.hasError() && !this.suppressError) {
                         // Errors that can be dismissed should be placed here as we discover them.
                         if (this.video.error.code != this.video.error.MEDIA_ERR_ABORTED)
                             return;
                         this.statusOverlay.hidden = true;
                         this.suppressError = true;
                         return;
                     }
 
-                    // Read defaultPrevented asynchronously, since Web content
-                    // may want to consume the "click" event but will only
-                    // receive it after us.
-                    let self = this;
-                    setTimeout(function clickToPlayCallback() {
-                        if (!e.defaultPrevented)
-                            self.togglePause();
+                    // Read defaultPrevented and the playback state asynchronously,
+                    // since Web content may want to consume the "click" event
+                    // but will only receive it after us. If web content
+                    // doesn't use preventDefault but still toggles playback,
+                    // we will treat that act the same as a call to preventDefault()
+                    // so the web content-initiated toggle is not reverted.
+                    let previousState = this.video.paused;
+                    setTimeout(() => {
+                        if (e.defaultPrevented ||
+                            this.video.paused != previousState) {
+                            return;
+                        }
+                        this.togglePause();
                     }, 0);
                 },
                 hideClickToPlay : function () {
                     let videoHeight = this.video.clientHeight;
                     let videoWidth = this.video.clientWidth;
 
                     // The play button will animate to 3x its size. This
                     // shows the animation unless the video is too small
@@ -1365,17 +1371,17 @@
                         }
                         return false;
                     }
                     return isDescendant(event.target) && isDescendant(event.relatedTarget);
                 },
 
                 log : function (msg) {
                     if (this.debug)
-                        dump("videoctl: " + msg + "\n");
+                        console.log("videoctl: " + msg + "\n");
                 },
 
                 get isTopLevelSyntheticDocument() {
                   let doc = this.video.ownerDocument;
                   let win = doc.defaultView;
                   return doc.mozSyntheticDocument && win === win.top;
                 },
 
@@ -1507,17 +1513,17 @@
                     // Helper function to add an event listener to the given element
                     function addListener(elem, eventName, func) {
                       let boundFunc = func.bind(self);
                       self.controlListeners.push({ item: elem, event: eventName, func: boundFunc });
                       elem.addEventListener(eventName, boundFunc, false);
                     }
 
                     addListener(this.muteButton, "command", this.toggleMute);
-                    addListener(this.playButton, "command", this.togglePause);
+                    addListener(this.playButton, "click", this.clickToPlayClickHandler);
                     addListener(this.fullscreenButton, "command", this.toggleFullscreen);
                     addListener(this.clickToPlay, "click", this.clickToPlayClickHandler);
                     addListener(this.controlsSpacer, "click", this.clickToPlayClickHandler);
                     addListener(this.controlsSpacer, "dblclick", this.toggleFullscreen);
 
                     addListener(this.videocontrols, "resizevideocontrols", this.adjustControlSize);
                     addListener(this.videocontrols, "transitionend", this.onTransitionEnd);
                     addListener(this.video.ownerDocument, "fullscreenchange", this.onFullscreenChange);