Bug 1274520 part 3 - Listen video events in system group. r=gijs draft
authorXidorn Quan <me@upsuper.org>
Wed, 25 May 2016 18:55:26 +1000
changeset 372666 d33d5451aff5dade23b6457242c255c0c93b82fc
parent 372665 f1755a506bf8f19a108b8750f5631958bd97c2a8
child 522209 30c453a2f734a35867fc757e0d26da6eb0ebeec8
push id19556
push userxquan@mozilla.com
push dateSun, 29 May 2016 23:37:12 +0000
reviewersgijs
bugs1274520
milestone49.0a1
Bug 1274520 part 3 - Listen video events in system group. r=gijs Since the UI is now changed in listeners inside the system group, they are invoked after the listeners added by the content. Because of that, UI check needs to be asynchronously, otherwise we would be checking the state before videocontrols update. For the same reason, mouse/keyboard operations need to be invoked asynchronously as well, otherwise they would be applied based on the UI before update. This shouldn't change what is tested here, as user inputs are asynchronous by nature. MozReview-Commit-ID: 4h9Oa9qMVc5
toolkit/content/tests/widgets/test_videocontrols.html
toolkit/content/tests/widgets/test_videocontrols_audio.html
toolkit/content/widgets/videocontrols.xml
--- a/toolkit/content/tests/widgets/test_videocontrols.html
+++ b/toolkit/content/tests/widgets/test_videocontrols.html
@@ -83,42 +83,50 @@ function runTest(event) {
      */
     case 1:
       // Check initial state upon load
       is(event.type, "canplaythrough", "checking event type");
       is(video.paused, true, "checking video play state");
       is(video.muted, false, "checking video mute state");
 
       // Click the play button
-      synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { });
+      SimpleTest.executeSoon(() => {
+        synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { });
+      });
       break;
 
     case 2:
       is(event.type, "play",  "checking event type");
       is(video.paused, false, "checking video play state");
       is(video.muted, false,  "checking video mute state");
 
       // Click the pause button
-      synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { });
+      SimpleTest.executeSoon(() => {
+        synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { });
+      });
       break;
 
     case 3:
       is(event.type, "pause", "checking event type");
       is(video.paused, true,  "checking video play state");
       is(video.muted, false,  "checking video mute state");
 
-      synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); // Mute.
+      SimpleTest.executeSoon(() => {
+        synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); // Mute.
+      });
       break;
 
     case 4:
       is(event.type, "volumechange", "checking event type");
       is(video.paused, true,  "checking video play state");
       is(video.muted,  true,  "checking video mute state");
 
-      synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); // Unmute.
+      SimpleTest.executeSoon(() => {
+        synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); // Unmute.
+      });
       break;
 
     /*
      * Bug 470596: Make sure that having CSS border or padding doesn't
      * break the controls (though it should move them)
      */
     case 5:
       is(event.type, "volumechange", "checking event type");
