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>