Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(93)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js

Issue 2592483002: [DevTools] Move console viewport to console, rename StaticViewportControl. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 * Copyright (C) 2013 Google Inc. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be
3 * 3 // found in the LICENSE file.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 /** 4 /**
32 * @unrestricted 5 * @unrestricted
33 */ 6 */
34 UI.ViewportControl = class { 7 UI.ViewportControl = class {
35 /** 8 /**
36 * @param {!UI.ViewportControl.Provider} provider 9 * @param {!UI.ViewportControl.Provider} provider
37 */ 10 */
38 constructor(provider) { 11 constructor(provider) {
39 this.element = createElement('div'); 12 this.element = createElement('div');
40 this.element.style.overflow = 'auto'; 13 this.element.style.overflow = 'auto';
41 this._topGapElement = this.element.createChild('div'); 14 this._innerElement = this.element.createChild('div');
42 this._topGapElement.style.height = '0px'; 15 this._innerElement.style.height = '0px';
43 this._topGapElement.style.color = 'transparent'; 16 this._innerElement.style.position = 'relative';
44 this._contentElement = this.element.createChild('div'); 17 this._innerElement.style.overflow = 'hidden';
45 this._bottomGapElement = this.element.createChild('div');
46 this._bottomGapElement.style.height = '0px';
47 this._bottomGapElement.style.color = 'transparent';
48
49 // Text content needed for range intersection checks in _updateSelectionMode l.
50 // Use Unicode ZERO WIDTH NO-BREAK SPACE, which avoids contributing any heig ht to the element's layout overflow.
51 this._topGapElement.textContent = '\uFEFF';
52 this._bottomGapElement.textContent = '\uFEFF';
53 18
54 this._provider = provider; 19 this._provider = provider;
55 this.element.addEventListener('scroll', this._onScroll.bind(this), false); 20 this.element.addEventListener('scroll', this._update.bind(this), false);
56 this.element.addEventListener('copy', this._onCopy.bind(this), false);
57 this.element.addEventListener('dragstart', this._onDragStart.bind(this), fal se);
58
59 this._firstActiveIndex = 0;
60 this._lastActiveIndex = -1;
61 this._renderedItems = [];
62 this._anchorSelection = null;
63 this._headSelection = null;
64 this._itemCount = 0; 21 this._itemCount = 0;
65 22 this._indexSymbol = Symbol('UI.ViewportControl._indexSymbol');
66 // Listen for any changes to descendants and trigger a refresh. This ensures
67 // that items updated asynchronously will not break stick-to-bottom behavior
68 // if they change the scroll height.
69 this._observer = new MutationObserver(this.refresh.bind(this));
70 this._observerConfig = {childList: true, subtree: true};
71 } 23 }
72 24
73 /** 25 refresh() {
74 * @return {boolean} 26 this._itemCount = this._provider.itemCount();
75 */ 27 this._innerElement.removeChildren();
76 stickToBottom() { 28
77 return this._stickToBottom; 29 var height = 0;
30 this._cumulativeHeights = new Int32Array(this._itemCount);
31 for (var i = 0; i < this._itemCount; ++i) {
32 height += this._provider.fastItemHeight(i);
33 this._cumulativeHeights[i] = height;
34 }
35 this._innerElement.style.height = height + 'px';
36
37 this._update();
78 } 38 }
79 39
80 /** 40 _update() {
81 * @param {boolean} value 41 if (!this._cumulativeHeights) {
82 */ 42 this.refresh();
83 setStickToBottom(value) { 43 return;
84 this._stickToBottom = value; 44 }
85 if (this._stickToBottom)
86 this._observer.observe(this._contentElement, this._observerConfig);
87 else
88 this._observer.disconnect();
89 }
90 45
91 /** 46 var visibleHeight = this._visibleHeight();
92 * @param {!Event} event 47 var visibleFrom = this.element.scrollTop;
93 */ 48 var activeHeight = visibleHeight * 2;
94 _onCopy(event) { 49 var firstActiveIndex = Math.max(
95 var text = this._selectedText(); 50 Array.prototype.lowerBound.call(this._cumulativeHeights, visibleFrom + 1 - (activeHeight - visibleHeight) / 2),
96 if (!text) 51 0);
97 return; 52 var lastActiveIndex = Math.min(
98 event.preventDefault(); 53 Array.prototype.lowerBound.call(
99 event.clipboardData.setData('text/plain', text); 54 this._cumulativeHeights, visibleFrom + visibleHeight + (activeHeight - visibleHeight) / 2),
100 } 55 this._itemCount - 1);
101 56
102 /** 57 var children = this._innerElement.children;
103 * @param {!Event} event 58 for (var i = children.length - 1; i >= 0; --i) {
104 */ 59 var element = children[i];
105 _onDragStart(event) { 60 if (element[this._indexSymbol] < firstActiveIndex || element[this._indexSy mbol] > lastActiveIndex)
106 var text = this._selectedText(); 61 element.remove();
107 if (!text) 62 }
108 return false;
109 event.dataTransfer.clearData();
110 event.dataTransfer.setData('text/plain', text);
111 event.dataTransfer.effectAllowed = 'copy';
112 return true;
113 }
114 63
115 /** 64 for (var i = firstActiveIndex; i <= lastActiveIndex; ++i)
116 * @return {!Element} 65 this._insertElement(i);
117 */
118 contentElement() {
119 return this._contentElement;
120 }
121
122 invalidate() {
123 delete this._cumulativeHeights;
124 delete this._cachedProviderElements;
125 this._itemCount = this._provider.itemCount();
126 this.refresh();
127 } 66 }
128 67
129 /** 68 /**
130 * @param {number} index 69 * @param {number} index
131 * @return {?UI.ViewportElement}
132 */ 70 */
133 _providerElement(index) { 71 _insertElement(index) {
134 if (!this._cachedProviderElements) 72 var element = this._provider.itemElement(index);
135 this._cachedProviderElements = new Array(this._itemCount); 73 if (!element || element.parentElement === this._innerElement)
136 var element = this._cachedProviderElements[index]; 74 return;
137 if (!element) {
138 element = this._provider.itemElement(index);
139 this._cachedProviderElements[index] = element;
140 }
141 return element;
142 }
143 75
144 _rebuildCumulativeHeightsIfNeeded() { 76 element.style.position = 'absolute';
145 if (this._cumulativeHeights) 77 element.style.top = (this._cumulativeHeights[index - 1] || 0) + 'px';
146 return; 78 element.style.left = '0';
147 if (!this._itemCount) 79 element.style.right = '0';
148 return; 80 element[this._indexSymbol] = index;
149 var firstActiveIndex = this._firstActiveIndex; 81 this._innerElement.appendChild(element);
150 var lastActiveIndex = this._lastActiveIndex;
151 var height = 0;
152 this._cumulativeHeights = new Int32Array(this._itemCount);
153 for (var i = 0; i < this._itemCount; ++i) {
154 if (firstActiveIndex <= i && i <= lastActiveIndex)
155 height += this._renderedItems[i - firstActiveIndex].element().offsetHeig ht;
156 else
157 height += this._provider.fastHeight(i);
158 this._cumulativeHeights[i] = height;
159 }
160 }
161
162 /**
163 * @param {number} index
164 * @return {number}
165 */
166 _cachedItemHeight(index) {
167 return index === 0 ? this._cumulativeHeights[0] :
168 this._cumulativeHeights[index] - this._cumulativeHeight s[index - 1];
169 }
170
171 /**
172 * @param {?Selection} selection
173 * @suppressGlobalPropertiesCheck
174 */
175 _isSelectionBackwards(selection) {
176 if (!selection || !selection.rangeCount)
177 return false;
178 var range = document.createRange();
179 range.setStart(selection.anchorNode, selection.anchorOffset);
180 range.setEnd(selection.focusNode, selection.focusOffset);
181 return range.collapsed;
182 }
183
184 /**
185 * @param {number} itemIndex
186 * @param {!Node} node
187 * @param {number} offset
188 * @return {!{item: number, node: !Node, offset: number}}
189 */
190 _createSelectionModel(itemIndex, node, offset) {
191 return {item: itemIndex, node: node, offset: offset};
192 }
193
194 /**
195 * @param {?Selection} selection
196 */
197 _updateSelectionModel(selection) {
198 var range = selection && selection.rangeCount ? selection.getRangeAt(0) : nu ll;
199 if (!range || selection.isCollapsed || !this.element.hasSelection()) {
200 this._headSelection = null;
201 this._anchorSelection = null;
202 return false;
203 }
204
205 var firstSelected = Number.MAX_VALUE;
206 var lastSelected = -1;
207
208 var hasVisibleSelection = false;
209 for (var i = 0; i < this._renderedItems.length; ++i) {
210 if (range.intersectsNode(this._renderedItems[i].element())) {
211 var index = i + this._firstActiveIndex;
212 firstSelected = Math.min(firstSelected, index);
213 lastSelected = Math.max(lastSelected, index);
214 hasVisibleSelection = true;
215 }
216 }
217 if (hasVisibleSelection) {
218 firstSelected =
219 this._createSelectionModel(firstSelected, /** @type {!Node} */ (range. startContainer), range.startOffset);
220 lastSelected =
221 this._createSelectionModel(lastSelected, /** @type {!Node} */ (range.e ndContainer), range.endOffset);
222 }
223 var topOverlap = range.intersectsNode(this._topGapElement) && this._topGapEl ement._active;
224 var bottomOverlap = range.intersectsNode(this._bottomGapElement) && this._bo ttomGapElement._active;
225 if (!topOverlap && !bottomOverlap && !hasVisibleSelection) {
226 this._headSelection = null;
227 this._anchorSelection = null;
228 return false;
229 }
230
231 if (!this._anchorSelection || !this._headSelection) {
232 this._anchorSelection = this._createSelectionModel(0, this.element, 0);
233 this._headSelection = this._createSelectionModel(this._itemCount - 1, this .element, this.element.children.length);
234 this._selectionIsBackward = false;
235 }
236
237 var isBackward = this._isSelectionBackwards(selection);
238 var startSelection = this._selectionIsBackward ? this._headSelection : this. _anchorSelection;
239 var endSelection = this._selectionIsBackward ? this._anchorSelection : this. _headSelection;
240 if (topOverlap && bottomOverlap && hasVisibleSelection) {
241 firstSelected = firstSelected.item < startSelection.item ? firstSelected : startSelection;
242 lastSelected = lastSelected.item > endSelection.item ? lastSelected : endS election;
243 } else if (!hasVisibleSelection) {
244 firstSelected = startSelection;
245 lastSelected = endSelection;
246 } else if (topOverlap) {
247 firstSelected = isBackward ? this._headSelection : this._anchorSelection;
248 } else if (bottomOverlap) {
249 lastSelected = isBackward ? this._anchorSelection : this._headSelection;
250 }
251
252 if (isBackward) {
253 this._anchorSelection = lastSelected;
254 this._headSelection = firstSelected;
255 } else {
256 this._anchorSelection = firstSelected;
257 this._headSelection = lastSelected;
258 }
259 this._selectionIsBackward = isBackward;
260 return true;
261 }
262
263 /**
264 * @param {?Selection} selection
265 */
266 _restoreSelection(selection) {
267 var anchorElement = null;
268 var anchorOffset;
269 if (this._firstActiveIndex <= this._anchorSelection.item && this._anchorSele ction.item <= this._lastActiveIndex) {
270 anchorElement = this._anchorSelection.node;
271 anchorOffset = this._anchorSelection.offset;
272 } else {
273 if (this._anchorSelection.item < this._firstActiveIndex)
274 anchorElement = this._topGapElement;
275 else if (this._anchorSelection.item > this._lastActiveIndex)
276 anchorElement = this._bottomGapElement;
277 anchorOffset = this._selectionIsBackward ? 1 : 0;
278 }
279
280 var headElement = null;
281 var headOffset;
282 if (this._firstActiveIndex <= this._headSelection.item && this._headSelectio n.item <= this._lastActiveIndex) {
283 headElement = this._headSelection.node;
284 headOffset = this._headSelection.offset;
285 } else {
286 if (this._headSelection.item < this._firstActiveIndex)
287 headElement = this._topGapElement;
288 else if (this._headSelection.item > this._lastActiveIndex)
289 headElement = this._bottomGapElement;
290 headOffset = this._selectionIsBackward ? 0 : 1;
291 }
292
293 selection.setBaseAndExtent(anchorElement, anchorOffset, headElement, headOff set);
294 }
295
296 refresh() {
297 this._observer.disconnect();
298 this._innerRefresh();
299 if (this._stickToBottom)
300 this._observer.observe(this._contentElement, this._observerConfig);
301 }
302
303 _innerRefresh() {
304 if (!this._visibleHeight())
305 return; // Do nothing for invisible controls.
306
307 if (!this._itemCount) {
308 for (var i = 0; i < this._renderedItems.length; ++i)
309 this._renderedItems[i].willHide();
310 this._renderedItems = [];
311 this._contentElement.removeChildren();
312 this._topGapElement.style.height = '0px';
313 this._bottomGapElement.style.height = '0px';
314 this._firstActiveIndex = -1;
315 this._lastActiveIndex = -1;
316 return;
317 }
318
319 var selection = this.element.getComponentSelection();
320 var shouldRestoreSelection = this._updateSelectionModel(selection);
321
322 var visibleFrom = this.element.scrollTop;
323 var visibleHeight = this._visibleHeight();
324 var isInvalidating = !this._cumulativeHeights;
325
326 for (var i = 0; i < this._renderedItems.length; ++i) {
327 // Tolerate 1-pixel error due to double-to-integer rounding errors.
328 if (this._cumulativeHeights &&
329 Math.abs(this._cachedItemHeight(this._firstActiveIndex + i) - this._re nderedItems[i].element().offsetHeight) >
330 1)
331 delete this._cumulativeHeights;
332 }
333 this._rebuildCumulativeHeightsIfNeeded();
334 var oldFirstActiveIndex = this._firstActiveIndex;
335 var oldLastActiveIndex = this._lastActiveIndex;
336 var activeHeight = visibleHeight * 2;
337 // When the viewport is scrolled to the bottom, using the cumulative heights estimate is not
338 // precise enough to determine next visible indices. This stickToBottom chec k avoids extra
339 // calls to refresh in those cases.
340 if (this._stickToBottom) {
341 this._firstActiveIndex =
342 Math.max(this._itemCount - Math.ceil(activeHeight / this._provider.min imumRowHeight()), 0);
343 this._lastActiveIndex = this._itemCount - 1;
344 } else {
345 this._firstActiveIndex = Math.max(
346 Array.prototype.lowerBound.call(
347 this._cumulativeHeights, visibleFrom + 1 - (activeHeight - visible Height) / 2),
348 0);
349 // Proactively render more rows in case some of them will be collapsed wit hout triggering refresh. @see crbug.com/390169
350 this._lastActiveIndex = this._firstActiveIndex + Math.ceil(activeHeight / this._provider.minimumRowHeight()) - 1;
351 this._lastActiveIndex = Math.min(this._lastActiveIndex, this._itemCount - 1);
352 }
353
354 var topGapHeight = this._cumulativeHeights[this._firstActiveIndex - 1] || 0;
355 var bottomGapHeight =
356 this._cumulativeHeights[this._cumulativeHeights.length - 1] - this._cumu lativeHeights[this._lastActiveIndex];
357
358 /**
359 * @this {UI.ViewportControl}
360 */
361 function prepare() {
362 this._topGapElement.style.height = topGapHeight + 'px';
363 this._bottomGapElement.style.height = bottomGapHeight + 'px';
364 this._topGapElement._active = !!topGapHeight;
365 this._bottomGapElement._active = !!bottomGapHeight;
366 this._contentElement.style.setProperty('height', '10000000px');
367 }
368
369 if (isInvalidating)
370 this._fullViewportUpdate(prepare.bind(this));
371 else
372 this._partialViewportUpdate(oldFirstActiveIndex, oldLastActiveIndex, prepa re.bind(this));
373 this._contentElement.style.removeProperty('height');
374 // Should be the last call in the method as it might force layout.
375 if (shouldRestoreSelection)
376 this._restoreSelection(selection);
377 if (this._stickToBottom)
378 this.element.scrollTop = 10000000;
379 }
380
381 /**
382 * @param {function()} prepare
383 */
384 _fullViewportUpdate(prepare) {
385 for (var i = 0; i < this._renderedItems.length; ++i)
386 this._renderedItems[i].willHide();
387 prepare();
388 this._renderedItems = [];
389 this._contentElement.removeChildren();
390 for (var i = this._firstActiveIndex; i <= this._lastActiveIndex; ++i) {
391 var viewportElement = this._providerElement(i);
392 this._contentElement.appendChild(viewportElement.element());
393 this._renderedItems.push(viewportElement);
394 }
395 for (var i = 0; i < this._renderedItems.length; ++i)
396 this._renderedItems[i].wasShown();
397 }
398
399 /**
400 * @param {number} oldFirstActiveIndex
401 * @param {number} oldLastActiveIndex
402 * @param {function()} prepare
403 */
404 _partialViewportUpdate(oldFirstActiveIndex, oldLastActiveIndex, prepare) {
405 var willBeHidden = [];
406 for (var i = 0; i < this._renderedItems.length; ++i) {
407 var index = oldFirstActiveIndex + i;
408 if (index < this._firstActiveIndex || this._lastActiveIndex < index)
409 willBeHidden.push(this._renderedItems[i]);
410 }
411 for (var i = 0; i < willBeHidden.length; ++i)
412 willBeHidden[i].willHide();
413 prepare();
414 for (var i = 0; i < willBeHidden.length; ++i)
415 willBeHidden[i].element().remove();
416
417 this._renderedItems = [];
418 var anchor = this._contentElement.firstChild;
419 var wasShown = [];
420 for (var i = this._firstActiveIndex; i <= this._lastActiveIndex; ++i) {
421 var viewportElement = this._providerElement(i);
422 var element = viewportElement.element();
423 if (element !== anchor) {
424 this._contentElement.insertBefore(element, anchor);
425 wasShown.push(viewportElement);
426 } else {
427 anchor = anchor.nextSibling;
428 }
429 this._renderedItems.push(viewportElement);
430 }
431 for (var i = 0; i < wasShown.length; ++i)
432 wasShown[i].wasShown();
433 }
434
435 /**
436 * @return {?string}
437 */
438 _selectedText() {
439 this._updateSelectionModel(this.element.getComponentSelection());
440 if (!this._headSelection || !this._anchorSelection)
441 return null;
442
443 var startSelection = null;
444 var endSelection = null;
445 if (this._selectionIsBackward) {
446 startSelection = this._headSelection;
447 endSelection = this._anchorSelection;
448 } else {
449 startSelection = this._anchorSelection;
450 endSelection = this._headSelection;
451 }
452
453 var textLines = [];
454 for (var i = startSelection.item; i <= endSelection.item; ++i)
455 textLines.push(this._providerElement(i).element().deepTextContent());
456
457 var endSelectionElement = this._providerElement(endSelection.item).element() ;
458 if (endSelection.node && endSelection.node.isSelfOrDescendant(endSelectionEl ement)) {
459 var itemTextOffset = this._textOffsetInNode(endSelectionElement, endSelect ion.node, endSelection.offset);
460 textLines[textLines.length - 1] = textLines.peekLast().substring(0, itemTe xtOffset);
461 }
462
463 var startSelectionElement = this._providerElement(startSelection.item).eleme nt();
464 if (startSelection.node && startSelection.node.isSelfOrDescendant(startSelec tionElement)) {
465 var itemTextOffset = this._textOffsetInNode(startSelectionElement, startSe lection.node, startSelection.offset);
466 textLines[0] = textLines[0].substring(itemTextOffset);
467 }
468
469 return textLines.join('\n');
470 }
471
472 /**
473 * @param {!Element} itemElement
474 * @param {!Node} container
475 * @param {number} offset
476 * @return {number}
477 */
478 _textOffsetInNode(itemElement, container, offset) {
479 var chars = 0;
480 var node = itemElement;
481 while ((node = node.traverseNextTextNode()) && !node.isSelfOrDescendant(cont ainer))
482 chars += node.textContent.length;
483 return chars + offset;
484 }
485
486 /**
487 * @param {!Event} event
488 */
489 _onScroll(event) {
490 this.refresh();
491 } 82 }
492 83
493 /** 84 /**
494 * @return {number} 85 * @return {number}
495 */ 86 */
496 firstVisibleIndex() { 87 firstVisibleIndex() {
497 var firstVisibleIndex = 88 return Math.max(Array.prototype.lowerBound.call(this._cumulativeHeights, thi s.element.scrollTop + 1), 0);
498 Math.max(Array.prototype.lowerBound.call(this._cumulativeHeights, this.e lement.scrollTop + 1), 0);
499 return Math.max(firstVisibleIndex, this._firstActiveIndex);
500 } 89 }
501 90
502 /** 91 /**
503 * @return {number} 92 * @return {number}
504 */ 93 */
505 lastVisibleIndex() { 94 lastVisibleIndex() {
506 var lastVisibleIndex; 95 return Math.min(
507 if (this._stickToBottom) { 96 Array.prototype.lowerBound.call(this._cumulativeHeights, this.element.sc rollTop + this._visibleHeight()),
508 lastVisibleIndex = this._itemCount - 1; 97 this._itemCount);
509 } else {
510 lastVisibleIndex =
511 this.firstVisibleIndex() + Math.ceil(this._visibleHeight() / this._pro vider.minimumRowHeight()) - 1;
512 }
513 return Math.min(lastVisibleIndex, this._lastActiveIndex);
514 } 98 }
515 99
516 /** 100 /**
517 * @return {?Element}
518 */
519 renderedElementAt(index) {
520 if (index < this._firstActiveIndex)
521 return null;
522 if (index > this._lastActiveIndex)
523 return null;
524 return this._renderedItems[index - this._firstActiveIndex].element();
525 }
526
527 /**
528 * @param {number} index 101 * @param {number} index
529 * @param {boolean=} makeLast 102 * @param {boolean=} makeLast
530 */ 103 */
531 scrollItemIntoView(index, makeLast) { 104 scrollItemIntoView(index, makeLast) {
532 var firstVisibleIndex = this.firstVisibleIndex(); 105 var firstVisibleIndex = this.firstVisibleIndex();
533 var lastVisibleIndex = this.lastVisibleIndex(); 106 var lastVisibleIndex = this.lastVisibleIndex();
534 if (index > firstVisibleIndex && index < lastVisibleIndex) 107 if (index > firstVisibleIndex && index < lastVisibleIndex)
535 return; 108 return;
536 if (makeLast) 109 if (makeLast)
537 this.forceScrollItemToBeLast(index); 110 this.forceScrollItemToBeLast(index);
538 else if (index <= firstVisibleIndex) 111 else if (index <= firstVisibleIndex)
539 this.forceScrollItemToBeFirst(index); 112 this.forceScrollItemToBeFirst(index);
540 else if (index >= lastVisibleIndex) 113 else if (index >= lastVisibleIndex)
541 this.forceScrollItemToBeLast(index); 114 this.forceScrollItemToBeLast(index);
542 } 115 }
543 116
544 /** 117 /**
545 * @param {number} index 118 * @param {number} index
546 */ 119 */
547 forceScrollItemToBeFirst(index) { 120 forceScrollItemToBeFirst(index) {
548 this.setStickToBottom(false);
549 this._rebuildCumulativeHeightsIfNeeded();
550 this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1] : 0; 121 this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1] : 0;
551 if (this.element.isScrolledToBottom()) 122 this._update();
552 this.setStickToBottom(true);
553 this.refresh();
554 } 123 }
555 124
556 /** 125 /**
557 * @param {number} index 126 * @param {number} index
558 */ 127 */
559 forceScrollItemToBeLast(index) { 128 forceScrollItemToBeLast(index) {
560 this.setStickToBottom(false);
561 this._rebuildCumulativeHeightsIfNeeded();
562 this.element.scrollTop = this._cumulativeHeights[index] - this._visibleHeigh t(); 129 this.element.scrollTop = this._cumulativeHeights[index] - this._visibleHeigh t();
563 if (this.element.isScrolledToBottom()) 130 this._update();
564 this.setStickToBottom(true);
565 this.refresh();
566 } 131 }
567 132
568 /** 133 /**
569 * @return {number} 134 * @return {number}
570 */ 135 */
571 _visibleHeight() { 136 _visibleHeight() {
572 // Use offsetHeight instead of clientHeight to avoid being affected by horiz ontal scroll.
573 return this.element.offsetHeight; 137 return this.element.offsetHeight;
574 } 138 }
575 }; 139 };
576 140
577 /** 141 /**
578 * @interface 142 * @interface
579 */ 143 */
580 UI.ViewportControl.Provider = function() {}; 144 UI.ViewportControl.Provider = function() {};
581 145
582 UI.ViewportControl.Provider.prototype = { 146 UI.ViewportControl.Provider.prototype = {
583 /** 147 /**
584 * @param {number} index 148 * @param {number} index
585 * @return {number} 149 * @return {number}
586 */ 150 */
587 fastHeight(index) { 151 fastItemHeight(index) {
588 return 0; 152 return 0;
589 }, 153 },
590 154
591 /** 155 /**
592 * @return {number} 156 * @return {number}
593 */ 157 */
594 itemCount() { 158 itemCount() {
595 return 0; 159 return 0;
596 }, 160 },
597 161
598 /** 162 /**
599 * @return {number}
600 */
601 minimumRowHeight() {
602 return 0;
603 },
604
605 /**
606 * @param {number} index 163 * @param {number} index
607 * @return {?UI.ViewportElement} 164 * @return {?Element}
608 */ 165 */
609 itemElement(index) { 166 itemElement(index) {
610 return null; 167 return null;
611 } 168 }
612 }; 169 };
613
614 /**
615 * @interface
616 */
617 UI.ViewportElement = function() {};
618 UI.ViewportElement.prototype = {
619 willHide() {},
620
621 wasShown() {},
622
623 /**
624 * @return {!Element}
625 */
626 element() {},
627 };
628
629 /**
630 * @implements {UI.ViewportElement}
631 * @unrestricted
632 */
633 UI.StaticViewportElement = class {
634 /**
635 * @param {!Element} element
636 */
637 constructor(element) {
638 this._element = element;
639 }
640
641 /**
642 * @override
643 */
644 willHide() {
645 }
646
647 /**
648 * @override
649 */
650 wasShown() {
651 }
652
653 /**
654 * @override
655 * @return {!Element}
656 */
657 element() {
658 return this._element;
659 }
660 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698