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