@@ -126,33 +134,37 @@ function runTest(event) {
       is(video.muted,  false, "checking video mute state");
 
       video.style.border = "medium solid purple";
       video.style.borderWidth = "30px 40px 50px 60px";
       video.style.padding = "10px 20px 30px 40px";
       // totals: top: 40px, right: 60px, bottom: 80px, left: 100px
 
       // Click the play button
-      synthesizeMouse(video, 100 + playButtonCenterX, 40 + playButtonCenterY, { });
+      SimpleTest.executeSoon(() => {
+        synthesizeMouse(video, 100 + playButtonCenterX, 40 + playButtonCenterY, { });
+      });
       break;
 
     case 6:
       is(event.type, "play",  "checking event type");
       is(video.paused, false, "checking video play state");
       is(video.muted, false,  "checking video mute state");
       video.pause();
       break;
 
     case 7:
       is(event.type, "pause",  "checking event type");
       is(video.paused, true, "checking video play state");
       is(video.muted, false,  "checking video mute state");
 
       // Click the mute button
-      synthesizeMouse(video, 100 + muteButtonCenterX, 40 + muteButtonCenterY, { });
+      SimpleTest.executeSoon(() => {
+        synthesizeMouse(video, 100 + muteButtonCenterX, 40 + muteButtonCenterY, { });
+      });
       break;
 
     case 8:
       is(event.type, "volumechange", "checking event type");
       is(video.paused, true,  "checking video play state");
       is(video.muted,  true,  "checking video mute state");
       // Clear the style set in test 5.
       video.style.border = "";
@@ -182,38 +194,42 @@ function runTest(event) {
      * Drag the slider's thumb to the halfway point with the mouse.
      */
     case 11:
       is(event.type, "seeked", "checking event type");
       ok(true, "video position is at " + video.currentTime);
       // Bug 477434 -- sometimes we get 0.098999 here instead of 0!
       // is(video.currentTime, 0.0, "checking playback position");
 
-      var beginDragX = scrubberOffsetX;
-      var endDragX = scrubberOffsetX + (scrubberWidth / 2);
-      synthesizeMouse(video, beginDragX, scrubberCenterY, { type: "mousedown", button: 0 });
-      synthesizeMouse(video, endDragX,   scrubberCenterY, { type: "mousemove", button: 0 });
-      synthesizeMouse(video, endDragX,   scrubberCenterY, { type: "mouseup",   button: 0 });
+      SimpleTest.executeSoon(() => {
+        var beginDragX = scrubberOffsetX;
+        var endDragX = scrubberOffsetX + (scrubberWidth / 2);
+        synthesizeMouse(video, beginDragX, scrubberCenterY, { type: "mousedown", button: 0 });
+        synthesizeMouse(video, endDragX,   scrubberCenterY, { type: "mousemove", button: 0 });
+        synthesizeMouse(video, endDragX,   scrubberCenterY, { type: "mouseup",   button: 0 });
+      });
       break;
 
     case 12:
       is(event.type, "seeking", "checking event type");
       ok(true, "video position is at " + video.currentTime);
       break;
 
     /*
      * Click the slider at the 1/4 point with the mouse (jump backwards)
      */
     case 13:
       is(event.type, "seeked", "checking event type");
       ok(true, "video position is at " + video.currentTime);
       var expectedTime = videoDuration / 2;
       ok(Math.abs(video.currentTime - expectedTime) < 0.1, "checking expected playback position");
 
-      synthesizeMouse(video, scrubberOffsetX + (scrubberWidth / 4), scrubberCenterY, { });
+      SimpleTest.executeSoon(() => {
+        synthesizeMouse(video, scrubberOffsetX + (scrubberWidth / 4), scrubberCenterY, { });
+      });
       break;
 
     case 14:
       is(event.type, "seeking", "checking event type");
       ok(true, "video position is at " + video.currentTime);
       break;
 
     case 15:
@@ -234,80 +250,90 @@ function runTest(event) {
 
     // See bug 694696.
     case 16:
       is(event.type, "volumechange", "checking event type");
       is(video.volume, 0.1, "Volume should be set.");
       ok(!video.muted, "Video is not muted.");
 
       video.focus();
-      synthesizeKey("VK_DOWN", {});
+      SimpleTest.executeSoon(() => synthesizeKey("VK_DOWN", {}));
       break;
 
     case 17:
       is(event.type, "volumechange", "checking event type");
       is(video.volume, 0, "Volume should be 0.");
       ok(!video.muted, "Video is not muted.");
-      ok(isMuteButtonMuted(), "Mute button says it's muted");
 
-      synthesizeKey("VK_UP", {});
+      SimpleTest.executeSoon(() => {
+        ok(isMuteButtonMuted(), "Mute button says it's muted");
+        synthesizeKey("VK_UP", {});
+      });
       break;
 
     case 18:
       is(event.type, "volumechange", "checking event type");
       is(video.volume, 0.1, "Volume is increased.");
       ok(!video.muted, "Video is not muted.");
-      ok(!isMuteButtonMuted(), "Mute button says it's not muted");
 
-      synthesizeKey("VK_DOWN", {});
+      SimpleTest.executeSoon(() => {
+        ok(!isMuteButtonMuted(), "Mute button says it's not muted");
+        synthesizeKey("VK_DOWN", {});
+      });
       break;
 
     case 19:
       is(event.type, "volumechange", "checking event type");
       is(video.volume, 0, "Volume should be 0.");
       ok(!video.muted, "Video is not muted.");
-      ok(isMuteButtonMuted(), "Mute button says it's muted");
 
-      synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { });
+      SimpleTest.executeSoon(() => {
+        ok(isMuteButtonMuted(), "Mute button says it's muted");
+        synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { });
+      });
       break;
 
     case 20:
       is(event.type, "volumechange", "checking event type");
       is(video.volume, 0.5, "Volume should be 0.5.");
       ok(!video.muted, "Video is not muted.");
 
-      synthesizeKey("VK_UP", {});
+      SimpleTest.executeSoon(() => synthesizeKey("VK_UP", {}));
       break;
 
     case 21:
       is(event.type, "volumechange", "checking event type");
       is(video.volume, 0.6, "Volume should be 0.6.");
       ok(!video.muted, "Video is not muted.");
 
-      synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { });
+      SimpleTest.executeSoon(() => {
+        synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { });
+      });
       break;
 
     case 22:
       is(event.type, "volumechange", "checking event type");
       is(video.volume, 0.6, "Volume should be 0.6.");
       ok(video.muted, "Video is muted.");
 
-      ok(isMuteButtonMuted(), "Mute button says it's muted");
-
-      synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { });
+      SimpleTest.executeSoon(() => {
+        ok(isMuteButtonMuted(), "Mute button says it's muted");
+        synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { });
+      });
       break;
 
     case 23:
       is(event.type, "volumechange", "checking event type");
       is(video.volume, 0.6, "Volume should be 0.6.");
       ok(!video.muted, "Video is not muted.");
 
