Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | <?xml version="1.0"?> |
michael@0 | 2 | <!-- This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | - License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> |
michael@0 | 5 | |
michael@0 | 6 | |
michael@0 | 7 | <bindings id="arrowscrollboxBindings" |
michael@0 | 8 | xmlns="http://www.mozilla.org/xbl" |
michael@0 | 9 | xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" |
michael@0 | 10 | xmlns:xbl="http://www.mozilla.org/xbl"> |
michael@0 | 11 | |
michael@0 | 12 | <binding id="scrollbox-base" extends="chrome://global/content/bindings/general.xml#basecontrol"> |
michael@0 | 13 | <resources> |
michael@0 | 14 | <stylesheet src="chrome://global/skin/scrollbox.css"/> |
michael@0 | 15 | </resources> |
michael@0 | 16 | </binding> |
michael@0 | 17 | |
michael@0 | 18 | <binding id="scrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base"> |
michael@0 | 19 | <content> |
michael@0 | 20 | <xul:box class="box-inherit scrollbox-innerbox" xbl:inherits="orient,align,pack,dir" flex="1"> |
michael@0 | 21 | <children/> |
michael@0 | 22 | </xul:box> |
michael@0 | 23 | </content> |
michael@0 | 24 | </binding> |
michael@0 | 25 | |
michael@0 | 26 | <binding id="arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base"> |
michael@0 | 27 | <content> |
michael@0 | 28 | <xul:autorepeatbutton class="autorepeatbutton-up" |
michael@0 | 29 | anonid="scrollbutton-up" |
michael@0 | 30 | collapsed="true" |
michael@0 | 31 | xbl:inherits="orient" |
michael@0 | 32 | oncommand="_autorepeatbuttonScroll(event);"/> |
michael@0 | 33 | <xul:scrollbox class="arrowscrollbox-scrollbox" |
michael@0 | 34 | anonid="scrollbox" |
michael@0 | 35 | flex="1" |
michael@0 | 36 | xbl:inherits="orient,align,pack,dir"> |
michael@0 | 37 | <children/> |
michael@0 | 38 | </xul:scrollbox> |
michael@0 | 39 | <xul:autorepeatbutton class="autorepeatbutton-down" |
michael@0 | 40 | anonid="scrollbutton-down" |
michael@0 | 41 | collapsed="true" |
michael@0 | 42 | xbl:inherits="orient" |
michael@0 | 43 | oncommand="_autorepeatbuttonScroll(event);"/> |
michael@0 | 44 | </content> |
michael@0 | 45 | |
michael@0 | 46 | <implementation> |
michael@0 | 47 | <destructor><![CDATA[ |
michael@0 | 48 | this._stopSmoothScroll(); |
michael@0 | 49 | ]]></destructor> |
michael@0 | 50 | |
michael@0 | 51 | <field name="_scrollbox"> |
michael@0 | 52 | document.getAnonymousElementByAttribute(this, "anonid", "scrollbox"); |
michael@0 | 53 | </field> |
michael@0 | 54 | <field name="_scrollButtonUp"> |
michael@0 | 55 | document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-up"); |
michael@0 | 56 | </field> |
michael@0 | 57 | <field name="_scrollButtonDown"> |
michael@0 | 58 | document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-down"); |
michael@0 | 59 | </field> |
michael@0 | 60 | |
michael@0 | 61 | <field name="__prefBranch">null</field> |
michael@0 | 62 | <property name="_prefBranch" readonly="true"> |
michael@0 | 63 | <getter><![CDATA[ |
michael@0 | 64 | if (this.__prefBranch === null) { |
michael@0 | 65 | this.__prefBranch = Components.classes['@mozilla.org/preferences-service;1'] |
michael@0 | 66 | .getService(Components.interfaces.nsIPrefBranch); |
michael@0 | 67 | } |
michael@0 | 68 | return this.__prefBranch; |
michael@0 | 69 | ]]></getter> |
michael@0 | 70 | </property> |
michael@0 | 71 | |
michael@0 | 72 | <field name="_scrollIncrement">null</field> |
michael@0 | 73 | <property name="scrollIncrement" readonly="true"> |
michael@0 | 74 | <getter><![CDATA[ |
michael@0 | 75 | if (this._scrollIncrement === null) { |
michael@0 | 76 | try { |
michael@0 | 77 | this._scrollIncrement = this._prefBranch |
michael@0 | 78 | .getIntPref("toolkit.scrollbox.scrollIncrement"); |
michael@0 | 79 | } |
michael@0 | 80 | catch (ex) { |
michael@0 | 81 | this._scrollIncrement = 20; |
michael@0 | 82 | } |
michael@0 | 83 | } |
michael@0 | 84 | return this._scrollIncrement; |
michael@0 | 85 | ]]></getter> |
michael@0 | 86 | </property> |
michael@0 | 87 | |
michael@0 | 88 | <field name="_smoothScroll">null</field> |
michael@0 | 89 | <property name="smoothScroll"> |
michael@0 | 90 | <getter><![CDATA[ |
michael@0 | 91 | if (this._smoothScroll === null) { |
michael@0 | 92 | if (this.hasAttribute("smoothscroll")) { |
michael@0 | 93 | this._smoothScroll = (this.getAttribute("smoothscroll") == "true"); |
michael@0 | 94 | } else { |
michael@0 | 95 | try { |
michael@0 | 96 | this._smoothScroll = this._prefBranch |
michael@0 | 97 | .getBoolPref("toolkit.scrollbox.smoothScroll"); |
michael@0 | 98 | } |
michael@0 | 99 | catch (ex) { |
michael@0 | 100 | this._smoothScroll = true; |
michael@0 | 101 | } |
michael@0 | 102 | } |
michael@0 | 103 | } |
michael@0 | 104 | return this._smoothScroll; |
michael@0 | 105 | ]]></getter> |
michael@0 | 106 | <setter><![CDATA[ |
michael@0 | 107 | this._smoothScroll = val; |
michael@0 | 108 | return val; |
michael@0 | 109 | ]]></setter> |
michael@0 | 110 | </property> |
michael@0 | 111 | |
michael@0 | 112 | <field name="_scrollBoxObject">null</field> |
michael@0 | 113 | <property name="scrollBoxObject" readonly="true"> |
michael@0 | 114 | <getter><![CDATA[ |
michael@0 | 115 | if (!this._scrollBoxObject) { |
michael@0 | 116 | this._scrollBoxObject = |
michael@0 | 117 | this._scrollbox.boxObject |
michael@0 | 118 | .QueryInterface(Components.interfaces.nsIScrollBoxObject); |
michael@0 | 119 | } |
michael@0 | 120 | return this._scrollBoxObject; |
michael@0 | 121 | ]]></getter> |
michael@0 | 122 | </property> |
michael@0 | 123 | |
michael@0 | 124 | <property name="scrollClientRect" readonly="true"> |
michael@0 | 125 | <getter><![CDATA[ |
michael@0 | 126 | return this._scrollbox.getBoundingClientRect(); |
michael@0 | 127 | ]]></getter> |
michael@0 | 128 | </property> |
michael@0 | 129 | |
michael@0 | 130 | <property name="scrollClientSize" readonly="true"> |
michael@0 | 131 | <getter><![CDATA[ |
michael@0 | 132 | return this.orient == "vertical" ? |
michael@0 | 133 | this._scrollbox.clientHeight : |
michael@0 | 134 | this._scrollbox.clientWidth; |
michael@0 | 135 | ]]></getter> |
michael@0 | 136 | </property> |
michael@0 | 137 | |
michael@0 | 138 | <property name="scrollSize" readonly="true"> |
michael@0 | 139 | <getter><![CDATA[ |
michael@0 | 140 | return this.orient == "vertical" ? |
michael@0 | 141 | this._scrollbox.scrollHeight : |
michael@0 | 142 | this._scrollbox.scrollWidth; |
michael@0 | 143 | ]]></getter> |
michael@0 | 144 | </property> |
michael@0 | 145 | <property name="scrollPaddingRect" readonly="true"> |
michael@0 | 146 | <getter><![CDATA[ |
michael@0 | 147 | // This assumes that this._scrollbox doesn't have any border. |
michael@0 | 148 | var outerRect = this.scrollClientRect; |
michael@0 | 149 | var innerRect = {}; |
michael@0 | 150 | innerRect.left = outerRect.left - this._scrollbox.scrollLeft; |
michael@0 | 151 | innerRect.top = outerRect.top - this._scrollbox.scrollTop; |
michael@0 | 152 | innerRect.right = innerRect.left + this._scrollbox.scrollWidth; |
michael@0 | 153 | innerRect.bottom = innerRect.top + this._scrollbox.scrollHeight; |
michael@0 | 154 | return innerRect; |
michael@0 | 155 | ]]></getter> |
michael@0 | 156 | </property> |
michael@0 | 157 | <property name="scrollboxPaddingStart" readonly="true"> |
michael@0 | 158 | <getter><![CDATA[ |
michael@0 | 159 | var ltr = (window.getComputedStyle(this, null).direction == "ltr"); |
michael@0 | 160 | var paddingStartName = ltr ? "padding-left" : "padding-right"; |
michael@0 | 161 | var scrollboxStyle = window.getComputedStyle(this._scrollbox, null); |
michael@0 | 162 | return parseFloat(scrollboxStyle.getPropertyValue(paddingStartName)); |
michael@0 | 163 | ]]></getter> |
michael@0 | 164 | </property> |
michael@0 | 165 | <property name="scrollPosition"> |
michael@0 | 166 | <getter><![CDATA[ |
michael@0 | 167 | return this.orient == "vertical" ? |
michael@0 | 168 | this._scrollbox.scrollTop : |
michael@0 | 169 | this._scrollbox.scrollLeft; |
michael@0 | 170 | ]]></getter> |
michael@0 | 171 | <setter><![CDATA[ |
michael@0 | 172 | if (this.orient == "vertical") |
michael@0 | 173 | this._scrollbox.scrollTop = val; |
michael@0 | 174 | else |
michael@0 | 175 | this._scrollbox.scrollLeft = val; |
michael@0 | 176 | return val; |
michael@0 | 177 | ]]></setter> |
michael@0 | 178 | </property> |
michael@0 | 179 | |
michael@0 | 180 | <property name="_startEndProps" readonly="true"> |
michael@0 | 181 | <getter><![CDATA[ |
michael@0 | 182 | return this.orient == "vertical" ? |
michael@0 | 183 | ["top", "bottom"] : ["left", "right"]; |
michael@0 | 184 | ]]></getter> |
michael@0 | 185 | </property> |
michael@0 | 186 | |
michael@0 | 187 | <field name="_isRTLScrollbox"><![CDATA[ |
michael@0 | 188 | this.orient != "vertical" && |
michael@0 | 189 | document.defaultView.getComputedStyle(this._scrollbox, "").direction == "rtl"; |
michael@0 | 190 | ]]></field> |
michael@0 | 191 | |
michael@0 | 192 | <field name="_scrollTarget">null</field> |
michael@0 | 193 | |
michael@0 | 194 | <method name="_canScrollToElement"> |
michael@0 | 195 | <parameter name="element"/> |
michael@0 | 196 | <body><![CDATA[ |
michael@0 | 197 | return window.getComputedStyle(element).display != "none"; |
michael@0 | 198 | ]]></body> |
michael@0 | 199 | </method> |
michael@0 | 200 | |
michael@0 | 201 | <method name="ensureElementIsVisible"> |
michael@0 | 202 | <parameter name="element"/> |
michael@0 | 203 | <parameter name="aSmoothScroll"/> |
michael@0 | 204 | <body><![CDATA[ |
michael@0 | 205 | if (!this._canScrollToElement(element)) |
michael@0 | 206 | return; |
michael@0 | 207 | |
michael@0 | 208 | var vertical = this.orient == "vertical"; |
michael@0 | 209 | var rect = this.scrollClientRect; |
michael@0 | 210 | var containerStart = vertical ? rect.top : rect.left; |
michael@0 | 211 | var containerEnd = vertical ? rect.bottom : rect.right; |
michael@0 | 212 | rect = element.getBoundingClientRect(); |
michael@0 | 213 | var elementStart = vertical ? rect.top : rect.left; |
michael@0 | 214 | var elementEnd = vertical ? rect.bottom : rect.right; |
michael@0 | 215 | |
michael@0 | 216 | var scrollPaddingRect = this.scrollPaddingRect; |
michael@0 | 217 | let style = window.getComputedStyle(this._scrollbox, null); |
michael@0 | 218 | var scrollContentRect = { |
michael@0 | 219 | left: scrollPaddingRect.left + parseFloat(style.paddingLeft), |
michael@0 | 220 | top: scrollPaddingRect.top + parseFloat(style.paddingTop), |
michael@0 | 221 | right: scrollPaddingRect.right - parseFloat(style.paddingRight), |
michael@0 | 222 | bottom: scrollPaddingRect.bottom - parseFloat(style.paddingBottom) |
michael@0 | 223 | }; |
michael@0 | 224 | |
michael@0 | 225 | // Provide an entry point for derived bindings to adjust these values. |
michael@0 | 226 | if (this._adjustElementStartAndEnd) { |
michael@0 | 227 | [elementStart, elementEnd] = |
michael@0 | 228 | this._adjustElementStartAndEnd(element, elementStart, elementEnd); |
michael@0 | 229 | } |
michael@0 | 230 | |
michael@0 | 231 | if (elementStart <= (vertical ? scrollContentRect.top : scrollContentRect.left)) { |
michael@0 | 232 | elementStart = vertical ? scrollPaddingRect.top : scrollPaddingRect.left; |
michael@0 | 233 | } |
michael@0 | 234 | if (elementEnd >= (vertical ? scrollContentRect.bottom : scrollContentRect.right)) { |
michael@0 | 235 | elementEnd = vertical ? scrollPaddingRect.bottom : scrollPaddingRect.right; |
michael@0 | 236 | } |
michael@0 | 237 | |
michael@0 | 238 | var amountToScroll; |
michael@0 | 239 | |
michael@0 | 240 | if (elementStart < containerStart) { |
michael@0 | 241 | amountToScroll = elementStart - containerStart; |
michael@0 | 242 | } else if (containerEnd < elementEnd) { |
michael@0 | 243 | amountToScroll = elementEnd - containerEnd; |
michael@0 | 244 | } else if (this._isScrolling) { |
michael@0 | 245 | // decelerate if a currently-visible element is selected during the scroll |
michael@0 | 246 | const STOP_DISTANCE = 15; |
michael@0 | 247 | if (this._isScrolling == -1 && elementStart - STOP_DISTANCE < containerStart) |
michael@0 | 248 | amountToScroll = elementStart - containerStart; |
michael@0 | 249 | else if (this._isScrolling == 1 && containerEnd - STOP_DISTANCE < elementEnd) |
michael@0 | 250 | amountToScroll = elementEnd - containerEnd; |
michael@0 | 251 | else |
michael@0 | 252 | amountToScroll = this._isScrolling * STOP_DISTANCE; |
michael@0 | 253 | } else { |
michael@0 | 254 | return; |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | this._stopSmoothScroll(); |
michael@0 | 258 | |
michael@0 | 259 | if (aSmoothScroll != false && this.smoothScroll) { |
michael@0 | 260 | this._smoothScrollByPixels(amountToScroll, element); |
michael@0 | 261 | } else { |
michael@0 | 262 | this.scrollByPixels(amountToScroll); |
michael@0 | 263 | } |
michael@0 | 264 | ]]></body> |
michael@0 | 265 | </method> |
michael@0 | 266 | |
michael@0 | 267 | <method name="_smoothScrollByPixels"> |
michael@0 | 268 | <parameter name="amountToScroll"/> |
michael@0 | 269 | <parameter name="element"/><!-- optional --> |
michael@0 | 270 | <body><![CDATA[ |
michael@0 | 271 | this._stopSmoothScroll(); |
michael@0 | 272 | if (amountToScroll == 0) |
michael@0 | 273 | return; |
michael@0 | 274 | |
michael@0 | 275 | this._scrollTarget = element; |
michael@0 | 276 | // Positive amountToScroll makes us scroll right (elements fly left), negative scrolls left. |
michael@0 | 277 | this._isScrolling = amountToScroll < 0 ? -1 : 1; |
michael@0 | 278 | |
michael@0 | 279 | this._scrollAnim.start(amountToScroll); |
michael@0 | 280 | ]]></body> |
michael@0 | 281 | </method> |
michael@0 | 282 | |
michael@0 | 283 | <field name="_scrollAnim"><![CDATA[({ |
michael@0 | 284 | scrollbox: this, |
michael@0 | 285 | requestHandle: 0, /* 0 indicates there is no pending request */ |
michael@0 | 286 | start: function scrollAnim_start(distance) { |
michael@0 | 287 | this.distance = distance; |
michael@0 | 288 | this.startPos = this.scrollbox.scrollPosition; |
michael@0 | 289 | this.duration = Math.min(1000, Math.round(50 * Math.sqrt(Math.abs(distance)))); |
michael@0 | 290 | this.startTime = window.mozAnimationStartTime; |
michael@0 | 291 | |
michael@0 | 292 | if (!this.requestHandle) |
michael@0 | 293 | this.requestHandle = window.mozRequestAnimationFrame(this); |
michael@0 | 294 | }, |
michael@0 | 295 | stop: function scrollAnim_stop() { |
michael@0 | 296 | window.mozCancelAnimationFrame(this.requestHandle); |
michael@0 | 297 | this.requestHandle = 0; |
michael@0 | 298 | }, |
michael@0 | 299 | sample: function scrollAnim_handleEvent(timeStamp) { |
michael@0 | 300 | const timePassed = timeStamp - this.startTime; |
michael@0 | 301 | const pos = timePassed >= this.duration ? 1 : |
michael@0 | 302 | 1 - Math.pow(1 - timePassed / this.duration, 4); |
michael@0 | 303 | |
michael@0 | 304 | this.scrollbox.scrollPosition = this.startPos + (this.distance * pos); |
michael@0 | 305 | |
michael@0 | 306 | if (pos == 1) |
michael@0 | 307 | this.scrollbox._stopSmoothScroll(); |
michael@0 | 308 | else |
michael@0 | 309 | this.requestHandle = window.mozRequestAnimationFrame(this); |
michael@0 | 310 | } |
michael@0 | 311 | })]]></field> |
michael@0 | 312 | |
michael@0 | 313 | <method name="scrollByIndex"> |
michael@0 | 314 | <parameter name="index"/> |
michael@0 | 315 | <parameter name="aSmoothScroll"/> |
michael@0 | 316 | <body><![CDATA[ |
michael@0 | 317 | if (index == 0) |
michael@0 | 318 | return; |
michael@0 | 319 | |
michael@0 | 320 | // Each scrollByIndex call is expected to scroll the given number of |
michael@0 | 321 | // items. If a previous call is still in progress because of smooth |
michael@0 | 322 | // scrolling, we need to complete it before starting a new one. |
michael@0 | 323 | if (this._scrollTarget) { |
michael@0 | 324 | let elements = this._getScrollableElements(); |
michael@0 | 325 | if (this._scrollTarget != elements[0] && |
michael@0 | 326 | this._scrollTarget != elements[elements.length - 1]) |
michael@0 | 327 | this.ensureElementIsVisible(this._scrollTarget, false); |
michael@0 | 328 | } |
michael@0 | 329 | |
michael@0 | 330 | var rect = this.scrollClientRect; |
michael@0 | 331 | var [start, end] = this._startEndProps; |
michael@0 | 332 | var x = index > 0 ? rect[end] + 1 : rect[start] - 1; |
michael@0 | 333 | var nextElement = this._elementFromPoint(x, index); |
michael@0 | 334 | if (!nextElement) |
michael@0 | 335 | return; |
michael@0 | 336 | |
michael@0 | 337 | var targetElement; |
michael@0 | 338 | if (this._isRTLScrollbox) |
michael@0 | 339 | index *= -1; |
michael@0 | 340 | while (index < 0 && nextElement) { |
michael@0 | 341 | if (this._canScrollToElement(nextElement)) |
michael@0 | 342 | targetElement = nextElement; |
michael@0 | 343 | nextElement = nextElement.previousSibling; |
michael@0 | 344 | index++; |
michael@0 | 345 | } |
michael@0 | 346 | while (index > 0 && nextElement) { |
michael@0 | 347 | if (this._canScrollToElement(nextElement)) |
michael@0 | 348 | targetElement = nextElement; |
michael@0 | 349 | nextElement = nextElement.nextSibling; |
michael@0 | 350 | index--; |
michael@0 | 351 | } |
michael@0 | 352 | if (!targetElement) |
michael@0 | 353 | return; |
michael@0 | 354 | |
michael@0 | 355 | this.ensureElementIsVisible(targetElement, aSmoothScroll); |
michael@0 | 356 | ]]></body> |
michael@0 | 357 | </method> |
michael@0 | 358 | |
michael@0 | 359 | <method name="_getScrollableElements"> |
michael@0 | 360 | <body><![CDATA[ |
michael@0 | 361 | var nodes = this.childNodes; |
michael@0 | 362 | if (nodes.length == 1 && |
michael@0 | 363 | nodes[0].localName == "children" && |
michael@0 | 364 | nodes[0].namespaceURI == "http://www.mozilla.org/xbl") { |
michael@0 | 365 | nodes = document.getBindingParent(this).childNodes; |
michael@0 | 366 | } |
michael@0 | 367 | |
michael@0 | 368 | return Array.filter(nodes, this._canScrollToElement, this); |
michael@0 | 369 | ]]></body> |
michael@0 | 370 | </method> |
michael@0 | 371 | |
michael@0 | 372 | <method name="_elementFromPoint"> |
michael@0 | 373 | <parameter name="aX"/> |
michael@0 | 374 | <parameter name="aPhysicalScrollDir"/> |
michael@0 | 375 | <body><![CDATA[ |
michael@0 | 376 | var elements = this._getScrollableElements(); |
michael@0 | 377 | if (!elements.length) |
michael@0 | 378 | return null; |
michael@0 | 379 | |
michael@0 | 380 | if (this._isRTLScrollbox) |
michael@0 | 381 | elements.reverse(); |
michael@0 | 382 | |
michael@0 | 383 | var [start, end] = this._startEndProps; |
michael@0 | 384 | var low = 0; |
michael@0 | 385 | var high = elements.length - 1; |
michael@0 | 386 | |
michael@0 | 387 | if (aX < elements[low].getBoundingClientRect()[start] || |
michael@0 | 388 | aX > elements[high].getBoundingClientRect()[end]) |
michael@0 | 389 | return null; |
michael@0 | 390 | |
michael@0 | 391 | var mid, rect; |
michael@0 | 392 | while (low <= high) { |
michael@0 | 393 | mid = Math.floor((low + high) / 2); |
michael@0 | 394 | rect = elements[mid].getBoundingClientRect(); |
michael@0 | 395 | if (rect[start] > aX) |
michael@0 | 396 | high = mid - 1; |
michael@0 | 397 | else if (rect[end] < aX) |
michael@0 | 398 | low = mid + 1; |
michael@0 | 399 | else |
michael@0 | 400 | return elements[mid]; |
michael@0 | 401 | } |
michael@0 | 402 | |
michael@0 | 403 | // There's no element at the requested coordinate, but the algorithm |
michael@0 | 404 | // from above yields an element next to it, in a random direction. |
michael@0 | 405 | // The desired scrolling direction leads to the correct element. |
michael@0 | 406 | |
michael@0 | 407 | if (!aPhysicalScrollDir) |
michael@0 | 408 | return null; |
michael@0 | 409 | |
michael@0 | 410 | if (aPhysicalScrollDir < 0 && rect[start] > aX) |
michael@0 | 411 | mid = Math.max(mid - 1, 0); |
michael@0 | 412 | else if (aPhysicalScrollDir > 0 && rect[end] < aX) |
michael@0 | 413 | mid = Math.min(mid + 1, elements.length - 1); |
michael@0 | 414 | |
michael@0 | 415 | return elements[mid]; |
michael@0 | 416 | ]]></body> |
michael@0 | 417 | </method> |
michael@0 | 418 | |
michael@0 | 419 | <method name="_autorepeatbuttonScroll"> |
michael@0 | 420 | <parameter name="event"/> |
michael@0 | 421 | <body><![CDATA[ |
michael@0 | 422 | var dir = event.originalTarget == this._scrollButtonUp ? -1 : 1; |
michael@0 | 423 | if (this._isRTLScrollbox) |
michael@0 | 424 | dir *= -1; |
michael@0 | 425 | |
michael@0 | 426 | this.scrollByPixels(this.scrollIncrement * dir); |
michael@0 | 427 | |
michael@0 | 428 | event.stopPropagation(); |
michael@0 | 429 | ]]></body> |
michael@0 | 430 | </method> |
michael@0 | 431 | |
michael@0 | 432 | <method name="scrollByPixels"> |
michael@0 | 433 | <parameter name="px"/> |
michael@0 | 434 | <body><![CDATA[ |
michael@0 | 435 | this.scrollPosition += px; |
michael@0 | 436 | ]]></body> |
michael@0 | 437 | </method> |
michael@0 | 438 | |
michael@0 | 439 | <!-- 0: idle |
michael@0 | 440 | 1: scrolling right |
michael@0 | 441 | -1: scrolling left --> |
michael@0 | 442 | <field name="_isScrolling">0</field> |
michael@0 | 443 | <field name="_prevMouseScrolls">[null, null]</field> |
michael@0 | 444 | |
michael@0 | 445 | <method name="_stopSmoothScroll"> |
michael@0 | 446 | <body><![CDATA[ |
michael@0 | 447 | if (this._isScrolling) { |
michael@0 | 448 | this._scrollAnim.stop(); |
michael@0 | 449 | this._isScrolling = 0; |
michael@0 | 450 | this._scrollTarget = null; |
michael@0 | 451 | } |
michael@0 | 452 | ]]></body> |
michael@0 | 453 | </method> |
michael@0 | 454 | |
michael@0 | 455 | <method name="_updateScrollButtonsDisabledState"> |
michael@0 | 456 | <body><![CDATA[ |
michael@0 | 457 | var disableUpButton = false; |
michael@0 | 458 | var disableDownButton = false; |
michael@0 | 459 | |
michael@0 | 460 | if (this.scrollPosition == 0) { |
michael@0 | 461 | // In the RTL case, this means the _last_ element in the |
michael@0 | 462 | // scrollbox is visible |
michael@0 | 463 | if (this._isRTLScrollbox) |
michael@0 | 464 | disableDownButton = true; |
michael@0 | 465 | else |
michael@0 | 466 | disableUpButton = true; |
michael@0 | 467 | } |
michael@0 | 468 | else if (this.scrollClientSize + this.scrollPosition == this.scrollSize) { |
michael@0 | 469 | // In the RTL case, this means the _first_ element in the |
michael@0 | 470 | // scrollbox is visible |
michael@0 | 471 | if (this._isRTLScrollbox) |
michael@0 | 472 | disableUpButton = true; |
michael@0 | 473 | else |
michael@0 | 474 | disableDownButton = true; |
michael@0 | 475 | } |
michael@0 | 476 | |
michael@0 | 477 | this._scrollButtonUp.disabled = disableUpButton; |
michael@0 | 478 | this._scrollButtonDown.disabled = disableDownButton; |
michael@0 | 479 | ]]></body> |
michael@0 | 480 | </method> |
michael@0 | 481 | </implementation> |
michael@0 | 482 | |
michael@0 | 483 | <handlers> |
michael@0 | 484 | <handler event="DOMMouseScroll"><![CDATA[ |
michael@0 | 485 | if (this.orient == "vertical") { |
michael@0 | 486 | // prevent horizontal scrolling from scrolling a vertical scrollbox |
michael@0 | 487 | if (event.axis == event.HORIZONTAL_AXIS) |
michael@0 | 488 | return; |
michael@0 | 489 | this.scrollByIndex(event.detail); |
michael@0 | 490 | } |
michael@0 | 491 | // We allow vertical scrolling to scroll a horizontal scrollbox |
michael@0 | 492 | // because many users have a vertical scroll wheel but no |
michael@0 | 493 | // horizontal support. |
michael@0 | 494 | // Because of this, we need to avoid scrolling chaos on trackpads |
michael@0 | 495 | // and mouse wheels that support simultaneous scrolling in both axes. |
michael@0 | 496 | // We do this by scrolling only when the last two scroll events were |
michael@0 | 497 | // on the same axis as the current scroll event. |
michael@0 | 498 | else { |
michael@0 | 499 | let isVertical = event.axis == event.VERTICAL_AXIS; |
michael@0 | 500 | |
michael@0 | 501 | if (this._prevMouseScrolls.every(function(prev) prev == isVertical)) |
michael@0 | 502 | this.scrollByIndex(isVertical && this._isRTLScrollbox ? -event.detail : |
michael@0 | 503 | event.detail); |
michael@0 | 504 | |
michael@0 | 505 | if (this._prevMouseScrolls.length > 1) |
michael@0 | 506 | this._prevMouseScrolls.shift(); |
michael@0 | 507 | this._prevMouseScrolls.push(isVertical); |
michael@0 | 508 | } |
michael@0 | 509 | |
michael@0 | 510 | event.stopPropagation(); |
michael@0 | 511 | event.preventDefault(); |
michael@0 | 512 | ]]></handler> |
michael@0 | 513 | |
michael@0 | 514 | <handler event="MozMousePixelScroll"><![CDATA[ |
michael@0 | 515 | event.stopPropagation(); |
michael@0 | 516 | event.preventDefault(); |
michael@0 | 517 | ]]></handler> |
michael@0 | 518 | |
michael@0 | 519 | <handler event="underflow" phase="capturing"><![CDATA[ |
michael@0 | 520 | // filter underflow events which were dispatched on nested scrollboxes |
michael@0 | 521 | if (event.target != this) |
michael@0 | 522 | return; |
michael@0 | 523 | |
michael@0 | 524 | // Ignore events that doesn't match our orientation. |
michael@0 | 525 | // Scrollport event orientation: |
michael@0 | 526 | // 0: vertical |
michael@0 | 527 | // 1: horizontal |
michael@0 | 528 | // 2: both |
michael@0 | 529 | if (this.orient == "vertical") { |
michael@0 | 530 | if (event.detail == 1) |
michael@0 | 531 | return; |
michael@0 | 532 | } |
michael@0 | 533 | else { // horizontal scrollbox |
michael@0 | 534 | if (event.detail == 0) |
michael@0 | 535 | return; |
michael@0 | 536 | } |
michael@0 | 537 | |
michael@0 | 538 | this._scrollButtonUp.collapsed = true; |
michael@0 | 539 | this._scrollButtonDown.collapsed = true; |
michael@0 | 540 | try { |
michael@0 | 541 | // See bug 341047 and comments in overflow handler as to why |
michael@0 | 542 | // try..catch is needed here |
michael@0 | 543 | let childNodes = this._getScrollableElements(); |
michael@0 | 544 | if (childNodes && childNodes.length) |
michael@0 | 545 | this.ensureElementIsVisible(childNodes[0], false); |
michael@0 | 546 | } |
michael@0 | 547 | catch(e) { |
michael@0 | 548 | this._scrollButtonUp.collapsed = false; |
michael@0 | 549 | this._scrollButtonDown.collapsed = false; |
michael@0 | 550 | } |
michael@0 | 551 | ]]></handler> |
michael@0 | 552 | |
michael@0 | 553 | <handler event="overflow" phase="capturing"><![CDATA[ |
michael@0 | 554 | // filter underflow events which were dispatched on nested scrollboxes |
michael@0 | 555 | if (event.target != this) |
michael@0 | 556 | return; |
michael@0 | 557 | |
michael@0 | 558 | // Ignore events that doesn't match our orientation. |
michael@0 | 559 | // Scrollport event orientation: |
michael@0 | 560 | // 0: vertical |
michael@0 | 561 | // 1: horizontal |
michael@0 | 562 | // 2: both |
michael@0 | 563 | if (this.orient == "vertical") { |
michael@0 | 564 | if (event.detail == 1) |
michael@0 | 565 | return; |
michael@0 | 566 | } |
michael@0 | 567 | else { // horizontal scrollbox |
michael@0 | 568 | if (event.detail == 0) |
michael@0 | 569 | return; |
michael@0 | 570 | } |
michael@0 | 571 | |
michael@0 | 572 | this._scrollButtonUp.collapsed = false; |
michael@0 | 573 | this._scrollButtonDown.collapsed = false; |
michael@0 | 574 | try { |
michael@0 | 575 | // See bug 341047, the overflow event is dispatched when the |
michael@0 | 576 | // scrollbox already is mostly destroyed. This causes some code in |
michael@0 | 577 | // _updateScrollButtonsDisabledState() to throw an error. It also |
michael@0 | 578 | // means that the scrollbarbuttons were uncollapsed when that should |
michael@0 | 579 | // not be happening, because the whole overflow event should not be |
michael@0 | 580 | // happening in that case. |
michael@0 | 581 | this._updateScrollButtonsDisabledState(); |
michael@0 | 582 | } |
michael@0 | 583 | catch(e) { |
michael@0 | 584 | this._scrollButtonUp.collapsed = true; |
michael@0 | 585 | this._scrollButtonDown.collapsed = true; |
michael@0 | 586 | } |
michael@0 | 587 | ]]></handler> |
michael@0 | 588 | |
michael@0 | 589 | <handler event="scroll" action="this._updateScrollButtonsDisabledState()"/> |
michael@0 | 590 | </handlers> |
michael@0 | 591 | </binding> |
michael@0 | 592 | |
michael@0 | 593 | <binding id="autorepeatbutton" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base"> |
michael@0 | 594 | <content repeat="hover"> |
michael@0 | 595 | <xul:image class="autorepeatbutton-icon"/> |
michael@0 | 596 | </content> |
michael@0 | 597 | </binding> |
michael@0 | 598 | |
michael@0 | 599 | <binding id="arrowscrollbox-clicktoscroll" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox"> |
michael@0 | 600 | <content> |
michael@0 | 601 | <xul:toolbarbutton class="scrollbutton-up" collapsed="true" |
michael@0 | 602 | xbl:inherits="orient" |
michael@0 | 603 | anonid="scrollbutton-up" |
michael@0 | 604 | onclick="_distanceScroll(event);" |
michael@0 | 605 | onmousedown="if (event.button == 0) _startScroll(-1);" |
michael@0 | 606 | onmouseup="if (event.button == 0) _stopScroll();" |
michael@0 | 607 | onmouseover="_continueScroll(-1);" |
michael@0 | 608 | onmouseout="_pauseScroll();"/> |
michael@0 | 609 | <xul:scrollbox class="arrowscrollbox-scrollbox" |
michael@0 | 610 | anonid="scrollbox" |
michael@0 | 611 | flex="1" |
michael@0 | 612 | xbl:inherits="orient,align,pack,dir"> |
michael@0 | 613 | <children/> |
michael@0 | 614 | </xul:scrollbox> |
michael@0 | 615 | <xul:toolbarbutton class="scrollbutton-down" collapsed="true" |
michael@0 | 616 | xbl:inherits="orient" |
michael@0 | 617 | anonid="scrollbutton-down" |
michael@0 | 618 | onclick="_distanceScroll(event);" |
michael@0 | 619 | onmousedown="if (event.button == 0) _startScroll(1);" |
michael@0 | 620 | onmouseup="if (event.button == 0) _stopScroll();" |
michael@0 | 621 | onmouseover="_continueScroll(1);" |
michael@0 | 622 | onmouseout="_pauseScroll();"/> |
michael@0 | 623 | </content> |
michael@0 | 624 | <implementation implements="nsITimerCallback, nsIDOMEventListener"> |
michael@0 | 625 | <constructor><![CDATA[ |
michael@0 | 626 | try { |
michael@0 | 627 | this._scrollDelay = this._prefBranch |
michael@0 | 628 | .getIntPref("toolkit.scrollbox.clickToScroll.scrollDelay"); |
michael@0 | 629 | } |
michael@0 | 630 | catch (ex) { |
michael@0 | 631 | } |
michael@0 | 632 | ]]></constructor> |
michael@0 | 633 | |
michael@0 | 634 | <destructor><![CDATA[ |
michael@0 | 635 | // Release timer to avoid reference cycles. |
michael@0 | 636 | if (this._scrollTimer) { |
michael@0 | 637 | this._scrollTimer.cancel(); |
michael@0 | 638 | this._scrollTimer = null; |
michael@0 | 639 | } |
michael@0 | 640 | ]]></destructor> |
michael@0 | 641 | |
michael@0 | 642 | <field name="_scrollIndex">0</field> |
michael@0 | 643 | <field name="_scrollDelay">150</field> |
michael@0 | 644 | |
michael@0 | 645 | <method name="notify"> |
michael@0 | 646 | <parameter name="aTimer"/> |
michael@0 | 647 | <body> |
michael@0 | 648 | <![CDATA[ |
michael@0 | 649 | if (!document) |
michael@0 | 650 | aTimer.cancel(); |
michael@0 | 651 | |
michael@0 | 652 | this.scrollByIndex(this._scrollIndex); |
michael@0 | 653 | ]]> |
michael@0 | 654 | </body> |
michael@0 | 655 | </method> |
michael@0 | 656 | |
michael@0 | 657 | <field name="_arrowScrollAnim"><![CDATA[({ |
michael@0 | 658 | scrollbox: this, |
michael@0 | 659 | requestHandle: 0, /* 0 indicates there is no pending request */ |
michael@0 | 660 | start: function arrowSmoothScroll_start() { |
michael@0 | 661 | this.lastFrameTime = window.mozAnimationStartTime; |
michael@0 | 662 | if (!this.requestHandle) |
michael@0 | 663 | this.requestHandle = window.mozRequestAnimationFrame(this); |
michael@0 | 664 | }, |
michael@0 | 665 | stop: function arrowSmoothScroll_stop() { |
michael@0 | 666 | window.mozCancelAnimationFrame(this.requestHandle); |
michael@0 | 667 | this.requestHandle = 0; |
michael@0 | 668 | }, |
michael@0 | 669 | sample: function arrowSmoothScroll_handleEvent(timeStamp) { |
michael@0 | 670 | const scrollIndex = this.scrollbox._scrollIndex; |
michael@0 | 671 | const timePassed = timeStamp - this.lastFrameTime; |
michael@0 | 672 | this.lastFrameTime = timeStamp; |
michael@0 | 673 | |
michael@0 | 674 | const scrollDelta = 0.5 * timePassed * scrollIndex; |
michael@0 | 675 | this.scrollbox.scrollPosition += scrollDelta; |
michael@0 | 676 | |
michael@0 | 677 | this.requestHandle = window.mozRequestAnimationFrame(this); |
michael@0 | 678 | } |
michael@0 | 679 | })]]></field> |
michael@0 | 680 | |
michael@0 | 681 | <method name="_startScroll"> |
michael@0 | 682 | <parameter name="index"/> |
michael@0 | 683 | <body><![CDATA[ |
michael@0 | 684 | if (this._isRTLScrollbox) |
michael@0 | 685 | index *= -1; |
michael@0 | 686 | this._scrollIndex = index; |
michael@0 | 687 | this._mousedown = true; |
michael@0 | 688 | if (this.smoothScroll) { |
michael@0 | 689 | this._arrowScrollAnim.start(); |
michael@0 | 690 | return; |
michael@0 | 691 | } |
michael@0 | 692 | |
michael@0 | 693 | if (!this._scrollTimer) |
michael@0 | 694 | this._scrollTimer = |
michael@0 | 695 | Components.classes["@mozilla.org/timer;1"] |
michael@0 | 696 | .createInstance(Components.interfaces.nsITimer); |
michael@0 | 697 | else |
michael@0 | 698 | this._scrollTimer.cancel(); |
michael@0 | 699 | |
michael@0 | 700 | this._scrollTimer.initWithCallback(this, this._scrollDelay, |
michael@0 | 701 | this._scrollTimer.TYPE_REPEATING_SLACK); |
michael@0 | 702 | this.notify(this._scrollTimer); |
michael@0 | 703 | ]]> |
michael@0 | 704 | </body> |
michael@0 | 705 | </method> |
michael@0 | 706 | |
michael@0 | 707 | <method name="_stopScroll"> |
michael@0 | 708 | <body><![CDATA[ |
michael@0 | 709 | if (this._scrollTimer) |
michael@0 | 710 | this._scrollTimer.cancel(); |
michael@0 | 711 | this._mousedown = false; |
michael@0 | 712 | if (!this._scrollIndex || !this.smoothScroll) |
michael@0 | 713 | return; |
michael@0 | 714 | |
michael@0 | 715 | this.scrollByIndex(this._scrollIndex); |
michael@0 | 716 | this._scrollIndex = 0; |
michael@0 | 717 | this._arrowScrollAnim.stop(); |
michael@0 | 718 | ]]></body> |
michael@0 | 719 | </method> |
michael@0 | 720 | |
michael@0 | 721 | <method name="_pauseScroll"> |
michael@0 | 722 | <body><![CDATA[ |
michael@0 | 723 | if (this._mousedown) { |
michael@0 | 724 | this._stopScroll(); |
michael@0 | 725 | this._mousedown = true; |
michael@0 | 726 | document.addEventListener("mouseup", this, false); |
michael@0 | 727 | document.addEventListener("blur", this, true); |
michael@0 | 728 | } |
michael@0 | 729 | ]]></body> |
michael@0 | 730 | </method> |
michael@0 | 731 | |
michael@0 | 732 | <method name="_continueScroll"> |
michael@0 | 733 | <parameter name="index"/> |
michael@0 | 734 | <body><![CDATA[ |
michael@0 | 735 | if (this._mousedown) |
michael@0 | 736 | this._startScroll(index); |
michael@0 | 737 | ]]></body> |
michael@0 | 738 | </method> |
michael@0 | 739 | |
michael@0 | 740 | <method name="handleEvent"> |
michael@0 | 741 | <parameter name="aEvent"/> |
michael@0 | 742 | <body><![CDATA[ |
michael@0 | 743 | if (aEvent.type == "mouseup" || |
michael@0 | 744 | aEvent.type == "blur" && aEvent.target == document) { |
michael@0 | 745 | this._mousedown = false; |
michael@0 | 746 | document.removeEventListener("mouseup", this, false); |
michael@0 | 747 | document.removeEventListener("blur", this, true); |
michael@0 | 748 | } |
michael@0 | 749 | ]]></body> |
michael@0 | 750 | </method> |
michael@0 | 751 | |
michael@0 | 752 | <method name="_distanceScroll"> |
michael@0 | 753 | <parameter name="aEvent"/> |
michael@0 | 754 | <body><![CDATA[ |
michael@0 | 755 | if (aEvent.detail < 2 || aEvent.detail > 3) |
michael@0 | 756 | return; |
michael@0 | 757 | |
michael@0 | 758 | var scrollBack = (aEvent.originalTarget == this._scrollButtonUp); |
michael@0 | 759 | var scrollLeftOrUp = this._isRTLScrollbox ? !scrollBack : scrollBack; |
michael@0 | 760 | var targetElement; |
michael@0 | 761 | |
michael@0 | 762 | if (aEvent.detail == 2) { |
michael@0 | 763 | // scroll by the size of the scrollbox |
michael@0 | 764 | let [start, end] = this._startEndProps; |
michael@0 | 765 | let x; |
michael@0 | 766 | if (scrollLeftOrUp) |
michael@0 | 767 | x = this.scrollClientRect[start] - this.scrollClientSize; |
michael@0 | 768 | else |
michael@0 | 769 | x = this.scrollClientRect[end] + this.scrollClientSize; |
michael@0 | 770 | targetElement = this._elementFromPoint(x, scrollLeftOrUp ? -1 : 1); |
michael@0 | 771 | |
michael@0 | 772 | // the next partly-hidden element will become fully visible, |
michael@0 | 773 | // so don't scroll too far |
michael@0 | 774 | if (targetElement) |
michael@0 | 775 | targetElement = scrollBack ? |
michael@0 | 776 | targetElement.nextSibling : |
michael@0 | 777 | targetElement.previousSibling; |
michael@0 | 778 | } |
michael@0 | 779 | |
michael@0 | 780 | if (!targetElement) { |
michael@0 | 781 | // scroll to the first resp. last element |
michael@0 | 782 | let elements = this._getScrollableElements(); |
michael@0 | 783 | targetElement = scrollBack ? |
michael@0 | 784 | elements[0] : |
michael@0 | 785 | elements[elements.length - 1]; |
michael@0 | 786 | } |
michael@0 | 787 | |
michael@0 | 788 | this.ensureElementIsVisible(targetElement); |
michael@0 | 789 | ]]></body> |
michael@0 | 790 | </method> |
michael@0 | 791 | |
michael@0 | 792 | </implementation> |
michael@0 | 793 | </binding> |
michael@0 | 794 | </bindings> |