Bug 462114 - Event handlers in video control XBL binding should self-terminate. r=enn
authorJustin Dolske <dolske@mozilla.com>
Wed, 25 Feb 2009 23:40:37 -0800
changeset 25541 6311be0957064ea81502cbcdfe381b6272e99e6b
parent 25540 98f8aef2dce4e1b09a46e7440eb2819c4c401f49
child 25542 c0c058f2f503b0c9624d4dcab2dc13f674a8a984
push id5598
push userjdolske@mozilla.com
push dateThu, 26 Feb 2009 07:41:19 +0000
treeherdermozilla-central@f31b9c9fb785 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersenn
bugs462114
milestone1.9.2a1pre
Bug 462114 - Event handlers in video control XBL binding should self-terminate. r=enn
toolkit/content/widgets/videocontrols.xml
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -109,55 +109,103 @@
         </method>
 
         <constructor>
             <![CDATA[
             this.init();
             ]]>
         </constructor>
 
+        <field name="randomID">0</field>
+
         <field name="Utils">
             <![CDATA[ ({
                 debug : false,
                 video : null,
                 videocontrols : null,
                 controlBar : null,
                 playButton : null,
                 muteButton : null,
 
                 scrubber       : null,
                 progressBar    : null,
                 bufferBar      : null,
                 thumbWidth : 0,
 
+                randomID : 0,
+                videoEvents : ["play", "pause", "ended", "volumechange", "loadeddata",
+                               "loadstart", "durationchange", "timeupdate", "progress",
+                               "emptied"],
+
                 FADE_TIME_MAX  : 200, // ms
                 FADE_TIME_STEP : 30,  // ms
 
                 fadeTime : 0,         // duration of active fade animation
                 fadingIn: false,      // are we fading in, or fading out?
                 fadeTimer : null,
                 controlsVisible : false,
 
                 firstFrameShown : false,
                 lastTimeUpdate : 0,
                 maxCurrentTimeSeen : 0,
 
+                /*
+                 * Set the initial state of the controls. The binding is normally created along
+                 * with video element, but could be attached at any point (eg, if the video is
+                 * removed from the document and then reinserted). Thus, some one-time events may
+                 * have already fired, and so we'll need to explicitly check the initial state.
+                 */
+                setupInitialState : function() {
+                    this.randomID = Math.random();
+                    this.videocontrols.randomID = this.randomID;
+
+                    this.playButton.setAttribute("paused", this.video.paused);
+                    this.muteButton.setAttribute("muted", this.video.muted);
+
+                    var duration = Math.round(this.video.duration * 1000); // in ms
+                    var currentTime = Math.round(this.video.currentTime * 1000); // in ms
+                    this.log("Initial playback position is at " + currentTime + " of " + duration);
+                    // It would be nice to retain maxCurrentTimeSeen, but it would be difficult
+                    // to determine if the media source changed while we were detached.
+                    this.maxCurrentTimeSeen = currentTime;
+                    this.durationChange(duration);
+                    this.showPosition(currentTime, duration);
+
+                    if (this.video.readyState >= this.video.HAVE_CURRENT_DATA)
+                        this.firstFrameShown = true;
+
+                    // We can't determine the exact buffering status, but do know if it's
+                    // fully loaded. (If it's still loading, it will fire a progress event
+                    // and we'll figure out the exact state then.)
+                    this.bufferBar.setAttribute("max", 100);
+                    if (this.video.networkState == this.video.NETWORK_LOADED)
+                        this.bufferBar.setAttribute("value", 100);
+                    else
+                        this.bufferBar.setAttribute("value", 0);
+                },
+
                 get dynamicControls() {
                     // Don't fade controls for <audio> elements.
                     var enabled = this.video instanceof HTMLVideoElement;
 
                     // Allow tests to explicitly suppress the fading of controls.
                     if (this.video.hasAttribute("mozNoDynamicControls"))
                         enabled = false;
 
                     return enabled;
                 },
                 
                 handleEvent : function (aEvent) {
                     this.log("Got " + aEvent.type + " media event");
+
+                    // If the binding is detached (or has been replaced by a
+                    // newer instance of the binding), nuke our event-listeners.
+                    if (this.videocontrols.randomID != this.randomID)
+                        this.terminateEventListeners();
+
                     switch (aEvent.type) {
                         case "play":
                             this.playButton.setAttribute("paused", false);
                             break;
                         case "pause":
                         case "ended":
                             this.playButton.setAttribute("paused", true);
                             break;
@@ -200,16 +248,27 @@
                         case "emptied":
                             this.bufferBar.value = 0;
                             break;
                         default:
                             this.log("!!! event " + aEvent.type + " not handled!");
                     }
                 },
 
+                terminateEventListeners : function () {
+                    for each (var event in this.videoEvents)
+                        this.video.removeEventListener(event, this, false);
+
+                    if (this.fadeTimer) {
+                        clearInterval(this.fadeTimer);
+                        this.fadeTimer = null;
+                    }
+                    this.log("--- videocontrols terminated ---");
+                },
+
                 durationChange : function (duration) {
                     if (isNaN(duration))
                         duration = this.maxCurrentTimeSeen;
                     this.log("Duration is " + duration + "ms");
 
                     this.scrubber.max = duration;
                     // XXX Can't set increment here, due to bug 473103. Also, doing so causes
                     // snapping when dragging with the mouse, so we can't just set a value for
@@ -386,18 +445,17 @@
             this.Utils.bufferBar      = document.getAnonymousElementByAttribute(this, "class", "bufferBar");
             this.Utils.scrubber       = document.getAnonymousElementByAttribute(this, "class", "scrubber");
 
             // Get the width of the scrubber thumb.
             var thumb = document.getAnonymousElementByAttribute(this.Utils.scrubber, "class", "scale-thumb");
             if (thumb)
                 this.Utils.thumbWidth = thumb.clientWidth;
              
-            // Set initial state of play/pause button.
-            this.Utils.playButton.setAttribute("paused", video.paused);
+            this.Utils.setupInitialState();
 
             // videocontrols.css hides the control bar by default, because if script
             // is disabled our binding's script is disabled too (bug 449358). Thus,
             // the controls are broken and we don't want them shown. But if script is
             // enabled, the code here will run and can explicitly unhide the controls.
             //
             // For videos with |autoplay| set, we'll leave the controls initially hidden,
             // so that they don't get in the way of the playing video. Otherwise we'll
@@ -406,27 +464,19 @@
             // (Note: the |controls| attribute is already handled via layout/style/html.css)
             if (!(video.autoplay && video.mozAutoplayEnabled) || !this.Utils.dynamicControls) {
                 this.Utils.controlBar.style.visibility = "visible";
                 this.Utils.controlBar.style.opacity = 1.0;
                 this.Utils.controlsVisible = true;
                 this.Utils.fadingIn = true;
             }
 
-            // Use Utils.handleEvent() callback for all media events.
-            video.addEventListener("play",         this.Utils, false);
-            video.addEventListener("pause",        this.Utils, false);
-            video.addEventListener("ended",        this.Utils, false);
-            video.addEventListener("volumechange", this.Utils, false);
-            video.addEventListener("loadeddata",   this.Utils, false);
-            video.addEventListener("loadstart",    this.Utils, false);
-            video.addEventListener("durationchange", this.Utils, false);
-            video.addEventListener("timeupdate",     this.Utils, false);
-            video.addEventListener("progress",       this.Utils, false);
-            video.addEventListener("emptied",        this.Utils, false);
+            // Use the Utils.handleEvent() callback for all media events.
+            for each (var event in this.Utils.videoEvents)
+                video.addEventListener(event, this.Utils, false);
 
             this.Utils.log("--- videocontrols initialized ---");
             ]]>
             </body>
         </method>
 
     </implementation>