toolkit/content/widgets/scrollbox.xml

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/content/widgets/scrollbox.xml	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,794 @@
     1.4 +<?xml version="1.0"?>
     1.5 +<!-- This Source Code Form is subject to the terms of the Mozilla Public
     1.6 +   - License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 +   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
     1.8 +
     1.9 +
    1.10 +<bindings id="arrowscrollboxBindings"
    1.11 +   xmlns="http://www.mozilla.org/xbl"
    1.12 +   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    1.13 +   xmlns:xbl="http://www.mozilla.org/xbl">
    1.14 +
    1.15 +  <binding id="scrollbox-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
    1.16 +    <resources>
    1.17 +      <stylesheet src="chrome://global/skin/scrollbox.css"/>
    1.18 +    </resources>
    1.19 +  </binding>
    1.20 +
    1.21 +  <binding id="scrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
    1.22 +    <content>
    1.23 +      <xul:box class="box-inherit scrollbox-innerbox" xbl:inherits="orient,align,pack,dir" flex="1">
    1.24 +        <children/>
    1.25 +      </xul:box>
    1.26 +    </content>
    1.27 +  </binding>
    1.28 +
    1.29 +  <binding id="arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
    1.30 +    <content>
    1.31 +      <xul:autorepeatbutton class="autorepeatbutton-up"
    1.32 +                            anonid="scrollbutton-up"
    1.33 +                            collapsed="true"
    1.34 +                            xbl:inherits="orient"
    1.35 +                            oncommand="_autorepeatbuttonScroll(event);"/>
    1.36 +      <xul:scrollbox class="arrowscrollbox-scrollbox"
    1.37 +                     anonid="scrollbox"
    1.38 +                     flex="1"
    1.39 +                     xbl:inherits="orient,align,pack,dir">
    1.40 +        <children/>
    1.41 +      </xul:scrollbox>
    1.42 +      <xul:autorepeatbutton class="autorepeatbutton-down"
    1.43 +                            anonid="scrollbutton-down"
    1.44 +                            collapsed="true"
    1.45 +                            xbl:inherits="orient"
    1.46 +                            oncommand="_autorepeatbuttonScroll(event);"/>
    1.47 +    </content>
    1.48 +
    1.49 +    <implementation>
    1.50 +      <destructor><![CDATA[
    1.51 +        this._stopSmoothScroll();
    1.52 +      ]]></destructor>
    1.53 +
    1.54 +      <field name="_scrollbox">
    1.55 +        document.getAnonymousElementByAttribute(this, "anonid", "scrollbox");
    1.56 +      </field>
    1.57 +      <field name="_scrollButtonUp">
    1.58 +        document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-up");
    1.59 +      </field>
    1.60 +      <field name="_scrollButtonDown">
    1.61 +        document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-down");
    1.62 +      </field>
    1.63 +
    1.64 +      <field name="__prefBranch">null</field>
    1.65 +      <property name="_prefBranch" readonly="true">
    1.66 +        <getter><![CDATA[
    1.67 +          if (this.__prefBranch === null) {
    1.68 +            this.__prefBranch = Components.classes['@mozilla.org/preferences-service;1']
    1.69 +                                          .getService(Components.interfaces.nsIPrefBranch);
    1.70 +          }
    1.71 +          return this.__prefBranch;
    1.72 +        ]]></getter>
    1.73 +      </property>
    1.74 +
    1.75 +      <field name="_scrollIncrement">null</field>
    1.76 +      <property name="scrollIncrement" readonly="true">
    1.77 +        <getter><![CDATA[
    1.78 +          if (this._scrollIncrement === null) {
    1.79 +            try {
    1.80 +              this._scrollIncrement = this._prefBranch
    1.81 +                                          .getIntPref("toolkit.scrollbox.scrollIncrement");
    1.82 +            }
    1.83 +            catch (ex) {
    1.84 +              this._scrollIncrement = 20;
    1.85 +            }
    1.86 +          }
    1.87 +          return this._scrollIncrement;
    1.88 +        ]]></getter>
    1.89 +      </property>
    1.90 +
    1.91 +      <field name="_smoothScroll">null</field>
    1.92 +      <property name="smoothScroll">
    1.93 +        <getter><![CDATA[
    1.94 +          if (this._smoothScroll === null) {
    1.95 +            if (this.hasAttribute("smoothscroll")) {
    1.96 +              this._smoothScroll = (this.getAttribute("smoothscroll") == "true");
    1.97 +            } else {
    1.98 +              try {
    1.99 +                this._smoothScroll = this._prefBranch
   1.100 +                                         .getBoolPref("toolkit.scrollbox.smoothScroll");
   1.101 +              }
   1.102 +              catch (ex) {
   1.103 +                this._smoothScroll = true;
   1.104 +              }
   1.105 +            }
   1.106 +          }
   1.107 +          return this._smoothScroll;
   1.108 +        ]]></getter>
   1.109 +        <setter><![CDATA[
   1.110 +          this._smoothScroll = val;
   1.111 +          return val;
   1.112 +        ]]></setter>
   1.113 +      </property>
   1.114 +
   1.115 +      <field name="_scrollBoxObject">null</field>
   1.116 +      <property name="scrollBoxObject" readonly="true">
   1.117 +        <getter><![CDATA[
   1.118 +          if (!this._scrollBoxObject) {
   1.119 +            this._scrollBoxObject =
   1.120 +              this._scrollbox.boxObject
   1.121 +                             .QueryInterface(Components.interfaces.nsIScrollBoxObject);
   1.122 +          }
   1.123 +          return this._scrollBoxObject;
   1.124 +        ]]></getter>
   1.125 +      </property>
   1.126 +
   1.127 +      <property name="scrollClientRect" readonly="true">
   1.128 +        <getter><![CDATA[
   1.129 +          return this._scrollbox.getBoundingClientRect();
   1.130 +        ]]></getter>
   1.131 +      </property>
   1.132 +
   1.133 +      <property name="scrollClientSize" readonly="true">
   1.134 +        <getter><![CDATA[
   1.135 +          return this.orient == "vertical" ?
   1.136 +                 this._scrollbox.clientHeight :
   1.137 +                 this._scrollbox.clientWidth;
   1.138 +        ]]></getter>
   1.139 +      </property>
   1.140 +
   1.141 +      <property name="scrollSize" readonly="true">
   1.142 +        <getter><![CDATA[
   1.143 +          return this.orient == "vertical" ?
   1.144 +                 this._scrollbox.scrollHeight :
   1.145 +                 this._scrollbox.scrollWidth;
   1.146 +        ]]></getter>
   1.147 +      </property>
   1.148 +      <property name="scrollPaddingRect" readonly="true">
   1.149 +        <getter><![CDATA[
   1.150 +          // This assumes that this._scrollbox doesn't have any border.
   1.151 +          var outerRect = this.scrollClientRect;
   1.152 +          var innerRect = {};
   1.153 +          innerRect.left = outerRect.left - this._scrollbox.scrollLeft;
   1.154 +          innerRect.top = outerRect.top - this._scrollbox.scrollTop;
   1.155 +          innerRect.right = innerRect.left + this._scrollbox.scrollWidth;
   1.156 +          innerRect.bottom = innerRect.top + this._scrollbox.scrollHeight;
   1.157 +          return innerRect;
   1.158 +        ]]></getter>
   1.159 +      </property>
   1.160 +      <property name="scrollboxPaddingStart" readonly="true">
   1.161 +        <getter><![CDATA[
   1.162 +          var ltr = (window.getComputedStyle(this, null).direction == "ltr");
   1.163 +          var paddingStartName = ltr ? "padding-left" : "padding-right";
   1.164 +          var scrollboxStyle = window.getComputedStyle(this._scrollbox, null);
   1.165 +          return parseFloat(scrollboxStyle.getPropertyValue(paddingStartName));
   1.166 +        ]]></getter>
   1.167 +      </property>
   1.168 +      <property name="scrollPosition">
   1.169 +        <getter><![CDATA[
   1.170 +          return this.orient == "vertical" ?
   1.171 +                 this._scrollbox.scrollTop :
   1.172 +                 this._scrollbox.scrollLeft;
   1.173 +        ]]></getter>
   1.174 +        <setter><![CDATA[
   1.175 +          if (this.orient == "vertical")
   1.176 +            this._scrollbox.scrollTop = val;
   1.177 +          else
   1.178 +            this._scrollbox.scrollLeft = val;
   1.179 +          return val;
   1.180 +        ]]></setter>
   1.181 +      </property>
   1.182 +
   1.183 +      <property name="_startEndProps" readonly="true">
   1.184 +        <getter><![CDATA[
   1.185 +          return this.orient == "vertical" ?
   1.186 +                 ["top", "bottom"] : ["left", "right"];
   1.187 +        ]]></getter>
   1.188 +      </property>
   1.189 +
   1.190 +      <field name="_isRTLScrollbox"><![CDATA[
   1.191 +        this.orient != "vertical" &&
   1.192 +        document.defaultView.getComputedStyle(this._scrollbox, "").direction == "rtl";
   1.193 +      ]]></field>
   1.194 +
   1.195 +      <field name="_scrollTarget">null</field>
   1.196 +
   1.197 +      <method name="_canScrollToElement">
   1.198 +        <parameter name="element"/>
   1.199 +        <body><![CDATA[
   1.200 +          return window.getComputedStyle(element).display != "none";
   1.201 +        ]]></body>
   1.202 +      </method>
   1.203 +
   1.204 +      <method name="ensureElementIsVisible">
   1.205 +        <parameter name="element"/>
   1.206 +        <parameter name="aSmoothScroll"/>
   1.207 +        <body><![CDATA[
   1.208 +          if (!this._canScrollToElement(element))
   1.209 +            return;
   1.210 +
   1.211 +          var vertical = this.orient == "vertical";
   1.212 +          var rect = this.scrollClientRect;
   1.213 +          var containerStart = vertical ? rect.top : rect.left;
   1.214 +          var containerEnd = vertical ? rect.bottom : rect.right;
   1.215 +          rect = element.getBoundingClientRect();
   1.216 +          var elementStart = vertical ? rect.top : rect.left;
   1.217 +          var elementEnd = vertical ? rect.bottom : rect.right;
   1.218 +
   1.219 +          var scrollPaddingRect = this.scrollPaddingRect;
   1.220 +          let style = window.getComputedStyle(this._scrollbox, null);
   1.221 +          var scrollContentRect = {
   1.222 +            left: scrollPaddingRect.left + parseFloat(style.paddingLeft),
   1.223 +            top: scrollPaddingRect.top + parseFloat(style.paddingTop),
   1.224 +            right: scrollPaddingRect.right - parseFloat(style.paddingRight),
   1.225 +            bottom: scrollPaddingRect.bottom - parseFloat(style.paddingBottom)
   1.226 +          };
   1.227 +
   1.228 +          // Provide an entry point for derived bindings to adjust these values.
   1.229 +          if (this._adjustElementStartAndEnd) {
   1.230 +            [elementStart, elementEnd] =
   1.231 +              this._adjustElementStartAndEnd(element, elementStart, elementEnd);
   1.232 +          }
   1.233 +
   1.234 +          if (elementStart <= (vertical ? scrollContentRect.top : scrollContentRect.left)) {
   1.235 +            elementStart = vertical ? scrollPaddingRect.top : scrollPaddingRect.left;
   1.236 +          }
   1.237 +          if (elementEnd >= (vertical ? scrollContentRect.bottom : scrollContentRect.right)) {
   1.238 +            elementEnd = vertical ? scrollPaddingRect.bottom : scrollPaddingRect.right;
   1.239 +          }
   1.240 +
   1.241 +          var amountToScroll;
   1.242 +
   1.243 +          if (elementStart < containerStart) {
   1.244 +            amountToScroll = elementStart - containerStart;
   1.245 +          } else if (containerEnd < elementEnd) {
   1.246 +            amountToScroll = elementEnd - containerEnd;
   1.247 +          } else if (this._isScrolling) {
   1.248 +            // decelerate if a currently-visible element is selected during the scroll
   1.249 +            const STOP_DISTANCE = 15;
   1.250 +            if (this._isScrolling == -1 && elementStart - STOP_DISTANCE < containerStart)
   1.251 +              amountToScroll = elementStart - containerStart;
   1.252 +            else if (this._isScrolling == 1 && containerEnd - STOP_DISTANCE < elementEnd)
   1.253 +              amountToScroll = elementEnd - containerEnd;
   1.254 +            else
   1.255 +              amountToScroll = this._isScrolling * STOP_DISTANCE;
   1.256 +          } else {
   1.257 +            return;
   1.258 +          }
   1.259 +
   1.260 +          this._stopSmoothScroll();
   1.261 +
   1.262 +          if (aSmoothScroll != false && this.smoothScroll) {
   1.263 +            this._smoothScrollByPixels(amountToScroll, element);
   1.264 +          } else {
   1.265 +            this.scrollByPixels(amountToScroll);
   1.266 +          }
   1.267 +        ]]></body>
   1.268 +      </method>
   1.269 +
   1.270 +      <method name="_smoothScrollByPixels">
   1.271 +        <parameter name="amountToScroll"/>
   1.272 +        <parameter name="element"/><!-- optional -->
   1.273 +        <body><![CDATA[
   1.274 +          this._stopSmoothScroll();
   1.275 +          if (amountToScroll == 0)
   1.276 +            return;
   1.277 +
   1.278 +          this._scrollTarget = element;
   1.279 +          // Positive amountToScroll makes us scroll right (elements fly left), negative scrolls left.
   1.280 +          this._isScrolling = amountToScroll < 0 ? -1 : 1;
   1.281 +
   1.282 +          this._scrollAnim.start(amountToScroll);
   1.283 +        ]]></body>
   1.284 +      </method>
   1.285 +
   1.286 +      <field name="_scrollAnim"><![CDATA[({
   1.287 +        scrollbox: this,
   1.288 +        requestHandle: 0, /* 0 indicates there is no pending request */
   1.289 +        start: function scrollAnim_start(distance) {
   1.290 +          this.distance = distance;
   1.291 +          this.startPos = this.scrollbox.scrollPosition;
   1.292 +          this.duration = Math.min(1000, Math.round(50 * Math.sqrt(Math.abs(distance))));
   1.293 +          this.startTime = window.mozAnimationStartTime;
   1.294 +
   1.295 +          if (!this.requestHandle)
   1.296 +            this.requestHandle = window.mozRequestAnimationFrame(this);
   1.297 +        },
   1.298 +        stop: function scrollAnim_stop() {
   1.299 +          window.mozCancelAnimationFrame(this.requestHandle);
   1.300 +          this.requestHandle = 0;
   1.301 +        },
   1.302 +        sample: function scrollAnim_handleEvent(timeStamp) {
   1.303 +          const timePassed = timeStamp - this.startTime;
   1.304 +          const pos = timePassed >= this.duration ? 1 :
   1.305 +                      1 - Math.pow(1 - timePassed / this.duration, 4);
   1.306 +
   1.307 +          this.scrollbox.scrollPosition = this.startPos + (this.distance * pos);
   1.308 +
   1.309 +          if (pos == 1)
   1.310 +            this.scrollbox._stopSmoothScroll();
   1.311 +          else
   1.312 +            this.requestHandle = window.mozRequestAnimationFrame(this);
   1.313 +        }
   1.314 +      })]]></field>
   1.315 +
   1.316 +      <method name="scrollByIndex">
   1.317 +        <parameter name="index"/>
   1.318 +        <parameter name="aSmoothScroll"/>
   1.319 +        <body><![CDATA[
   1.320 +          if (index == 0)
   1.321 +            return;
   1.322 +
   1.323 +          // Each scrollByIndex call is expected to scroll the given number of
   1.324 +          // items. If a previous call is still in progress because of smooth
   1.325 +          // scrolling, we need to complete it before starting a new one.
   1.326 +          if (this._scrollTarget) {
   1.327 +            let elements = this._getScrollableElements();
   1.328 +            if (this._scrollTarget != elements[0] &&
   1.329 +                this._scrollTarget != elements[elements.length - 1])
   1.330 +              this.ensureElementIsVisible(this._scrollTarget, false);
   1.331 +          }
   1.332 +
   1.333 +          var rect = this.scrollClientRect;
   1.334 +          var [start, end] = this._startEndProps;
   1.335 +          var x = index > 0 ? rect[end] + 1 : rect[start] - 1;
   1.336 +          var nextElement = this._elementFromPoint(x, index);
   1.337 +          if (!nextElement)
   1.338 +            return;
   1.339 +
   1.340 +          var targetElement;
   1.341 +          if (this._isRTLScrollbox)
   1.342 +            index *= -1;
   1.343 +          while (index < 0 && nextElement) {
   1.344 +            if (this._canScrollToElement(nextElement))
   1.345 +              targetElement = nextElement;
   1.346 +            nextElement = nextElement.previousSibling;
   1.347 +            index++;
   1.348 +          }
   1.349 +          while (index > 0 && nextElement) {
   1.350 +            if (this._canScrollToElement(nextElement))
   1.351 +              targetElement = nextElement;
   1.352 +            nextElement = nextElement.nextSibling;
   1.353 +            index--;
   1.354 +          }
   1.355 +          if (!targetElement)
   1.356 +            return;
   1.357 +
   1.358 +          this.ensureElementIsVisible(targetElement, aSmoothScroll);
   1.359 +        ]]></body>
   1.360 +      </method>
   1.361 +
   1.362 +      <method name="_getScrollableElements">
   1.363 +        <body><![CDATA[
   1.364 +          var nodes = this.childNodes;
   1.365 +          if (nodes.length == 1 &&
   1.366 +              nodes[0].localName == "children" &&
   1.367 +              nodes[0].namespaceURI == "http://www.mozilla.org/xbl") {
   1.368 +            nodes = document.getBindingParent(this).childNodes;
   1.369 +          }
   1.370 +
   1.371 +          return Array.filter(nodes, this._canScrollToElement, this);
   1.372 +        ]]></body>
   1.373 +      </method>
   1.374 +
   1.375 +      <method name="_elementFromPoint">
   1.376 +        <parameter name="aX"/>
   1.377 +        <parameter name="aPhysicalScrollDir"/>
   1.378 +        <body><![CDATA[
   1.379 +          var elements = this._getScrollableElements();
   1.380 +          if (!elements.length)
   1.381 +            return null;
   1.382 +
   1.383 +          if (this._isRTLScrollbox)
   1.384 +            elements.reverse();
   1.385 +
   1.386 +          var [start, end] = this._startEndProps;
   1.387 +          var low = 0;
   1.388 +          var high = elements.length - 1;
   1.389 +
   1.390 +          if (aX < elements[low].getBoundingClientRect()[start] ||
   1.391 +              aX > elements[high].getBoundingClientRect()[end])
   1.392 +            return null;
   1.393 +
   1.394 +          var mid, rect;
   1.395 +          while (low <= high) {
   1.396 +            mid = Math.floor((low + high) / 2);
   1.397 +            rect = elements[mid].getBoundingClientRect();
   1.398 +            if (rect[start] > aX)
   1.399 +              high = mid - 1; 
   1.400 +            else if (rect[end] < aX)
   1.401 +              low = mid + 1;
   1.402 +            else
   1.403 +              return elements[mid];
   1.404 +          }
   1.405 +
   1.406 +          // There's no element at the requested coordinate, but the algorithm
   1.407 +          // from above yields an element next to it, in a random direction.
   1.408 +          // The desired scrolling direction leads to the correct element.
   1.409 +
   1.410 +          if (!aPhysicalScrollDir)
   1.411 +            return null;
   1.412 +
   1.413 +          if (aPhysicalScrollDir < 0 && rect[start] > aX)
   1.414 +            mid = Math.max(mid - 1, 0);
   1.415 +          else if (aPhysicalScrollDir > 0 && rect[end] < aX)
   1.416 +            mid = Math.min(mid + 1, elements.length - 1);
   1.417 +
   1.418 +          return elements[mid];
   1.419 +        ]]></body>
   1.420 +      </method>
   1.421 +
   1.422 +      <method name="_autorepeatbuttonScroll">
   1.423 +        <parameter name="event"/>
   1.424 +        <body><![CDATA[
   1.425 +          var dir = event.originalTarget == this._scrollButtonUp ? -1 : 1;
   1.426 +          if (this._isRTLScrollbox)
   1.427 +            dir *= -1;
   1.428 +
   1.429 +          this.scrollByPixels(this.scrollIncrement * dir);
   1.430 +
   1.431 +          event.stopPropagation();
   1.432 +        ]]></body>
   1.433 +      </method>
   1.434 +
   1.435 +      <method name="scrollByPixels">
   1.436 +        <parameter name="px"/>
   1.437 +        <body><![CDATA[
   1.438 +          this.scrollPosition += px;
   1.439 +        ]]></body>
   1.440 +      </method>
   1.441 +
   1.442 +      <!-- 0: idle
   1.443 +           1: scrolling right
   1.444 +          -1: scrolling left -->
   1.445 +      <field name="_isScrolling">0</field>
   1.446 +      <field name="_prevMouseScrolls">[null, null]</field>
   1.447 +
   1.448 +      <method name="_stopSmoothScroll">
   1.449 +        <body><![CDATA[
   1.450 +          if (this._isScrolling) {
   1.451 +            this._scrollAnim.stop();
   1.452 +            this._isScrolling = 0;
   1.453 +            this._scrollTarget = null;
   1.454 +          }
   1.455 +        ]]></body>
   1.456 +      </method>
   1.457 +
   1.458 +      <method name="_updateScrollButtonsDisabledState">
   1.459 +        <body><![CDATA[
   1.460 +          var disableUpButton = false;
   1.461 +          var disableDownButton = false;
   1.462 +
   1.463 +          if (this.scrollPosition == 0) {
   1.464 +            // In the RTL case, this means the _last_ element in the
   1.465 +            // scrollbox is visible
   1.466 +            if (this._isRTLScrollbox) 
   1.467 +              disableDownButton = true;
   1.468 +            else
   1.469 +              disableUpButton = true;
   1.470 +          }
   1.471 +          else if (this.scrollClientSize + this.scrollPosition == this.scrollSize) {
   1.472 +            // In the RTL case, this means the _first_ element in the
   1.473 +            // scrollbox is visible
   1.474 +            if (this._isRTLScrollbox)
   1.475 +              disableUpButton = true;
   1.476 +            else
   1.477 +              disableDownButton = true;
   1.478 +          }
   1.479 +
   1.480 +          this._scrollButtonUp.disabled = disableUpButton;
   1.481 +          this._scrollButtonDown.disabled = disableDownButton;
   1.482 +        ]]></body>
   1.483 +      </method>
   1.484 +    </implementation>
   1.485 +
   1.486 +    <handlers>
   1.487 +      <handler event="DOMMouseScroll"><![CDATA[
   1.488 +        if (this.orient == "vertical") {
   1.489 +          // prevent horizontal scrolling from scrolling a vertical scrollbox
   1.490 +          if (event.axis == event.HORIZONTAL_AXIS)
   1.491 +            return;
   1.492 +          this.scrollByIndex(event.detail);
   1.493 +        }
   1.494 +        // We allow vertical scrolling to scroll a horizontal scrollbox
   1.495 +        // because many users have a vertical scroll wheel but no
   1.496 +        // horizontal support.
   1.497 +        // Because of this, we need to avoid scrolling chaos on trackpads
   1.498 +        // and mouse wheels that support simultaneous scrolling in both axes.
   1.499 +        // We do this by scrolling only when the last two scroll events were
   1.500 +        // on the same axis as the current scroll event.
   1.501 +        else {
   1.502 +          let isVertical = event.axis == event.VERTICAL_AXIS;
   1.503 +
   1.504 +          if (this._prevMouseScrolls.every(function(prev) prev == isVertical))
   1.505 +            this.scrollByIndex(isVertical && this._isRTLScrollbox ? -event.detail :
   1.506 +                                                                    event.detail);
   1.507 +
   1.508 +          if (this._prevMouseScrolls.length > 1)
   1.509 +            this._prevMouseScrolls.shift();
   1.510 +          this._prevMouseScrolls.push(isVertical);
   1.511 +        }
   1.512 +
   1.513 +        event.stopPropagation();
   1.514 +        event.preventDefault();
   1.515 +      ]]></handler>
   1.516 +
   1.517 +      <handler event="MozMousePixelScroll"><![CDATA[
   1.518 +        event.stopPropagation();
   1.519 +        event.preventDefault();
   1.520 +      ]]></handler>
   1.521 +
   1.522 +      <handler event="underflow" phase="capturing"><![CDATA[
   1.523 +        // filter underflow events which were dispatched on nested scrollboxes
   1.524 +        if (event.target != this)
   1.525 +          return;
   1.526 +
   1.527 +        // Ignore events that doesn't match our orientation.
   1.528 +        // Scrollport event orientation:
   1.529 +        //   0: vertical
   1.530 +        //   1: horizontal
   1.531 +        //   2: both
   1.532 +        if (this.orient == "vertical") {
   1.533 +          if (event.detail == 1)
   1.534 +            return;
   1.535 +        }
   1.536 +        else {    // horizontal scrollbox
   1.537 +          if (event.detail == 0)
   1.538 +            return;
   1.539 +        }
   1.540 +
   1.541 +        this._scrollButtonUp.collapsed = true;
   1.542 +        this._scrollButtonDown.collapsed = true;
   1.543 +        try {
   1.544 +          // See bug 341047 and comments in overflow handler as to why 
   1.545 +          // try..catch is needed here
   1.546 +          let childNodes = this._getScrollableElements();
   1.547 +          if (childNodes && childNodes.length)
   1.548 +            this.ensureElementIsVisible(childNodes[0], false);
   1.549 +        }
   1.550 +        catch(e) {
   1.551 +          this._scrollButtonUp.collapsed = false;
   1.552 +          this._scrollButtonDown.collapsed = false;
   1.553 +        }
   1.554 +      ]]></handler>
   1.555 +
   1.556 +      <handler event="overflow" phase="capturing"><![CDATA[
   1.557 +        // filter underflow events which were dispatched on nested scrollboxes
   1.558 +        if (event.target != this)
   1.559 +          return;
   1.560 +
   1.561 +        // Ignore events that doesn't match our orientation.
   1.562 +        // Scrollport event orientation:
   1.563 +        //   0: vertical
   1.564 +        //   1: horizontal
   1.565 +        //   2: both
   1.566 +        if (this.orient == "vertical") {
   1.567 +          if (event.detail == 1)
   1.568 +            return;
   1.569 +        }
   1.570 +        else {    // horizontal scrollbox
   1.571 +          if (event.detail == 0)
   1.572 +            return;
   1.573 +        }
   1.574 +
   1.575 +        this._scrollButtonUp.collapsed = false;
   1.576 +        this._scrollButtonDown.collapsed = false;
   1.577 +        try {
   1.578 +          // See bug 341047, the overflow event is dispatched when the 
   1.579 +          // scrollbox already is mostly destroyed. This causes some code in
   1.580 +          // _updateScrollButtonsDisabledState() to throw an error. It also
   1.581 +          // means that the scrollbarbuttons were uncollapsed when that should
   1.582 +          // not be happening, because the whole overflow event should not be
   1.583 +          // happening in that case.
   1.584 +          this._updateScrollButtonsDisabledState();
   1.585 +        } 
   1.586 +        catch(e) {
   1.587 +          this._scrollButtonUp.collapsed = true;
   1.588 +          this._scrollButtonDown.collapsed = true;
   1.589 +        }
   1.590 +      ]]></handler>
   1.591 +
   1.592 +      <handler event="scroll" action="this._updateScrollButtonsDisabledState()"/>
   1.593 +    </handlers>
   1.594 +  </binding>
   1.595 +
   1.596 +  <binding id="autorepeatbutton" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
   1.597 +    <content repeat="hover">
   1.598 +      <xul:image class="autorepeatbutton-icon"/>
   1.599 +    </content>
   1.600 +  </binding>
   1.601 +
   1.602 +  <binding id="arrowscrollbox-clicktoscroll" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox">
   1.603 +    <content>
   1.604 +      <xul:toolbarbutton class="scrollbutton-up" collapsed="true"
   1.605 +                         xbl:inherits="orient"
   1.606 +                         anonid="scrollbutton-up"
   1.607 +                         onclick="_distanceScroll(event);"
   1.608 +                         onmousedown="if (event.button == 0) _startScroll(-1);"
   1.609 +                         onmouseup="if (event.button == 0) _stopScroll();"
   1.610 +                         onmouseover="_continueScroll(-1);"
   1.611 +                         onmouseout="_pauseScroll();"/>
   1.612 +      <xul:scrollbox class="arrowscrollbox-scrollbox"
   1.613 +                     anonid="scrollbox"
   1.614 +                     flex="1"
   1.615 +                     xbl:inherits="orient,align,pack,dir">
   1.616 +        <children/>
   1.617 +      </xul:scrollbox>
   1.618 +      <xul:toolbarbutton class="scrollbutton-down" collapsed="true"
   1.619 +                         xbl:inherits="orient"
   1.620 +                         anonid="scrollbutton-down"
   1.621 +                         onclick="_distanceScroll(event);"
   1.622 +                         onmousedown="if (event.button == 0) _startScroll(1);"
   1.623 +                         onmouseup="if (event.button == 0) _stopScroll();"
   1.624 +                         onmouseover="_continueScroll(1);"
   1.625 +                         onmouseout="_pauseScroll();"/>
   1.626 +    </content>
   1.627 +    <implementation implements="nsITimerCallback, nsIDOMEventListener">
   1.628 +      <constructor><![CDATA[
   1.629 +        try {
   1.630 +          this._scrollDelay = this._prefBranch
   1.631 +                                  .getIntPref("toolkit.scrollbox.clickToScroll.scrollDelay");
   1.632 +        }
   1.633 +        catch (ex) {
   1.634 +        }
   1.635 +      ]]></constructor>
   1.636 +
   1.637 +      <destructor><![CDATA[
   1.638 +        // Release timer to avoid reference cycles.
   1.639 +        if (this._scrollTimer) {
   1.640 +          this._scrollTimer.cancel();
   1.641 +          this._scrollTimer = null;
   1.642 +        }
   1.643 +      ]]></destructor>
   1.644 +
   1.645 +      <field name="_scrollIndex">0</field>
   1.646 +      <field name="_scrollDelay">150</field>
   1.647 +
   1.648 +      <method name="notify">
   1.649 +        <parameter name="aTimer"/>
   1.650 +        <body>
   1.651 +        <![CDATA[
   1.652 +          if (!document)
   1.653 +            aTimer.cancel();
   1.654 +
   1.655 +          this.scrollByIndex(this._scrollIndex);
   1.656 +        ]]>
   1.657 +        </body>
   1.658 +      </method>
   1.659 +
   1.660 +      <field name="_arrowScrollAnim"><![CDATA[({
   1.661 +        scrollbox: this,
   1.662 +        requestHandle: 0, /* 0 indicates there is no pending request */
   1.663 +        start: function arrowSmoothScroll_start() {
   1.664 +          this.lastFrameTime = window.mozAnimationStartTime;
   1.665 +          if (!this.requestHandle)
   1.666 +            this.requestHandle = window.mozRequestAnimationFrame(this);
   1.667 +        },
   1.668 +        stop: function arrowSmoothScroll_stop() {
   1.669 +          window.mozCancelAnimationFrame(this.requestHandle);
   1.670 +          this.requestHandle = 0;
   1.671 +        },
   1.672 +        sample: function arrowSmoothScroll_handleEvent(timeStamp) {
   1.673 +          const scrollIndex = this.scrollbox._scrollIndex;
   1.674 +          const timePassed = timeStamp - this.lastFrameTime;
   1.675 +          this.lastFrameTime = timeStamp;
   1.676 +          
   1.677 +          const scrollDelta = 0.5 * timePassed * scrollIndex;
   1.678 +          this.scrollbox.scrollPosition += scrollDelta;
   1.679 +
   1.680 +          this.requestHandle = window.mozRequestAnimationFrame(this);
   1.681 +        }
   1.682 +      })]]></field>
   1.683 +
   1.684 +      <method name="_startScroll">
   1.685 +        <parameter name="index"/>
   1.686 +        <body><![CDATA[
   1.687 +          if (this._isRTLScrollbox)
   1.688 +            index *= -1;
   1.689 +          this._scrollIndex = index;
   1.690 +          this._mousedown = true;
   1.691 +          if (this.smoothScroll) {
   1.692 +            this._arrowScrollAnim.start();
   1.693 +            return;
   1.694 +          }
   1.695 +
   1.696 +          if (!this._scrollTimer)
   1.697 +            this._scrollTimer =
   1.698 +              Components.classes["@mozilla.org/timer;1"]
   1.699 +                        .createInstance(Components.interfaces.nsITimer);
   1.700 +          else
   1.701 +            this._scrollTimer.cancel();
   1.702 +
   1.703 +          this._scrollTimer.initWithCallback(this, this._scrollDelay,
   1.704 +                                             this._scrollTimer.TYPE_REPEATING_SLACK);
   1.705 +          this.notify(this._scrollTimer);
   1.706 +        ]]>
   1.707 +        </body>
   1.708 +      </method>
   1.709 +
   1.710 +      <method name="_stopScroll">
   1.711 +        <body><![CDATA[
   1.712 +          if (this._scrollTimer)
   1.713 +            this._scrollTimer.cancel();
   1.714 +          this._mousedown = false;
   1.715 +          if (!this._scrollIndex || !this.smoothScroll)
   1.716 +            return;
   1.717 +
   1.718 +          this.scrollByIndex(this._scrollIndex);
   1.719 +          this._scrollIndex = 0;
   1.720 +          this._arrowScrollAnim.stop();
   1.721 +        ]]></body>
   1.722 +      </method>
   1.723 +
   1.724 +      <method name="_pauseScroll">
   1.725 +        <body><![CDATA[
   1.726 +          if (this._mousedown) {
   1.727 +            this._stopScroll();
   1.728 +            this._mousedown = true;
   1.729 +            document.addEventListener("mouseup", this, false);
   1.730 +            document.addEventListener("blur", this, true);
   1.731 +          }
   1.732 +        ]]></body>
   1.733 +      </method>
   1.734 +
   1.735 +      <method name="_continueScroll">
   1.736 +        <parameter name="index"/>
   1.737 +        <body><![CDATA[
   1.738 +          if (this._mousedown)
   1.739 +            this._startScroll(index);
   1.740 +        ]]></body>
   1.741 +      </method>
   1.742 +
   1.743 +      <method name="handleEvent">
   1.744 +        <parameter name="aEvent"/>
   1.745 +        <body><![CDATA[
   1.746 +          if (aEvent.type == "mouseup" ||
   1.747 +              aEvent.type == "blur" && aEvent.target == document) {
   1.748 +            this._mousedown = false;
   1.749 +            document.removeEventListener("mouseup", this, false);
   1.750 +            document.removeEventListener("blur", this, true);
   1.751 +          }
   1.752 +        ]]></body>
   1.753 +      </method>
   1.754 +
   1.755 +      <method name="_distanceScroll">
   1.756 +        <parameter name="aEvent"/>
   1.757 +        <body><![CDATA[
   1.758 +          if (aEvent.detail < 2 || aEvent.detail > 3)
   1.759 +            return;
   1.760 +
   1.761 +          var scrollBack = (aEvent.originalTarget == this._scrollButtonUp);
   1.762 +          var scrollLeftOrUp = this._isRTLScrollbox ? !scrollBack : scrollBack;
   1.763 +          var targetElement;
   1.764 +
   1.765 +          if (aEvent.detail == 2) {
   1.766 +            // scroll by the size of the scrollbox
   1.767 +            let [start, end] = this._startEndProps;
   1.768 +            let x;
   1.769 +            if (scrollLeftOrUp)
   1.770 +              x = this.scrollClientRect[start] - this.scrollClientSize;
   1.771 +            else
   1.772 +              x = this.scrollClientRect[end] + this.scrollClientSize;
   1.773 +            targetElement = this._elementFromPoint(x, scrollLeftOrUp ? -1 : 1);
   1.774 +
   1.775 +            // the next partly-hidden element will become fully visible,
   1.776 +            // so don't scroll too far
   1.777 +            if (targetElement)
   1.778 +              targetElement = scrollBack ?
   1.779 +                              targetElement.nextSibling :
   1.780 +                              targetElement.previousSibling;
   1.781 +          }
   1.782 +
   1.783 +          if (!targetElement) {
   1.784 +            // scroll to the first resp. last element
   1.785 +            let elements = this._getScrollableElements();
   1.786 +            targetElement = scrollBack ?
   1.787 +                            elements[0] :
   1.788 +                            elements[elements.length - 1];
   1.789 +          }
   1.790 +
   1.791 +          this.ensureElementIsVisible(targetElement);
   1.792 +        ]]></body>
   1.793 +      </method>
   1.794 +
   1.795 +    </implementation>
   1.796 +  </binding>
   1.797 +</bindings>

mercurial