2008-07-09 01:22:20 -07:00
|
|
|
<?xml version="1.0"?>
|
|
|
|
|
|
|
|
<bindings id="videoContolBindings"
|
|
|
|
xmlns="http://www.mozilla.org/xbl"
|
|
|
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
|
|
|
xmlns:xbl="http://www.mozilla.org/xbl"
|
|
|
|
xmlns:svg="http://www.w3.org/2000/svg">
|
|
|
|
|
2009-01-24 21:21:32 -08:00
|
|
|
<binding id="suppressChangeEvent"
|
|
|
|
extends="chrome://global/content/bindings/scale.xml#scale">
|
|
|
|
<implementation>
|
|
|
|
<method name="valueChanged">
|
|
|
|
<parameter name="which"/>
|
|
|
|
<parameter name="newValue"/>
|
|
|
|
<parameter name="userChanged"/>
|
|
|
|
<body>
|
|
|
|
<![CDATA[
|
|
|
|
// This method is a copy of the base binding's valueChanged(), except that it does
|
|
|
|
// not dispatch a |change| event (to avoid exposing the event to web content), and
|
|
|
|
// just calls the videocontrol's seekToPosition() method directly.
|
|
|
|
switch (which) {
|
|
|
|
case "curpos":
|
|
|
|
// The value of userChanged is true when changing the position with the mouse,
|
|
|
|
// but not when pressing an arrow key. However, the base binding sets
|
|
|
|
// ._userChanged in its keypress handlers, so we just need to check both.
|
|
|
|
if (!userChanged && !this._userChanged)
|
|
|
|
return;
|
|
|
|
this.setAttribute("value", newValue);
|
|
|
|
this.parentNode.parentNode.parentNode.Utils.seekToPosition();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "minpos":
|
|
|
|
this.setAttribute("min", newValue);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "maxpos":
|
|
|
|
this.setAttribute("max", newValue);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
]]>
|
|
|
|
</body>
|
|
|
|
</method>
|
|
|
|
</implementation>
|
|
|
|
</binding>
|
|
|
|
|
2008-07-09 01:22:20 -07:00
|
|
|
<binding id="videoControls">
|
2008-10-01 01:00:22 -07:00
|
|
|
|
|
|
|
<resources>
|
2009-01-19 14:51:38 -08:00
|
|
|
<stylesheet src="chrome://global/content/bindings/videocontrols.css"/>
|
2008-10-01 01:00:22 -07:00
|
|
|
<stylesheet src="chrome://global/skin/media/videocontrols.css"/>
|
|
|
|
</resources>
|
|
|
|
|
|
|
|
<xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
|
|
<spacer flex="1"/>
|
2008-12-20 14:57:34 -08:00
|
|
|
<hbox class="controlBar">
|
2008-12-22 13:05:29 -08:00
|
|
|
<button class="playButton" oncommand="this.parentNode.parentNode.Utils.togglePause();"/>
|
2009-01-24 21:21:32 -08:00
|
|
|
<stack class="scrubberStack" flex="1">
|
|
|
|
<box class="backgroundBar" flex="1"/>
|
|
|
|
<progressmeter class="bufferBar" flex="1"/>
|
|
|
|
<progressmeter class="progressBar" flex="1" max="10000"/>
|
|
|
|
<scale class="scrubber" flex="1"/>
|
|
|
|
</stack>
|
2008-12-22 13:05:29 -08:00
|
|
|
<button class="muteButton" oncommand="this.parentNode.parentNode.Utils.toggleMute();"/>
|
2008-10-29 00:35:49 -07:00
|
|
|
</hbox>
|
2008-10-01 01:00:22 -07:00
|
|
|
</xbl:content>
|
2008-07-09 01:22:20 -07:00
|
|
|
|
|
|
|
<implementation implements="nsISecurityCheckedComponent">
|
2008-10-01 01:00:22 -07:00
|
|
|
<!-- nsISecurityCheckedComponent -->
|
|
|
|
<method name="canCreateWrapper">
|
|
|
|
<parameter name="aIID"/>
|
|
|
|
<body>
|
|
|
|
return "AllAccess";
|
|
|
|
</body>
|
|
|
|
</method>
|
|
|
|
|
|
|
|
<method name="canCallMethod">
|
|
|
|
<parameter name="aIID"/>
|
|
|
|
<parameter name="aMethodName"/>
|
|
|
|
<body>
|
|
|
|
return "AllAccess";
|
|
|
|
</body>
|
|
|
|
</method>
|
|
|
|
|
|
|
|
<method name="canGetProperty">
|
|
|
|
<parameter name="aIID"/>
|
|
|
|
<parameter name="aPropertyName"/>
|
|
|
|
<body>
|
|
|
|
return "AllAccess";
|
|
|
|
</body>
|
|
|
|
</method>
|
|
|
|
|
|
|
|
<method name="canSetProperty">
|
|
|
|
<parameter name="aIID"/>
|
|
|
|
<parameter name="aPropertyName"/>
|
|
|
|
<body>
|
|
|
|
return "AllAccess";
|
|
|
|
</body>
|
|
|
|
</method>
|
|
|
|
|
|
|
|
<method name="QueryInterface">
|
|
|
|
<parameter name="aIID"/>
|
|
|
|
<body>
|
|
|
|
<![CDATA[
|
|
|
|
if (!iid.equals(Components.interfaces.nsISecurityCheckedComponent))
|
|
|
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
|
|
|
return this;
|
|
|
|
]]>
|
|
|
|
</body>
|
|
|
|
</method>
|
|
|
|
|
|
|
|
<constructor>
|
|
|
|
<![CDATA[
|
|
|
|
this.init();
|
|
|
|
]]>
|
|
|
|
</constructor>
|
|
|
|
|
|
|
|
<field name="Utils">
|
|
|
|
<![CDATA[ ({
|
|
|
|
debug : false,
|
|
|
|
video : null,
|
2009-01-10 08:28:15 -08:00
|
|
|
videocontrols : null,
|
2008-12-19 17:33:20 -08:00
|
|
|
controlBar : null,
|
2008-10-01 01:00:22 -07:00
|
|
|
playButton : null,
|
|
|
|
muteButton : null,
|
|
|
|
|
2009-01-24 21:21:32 -08:00
|
|
|
scrubber : null,
|
|
|
|
progressBar : null,
|
|
|
|
bufferBar : null,
|
|
|
|
thumbWidth : 0,
|
|
|
|
|
2008-12-19 17:33:20 -08:00
|
|
|
FADE_TIME_MAX : 200, // ms
|
|
|
|
FADE_TIME_STEP : 30, // ms
|
2008-10-01 01:00:22 -07:00
|
|
|
|
2008-12-19 17:33:20 -08:00
|
|
|
fadeTime : 0, // duration of active fade animation
|
2009-01-19 14:51:38 -08:00
|
|
|
fadingIn: false, // are we fading in, or fading out?
|
2008-12-19 17:33:20 -08:00
|
|
|
fadeTimer : null,
|
|
|
|
controlsVisible : false,
|
|
|
|
|
2009-01-19 14:51:38 -08:00
|
|
|
firstFrameShown : false,
|
2009-01-24 21:21:32 -08:00
|
|
|
lastTimeUpdate : 0,
|
|
|
|
maxCurrentTimeSeen : 0,
|
2009-01-19 14:51:38 -08:00
|
|
|
|
2008-12-19 17:33:20 -08:00
|
|
|
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;
|
|
|
|
},
|
|
|
|
|
2008-10-29 00:35:49 -07:00
|
|
|
handleEvent : function (aEvent) {
|
2008-10-01 01:00:22 -07:00
|
|
|
this.log("Got " + aEvent.type + " media event");
|
|
|
|
switch (aEvent.type) {
|
|
|
|
case "play":
|
|
|
|
this.playButton.setAttribute("paused", false);
|
|
|
|
break;
|
|
|
|
case "pause":
|
2008-11-03 19:22:30 -08:00
|
|
|
case "ended":
|
2008-10-01 01:00:22 -07:00
|
|
|
this.playButton.setAttribute("paused", true);
|
|
|
|
break;
|
|
|
|
case "volumechange":
|
|
|
|
this.muteButton.setAttribute("muted", this.video.muted);
|
|
|
|
break;
|
2009-01-19 14:51:38 -08:00
|
|
|
case "loadeddata":
|
|
|
|
this.firstFrameShown = true;
|
|
|
|
break;
|
2009-01-24 21:21:32 -08:00
|
|
|
case "loadstart":
|
|
|
|
this.maxCurrentTimeSeen = 0;
|
|
|
|
break;
|
|
|
|
case "durationchange":
|
|
|
|
var duration = Math.round(this.video.duration * 1000); // in ms
|
|
|
|
this.durationChange(duration);
|
|
|
|
break;
|
|
|
|
case "progress":
|
|
|
|
var loaded = aEvent.loaded;
|
|
|
|
var total = aEvent.total;
|
|
|
|
this.log("+++ load, " + loaded + " of " + total);
|
|
|
|
// When the source is streaming, the value of .total is -1. Set the
|
|
|
|
// progress bar to the maximum, since it's not useful.
|
|
|
|
if (total == -1)
|
|
|
|
total = loaded;
|
|
|
|
this.bufferBar.max = total;
|
|
|
|
this.bufferBar.value = loaded;
|
|
|
|
break;
|
|
|
|
case "timeupdate":
|
|
|
|
var currentTime = Math.round(this.video.currentTime * 1000); // in ms
|
|
|
|
var duration = Math.round(this.video.duration * 1000); // in ms
|
|
|
|
|
|
|
|
// Timeupdate events are dispatched *every frame*. Reduce workload by
|
|
|
|
// ignoring position changes that are within 333ms of the current position.
|
|
|
|
if (Math.abs(currentTime - this.lastTimeUpdate) < 333)
|
|
|
|
return;
|
|
|
|
this.lastTimeUpdate = currentTime;
|
|
|
|
|
|
|
|
this.showPosition(currentTime, duration);
|
|
|
|
break;
|
|
|
|
case "emptied":
|
|
|
|
this.bufferBar.value = 0;
|
|
|
|
break;
|
2008-10-01 01:00:22 -07:00
|
|
|
default:
|
|
|
|
this.log("!!! event " + aEvent.type + " not handled!");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2009-01-24 21:21:32 -08:00
|
|
|
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
|
|
|
|
// the arrow-keys.
|
|
|
|
//this.scrubber.increment = duration / 50;
|
|
|
|
this.scrubber.pageincrement = Math.round(duration / 10);
|
|
|
|
},
|
|
|
|
|
|
|
|
seekToPosition : function() {
|
|
|
|
var newPosition = this.scrubber.getAttribute("value");
|
|
|
|
newPosition /= 1000; // convert from ms
|
|
|
|
this.log("+++ seeking to " + newPosition);
|
|
|
|
this.video.currentTime = newPosition;
|
|
|
|
},
|
|
|
|
|
|
|
|
showPosition : function(currentTime, duration) {
|
|
|
|
// If the duration is unknown (because the server didn't provide
|
|
|
|
// it, or the video is a stream), then we want to fudge the duration
|
|
|
|
// by using the maximum playback position that's been seen.
|
|
|
|
if (currentTime > this.maxCurrentTimeSeen)
|
|
|
|
this.maxCurrentTimeSeen = currentTime;
|
|
|
|
if (isNaN(duration)) {
|
|
|
|
duration = this.maxCurrentTimeSeen;
|
|
|
|
this.durationChange(duration);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.log("time update @ " + currentTime + "ms of " + duration + "ms");
|
|
|
|
this.scrubber.value = currentTime;
|
|
|
|
|
|
|
|
// Extend the progressBar so it goes to the right-edge of the
|
|
|
|
// scrubber thumb. This is a bit tricky, due to how the thumb is
|
|
|
|
// positioned... It's flush-left at the minimum position, but
|
|
|
|
// flush-right at the maximum position (so that the full thumb is
|
|
|
|
// always visible in the <scale>'s box).
|
|
|
|
var percent = currentTime / duration;
|
|
|
|
var leftEdge = Math.floor(percent * (this.scrubber.clientWidth - this.thumbWidth));
|
|
|
|
var rightEdge = leftEdge + this.thumbWidth;
|
|
|
|
var adjPercent = rightEdge / this.scrubber.clientWidth;
|
|
|
|
|
|
|
|
// The progressBar has max=10000
|
|
|
|
this.progressBar.value = Math.round(adjPercent * 10000);
|
|
|
|
},
|
|
|
|
|
2008-12-19 17:33:20 -08:00
|
|
|
onMouseInOut : function (event) {
|
2009-01-19 14:51:38 -08:00
|
|
|
// If the controls are static, don't change anything.
|
2008-12-19 17:33:20 -08:00
|
|
|
if (!this.dynamicControls)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Ignore events caused by transitions between child nodes.
|
2009-01-10 08:28:15 -08:00
|
|
|
// Note that the videocontrols element is the same
|
|
|
|
// size as the *content area* of the video element,
|
|
|
|
// but this is not the same as the video element's
|
|
|
|
// border area if the video has border or padding.
|
|
|
|
if (this.isControlsOrDescendant(event.target) &&
|
|
|
|
this.isControlsOrDescendant(event.relatedTarget))
|
2008-12-19 17:33:20 -08:00
|
|
|
return;
|
|
|
|
|
2009-01-19 14:51:38 -08:00
|
|
|
var isMouseOver = (event.type == "mouseover");
|
|
|
|
|
|
|
|
// Suppress fading out the controls until the video has rendered
|
|
|
|
// its first frame. But since autoplay videos start off with no
|
|
|
|
// controls, let them fade-out so the controls don't get stuck on.
|
|
|
|
if (!this.firstFrameShown && !isMouseOver && !this.video.autoplay)
|
2008-12-19 17:33:20 -08:00
|
|
|
return;
|
2008-12-10 23:30:54 -08:00
|
|
|
|
2009-01-19 14:51:38 -08:00
|
|
|
// If we're already fading towards the desired state (or are
|
|
|
|
// already there), then we don't need to do anything more.
|
|
|
|
var directionChange = (this.fadingIn != isMouseOver);
|
|
|
|
if (!directionChange)
|
|
|
|
return;
|
2008-12-19 17:33:20 -08:00
|
|
|
|
|
|
|
this.fadingIn = isMouseOver;
|
2009-01-19 14:51:38 -08:00
|
|
|
this.log("Fading controls " + (this.fadingIn ? "in" : "out"));
|
|
|
|
|
|
|
|
// When switching direction mid-fade, we want the reversed fade
|
|
|
|
// to complete in the same amount of time as the current fade has
|
|
|
|
// been running. So we invert fadeTime.
|
|
|
|
//
|
|
|
|
// For example, if we're 20ms into a 100ms fade-in, then we want to
|
|
|
|
// fade-out over 20ms. This is done by setting fadeTime to 80ms
|
|
|
|
// (100-20), so that fadeControls will only animate for 20ms more.
|
|
|
|
if (this.fadeTime)
|
2008-12-19 17:33:20 -08:00
|
|
|
this.fadeTime = this.FADE_TIME_MAX - this.fadeTime;
|
|
|
|
|
|
|
|
if (!this.fadeTimer)
|
|
|
|
this.fadeTimer = setInterval(this.fadeControls, this.FADE_TIME_STEP, this);
|
|
|
|
|
|
|
|
// If we're fading in, immediately make the controls clickable.
|
|
|
|
// Otherwise they might not activate until the first fadeTimer
|
|
|
|
// fires, which is hard to test reliably.
|
|
|
|
if (this.fadingIn)
|
|
|
|
this.controlBar.style.visibility = "visible";
|
|
|
|
},
|
|
|
|
|
|
|
|
fadeControls : function (self, lateness) {
|
|
|
|
// Update elapsed time, and compute position as a percent
|
|
|
|
// of total. Last frame could run over, so clamp to 1.
|
|
|
|
self.fadeTime += self.FADE_TIME_STEP + lateness;
|
|
|
|
var pos = self.fadeTime / self.FADE_TIME_MAX;
|
|
|
|
if (pos > 1)
|
|
|
|
pos = 1;
|
|
|
|
|
|
|
|
// Calculate the opacity for our position in the animation.
|
|
|
|
var opacity;
|
|
|
|
if (self.fadingIn)
|
|
|
|
opacity = Math.pow(pos, 0.5);
|
|
|
|
else
|
|
|
|
opacity = Math.pow(1 - pos, 0.5);
|
|
|
|
self.controlsVisible = (opacity ? true : false);
|
|
|
|
|
|
|
|
self.controlBar.style.opacity = opacity;
|
|
|
|
|
|
|
|
// Use .visibility to ignore mouse clicks when hidden.
|
|
|
|
if (self.controlsVisible)
|
|
|
|
self.controlBar.style.visibility = "visible";
|
|
|
|
else
|
|
|
|
self.controlBar.style.visibility = "hidden";
|
|
|
|
|
|
|
|
// Is the animation done?
|
|
|
|
if (pos == 1) {
|
|
|
|
clearInterval(self.fadeTimer);
|
|
|
|
self.fadeTimer = null;
|
|
|
|
self.fadeTime = 0;
|
|
|
|
}
|
2008-10-01 01:00:22 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
togglePause : function () {
|
|
|
|
if (this.video.paused)
|
|
|
|
this.video.play();
|
|
|
|
else
|
|
|
|
this.video.pause();
|
|
|
|
|
|
|
|
// We'll handle style changes in the event listener for
|
|
|
|
// the "play" and "pause" events, same as if content
|
|
|
|
// script was controlling video playback.
|
|
|
|
},
|
|
|
|
|
|
|
|
toggleMute : function () {
|
|
|
|
this.video.muted = !this.video.muted;
|
|
|
|
|
|
|
|
// We'll handle style changes in the event listener for
|
|
|
|
// the "volumechange" event, same as if content script was
|
|
|
|
// controlling volume.
|
|
|
|
},
|
|
|
|
|
2009-01-10 08:28:15 -08:00
|
|
|
isControlsOrDescendant : function (node) {
|
2008-10-01 01:00:22 -07:00
|
|
|
while (node) {
|
2009-01-10 08:28:15 -08:00
|
|
|
if (node == this.videocontrols)
|
|
|
|
return true;
|
2008-10-01 01:00:22 -07:00
|
|
|
node = node.parentNode;
|
|
|
|
}
|
2009-01-10 08:28:15 -08:00
|
|
|
return false;
|
2008-10-01 01:00:22 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
log : function (msg) {
|
|
|
|
if (this.debug)
|
|
|
|
dump("videoctl: " + msg + "\n");
|
|
|
|
}
|
|
|
|
}) ]]>
|
|
|
|
</field>
|
|
|
|
|
|
|
|
<method name="init">
|
|
|
|
<body>
|
|
|
|
<![CDATA[
|
2008-07-09 01:22:20 -07:00
|
|
|
var video = this.parentNode;
|
2008-10-01 01:00:22 -07:00
|
|
|
this.Utils.video = video;
|
2009-01-10 08:28:15 -08:00
|
|
|
this.Utils.videocontrols = this;
|
2008-10-01 01:00:22 -07:00
|
|
|
|
2008-12-20 14:57:34 -08:00
|
|
|
this.Utils.controlBar = document.getAnonymousElementByAttribute(this, "class", "controlBar");
|
|
|
|
this.Utils.playButton = document.getAnonymousElementByAttribute(this, "class", "playButton");
|
|
|
|
this.Utils.muteButton = document.getAnonymousElementByAttribute(this, "class", "muteButton");
|
2009-01-24 21:21:32 -08:00
|
|
|
this.Utils.progressBar = document.getAnonymousElementByAttribute(this, "class", "progressBar");
|
|
|
|
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;
|
|
|
|
|
2008-10-01 01:00:22 -07:00
|
|
|
// Set initial state of play/pause button.
|
2008-10-28 14:54:17 -07:00
|
|
|
this.Utils.playButton.setAttribute("paused", video.paused);
|
2009-01-19 14:51:38 -08:00
|
|
|
|
|
|
|
// 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
|
|
|
|
// go ahead and reveal the controls now, so they're an obvious user cue.
|
|
|
|
//
|
|
|
|
// (Note: the |controls| attribute is already handled via layout/style/html.css)
|
|
|
|
if (!video.autoplay || !this.Utils.dynamicControls) {
|
|
|
|
this.Utils.controlBar.style.visibility = "visible";
|
|
|
|
this.Utils.controlBar.style.opacity = 1.0;
|
|
|
|
this.Utils.controlsVisible = true;
|
|
|
|
this.Utils.fadingIn = true;
|
2008-12-16 16:27:46 -08:00
|
|
|
}
|
2008-10-01 01:00:22 -07:00
|
|
|
|
2008-10-29 00:35:49 -07:00
|
|
|
// Use Utils.handleEvent() callback for all media events.
|
|
|
|
video.addEventListener("play", this.Utils, false);
|
|
|
|
video.addEventListener("pause", this.Utils, false);
|
2008-11-03 19:22:30 -08:00
|
|
|
video.addEventListener("ended", this.Utils, false);
|
2008-10-29 00:35:49 -07:00
|
|
|
video.addEventListener("volumechange", this.Utils, false);
|
2009-01-19 14:51:38 -08:00
|
|
|
video.addEventListener("loadeddata", this.Utils, false);
|
2009-01-24 21:21:32 -08:00
|
|
|
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);
|
2008-10-01 01:00:22 -07:00
|
|
|
|
|
|
|
this.Utils.log("--- videocontrols initialized ---");
|
|
|
|
]]>
|
|
|
|
</body>
|
|
|
|
</method>
|
2008-07-09 01:22:20 -07:00
|
|
|
|
|
|
|
</implementation>
|
2008-10-01 01:00:22 -07:00
|
|
|
|
|
|
|
<handlers>
|
|
|
|
<handler event="mouseover">
|
2008-12-19 17:33:20 -08:00
|
|
|
this.Utils.onMouseInOut(event);
|
2008-10-01 01:00:22 -07:00
|
|
|
</handler>
|
|
|
|
<handler event="mouseout">
|
2008-12-19 17:33:20 -08:00
|
|
|
this.Utils.onMouseInOut(event);
|
2008-10-01 01:00:22 -07:00
|
|
|
</handler>
|
|
|
|
</handlers>
|
2008-07-09 01:22:20 -07:00
|
|
|
</binding>
|
|
|
|
</bindings>
|