-      ok(!isMuteButtonMuted(), "Mute button says it's not muted");
-
-      synthesizeMouse(video, fullscreenButtonCenterX, fullscreenButtonCenterY, { });
+      SimpleTest.executeSoon(() => {
+        ok(!isMuteButtonMuted(), "Mute button says it's not muted");
+        synthesizeMouse(video, fullscreenButtonCenterX, fullscreenButtonCenterY, { });
+      });
       break;
 
     case 24:
       is(event.type, "mozfullscreenchange", "checking event type");
       is(video.volume, 0.6, "Volume should still be 0.6");
       SimpleTest.executeSoon(function() {
         isVolumeSliderShowingCorrectVolume(video.volume);
         synthesizeKey("VK_ESCAPE", {});
--- a/toolkit/content/tests/widgets/test_videocontrols_audio.html
+++ b/toolkit/content/tests/widgets/test_videocontrols_audio.html
@@ -32,19 +32,21 @@
       if (result) {
         return result;
       }
     }
     return false;
   }
 
   function loadedmetadata(event) {
-    var controlBar = findElementByAttribute(video, "class", "controlBar");
-    is(controlBar.getAttribute("fullscreen-unavailable"), "true", "Fullscreen button is hidden");
-    SimpleTest.finish();
+    SimpleTest.executeSoon(function() {
+      var controlBar = findElementByAttribute(video, "class", "controlBar");
+      is(controlBar.getAttribute("fullscreen-unavailable"), "true", "Fullscreen button is hidden");
+      SimpleTest.finish();
+    });
   }
 
   var video = document.getElementById("video");
 
   SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startTest);
   function startTest() {
     // Kick off test once audio has loaded.
     video.addEventListener("loadedmetadata", loadedmetadata, false);
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -626,18 +626,22 @@
                             // Nothing to do...
                             break;
                         default:
                             this.log("!!! event " + aEvent.type + " not handled!");
                     }
                 },
 
                 terminateEventListeners : function () {
-                    for (let event of this.videoEvents)
-                        this.video.removeEventListener(event, this, false);
+                    for (let event of this.videoEvents) {
+                        this.video.removeEventListener(event, this, {
+                            capture: true,
+                            mozSystemGroup: true
+                        });
+                    }
 
                     for (let element of this.controlListeners) {
                         element.item.removeEventListener(element.event, element.func,
                                                          { mozSystemGroup: true });
                     }
 
                     delete this.controlListeners;
 
@@ -1323,20 +1327,25 @@
                     this.fullscreenButton   = document.getAnonymousElementByAttribute(binding, "class", "fullscreenButton");
                     this.volumeForeground   = document.getAnonymousElementByAttribute(binding, "anonid", "volumeForeground");
 
                     this.isAudioOnly = (this.video instanceof HTMLAudioElement);
                     this.setupInitialState();
                     this.setupNewLoadState();
 
                     // Use the handleEvent() callback for all media events.
-                    // The "error" event listener must capture, so that it can trap error events
-                    // from the <source> children, which don't bubble.
-                    for (let event of this.videoEvents)
-                        this.video.addEventListener(event, this, (event == "error") ? true : false);
+                    // Only the "error" event listener must capture, so that it can trap error
+                    // events from <source> children, which don't bubble. But we use capture
+                    // for all events in order to simplify the event listener add/remove.
+                    for (let event of this.videoEvents) {
+                        this.video.addEventListener(event, this, {
+                            capture: true,
+                            mozSystemGroup: true
+                        });
+                    }
 
                     var self = this;
 
                     this.controlListeners = [];
 
                     // Helper function to add an event listener to the given element
                     function addListener(elem, eventName, func) {
                       let boundFunc = func.bind(self);
@@ -1662,17 +1671,17 @@
           this.randomID = 0;
           this.Utils = {
             randomID : 0,
             videoEvents : ["play",
                            "playing"],
             controlListeners: [],
             terminateEventListeners : function () {
               for (let event of this.videoEvents)
-                this.video.removeEventListener(event, this, false);
+                this.video.removeEventListener(event, this, { mozSystemGroup: true });
 
               for (let element of this.controlListeners) {
                 element.item.removeEventListener(element.event, element.func,
                                                  { mozSystemGroup: true });
               }
 
               delete this.controlListeners;
             },
@@ -1737,17 +1746,17 @@
                 let boundFunc = func.bind(self);
                 self.controlListeners.push({ item: elem, event: eventName, func: boundFunc });
                 elem.addEventListener(eventName, boundFunc, { mozSystemGroup: true });
               }
               addListener(this.clickToPlay, "click", this.clickToPlayClickHandler);
               addListener(this.video, "MozNoControlsBlockedVideo", this.blockedVideoHandler);
 
               for (let event of this.videoEvents) {
-                this.video.addEventListener(event, this, false);
+                this.video.addEventListener(event, this, { mozSystemGroup: true });
               }
 
               if (this.video.autoplay && !this.video.mozAutoplayEnabled) {
                 this.blockedVideoHandler();
               }
             }
           };
           this.Utils.init(this);