Bug 1274520 part 3 - Listen video events in system group. r=gijs,jaws
authorXidorn Quan <me@upsuper.org>
Wed, 01 Jun 2016 11:35:53 +1000
changeset 338839 2863702401b0927e2884c2b15476790265c1ff5c
parent 338838 615ccf4a8bdc8cc2af1c4a16c1f89f930b0427b0
child 338840 b3415732a219a1b1ab4db34c2c7c2ee893d32bba
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgijs, jaws
bugs1274520
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1274520 part 3 - Listen video events in system group. r=gijs,jaws 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);